diff --git a/docs/web/control-ui.md b/docs/web/control-ui.md index 43726519f593..a86fa17bb63f 100644 --- a/docs/web/control-ui.md +++ b/docs/web/control-ui.md @@ -308,7 +308,7 @@ The macOS app keeps its native link-browser sidebar for links clicked in the das - The session diff panel shows what a session's checkout actually changed: the branch button (in the workspace rail header, the split-pane header, or the floating button in single-pane chat) opens the detail panel with a per-file diff of branch, uncommitted, and untracked work against the checkout's default-branch merge base — status dot, rename arrow, per-file +/− counts, collapsible files, and "N unmodified lines" markers between hunks. Diffs are computed server-side through the `sessions.diff` Gateway method (`operator.read` scope); binary and oversized files degrade to stats-only entries, and the button only appears when the connected Gateway advertises `sessions.diff`. - The session workspace rail in each Chat pane lists session files, project files, and artifacts. It docks to the pane's right edge by default; drag its header (or use the dock button) to move it to the bottom, and the choice is stored in the current browser profile. A collapsed rail takes no space at all: reopen it with ⇧⌘B, the files toggle in the split-pane header, or the floating files button in single-pane chat (both carry a changed-file count badge). The separate file, tool, and Canvas detail panel is unaffected. - The background tasks rail in each Chat pane lists the current agent's background tasks and subagents (`tasks.list` scoped by agent, kept live by `task` events): running work with a stop control, a collapsible finished section, and a View transcript link that opens the task's child session in the pane. Open it with the activity toggle in the split-pane header or the floating activity button in single-pane chat (both carry a running-count badge once loaded). The Tasks page remains the full cross-agent ledger. - - The workspace rail and detail panel adapt to each pane's own width rather than the window: in a narrow pane or compact window the workspace rail always presents as a bottom strip (side-dock controls hide until the pane widens), and the detail panel stacks below the thread with a horizontal resize handle instead of sharing the row with it. Phone-sized viewports still open the detail panel full-screen. + - The workspace rail, background tasks rail, and detail panel adapt to each pane's own width rather than the window: in a narrow pane or compact window both rails present as bottom strips (side-dock controls hide until the pane widens; the workspace rail keeps first claim on the side slot when only one column fits), and the detail panel stacks below the thread with a horizontal resize handle instead of sharing the row with it. Phone-sized viewports still open the detail panel full-screen. - The chat header model and thinking pickers patch the active session immediately through `sessions.patch`; they are persistent session overrides, not one-turn-only send options. - **Split view:** open it from the top-right floating toggle row (beside the session diff, background tasks, and session files toggles), then split the active pane right or down for as many panes as fit. Each pane has its own session, transcript, composer, and tool stream. - Drag a session from the sidebar into chat to open it in a pane. An animated drop preview glides between zones and labels the outcome — "Split" over the exact half a new pane will occupy, "Open here" over a whole pane — and drops also work from single-pane mode. diff --git a/ui/src/pages/chat/chat-pane.ts b/ui/src/pages/chat/chat-pane.ts index 792419a5f6f3..9199e5b6ad14 100644 --- a/ui/src/pages/chat/chat-pane.ts +++ b/ui/src/pages/chat/chat-pane.ts @@ -1283,18 +1283,24 @@ class ChatPane extends OpenClawLightDomElement { const sessionWorkspace = createSessionWorkspaceProps(state, { narrowLayout: this.paneWidth < WORKSPACE_RAIL_SIDE_MIN_PANE_WIDTH, }); - const backgroundTasks = createBackgroundTasksProps(state, { - onOpenSession: (sessionKey) => { - this.onPaneSessionChange?.(this.paneId, sessionKey); - }, - }); const railSideDocked = !sessionWorkspace.collapsed && !sessionWorkspace.narrowLayout && sessionWorkspace.dock !== "bottom"; - // Every open side rail (workspace and/or background tasks) narrows the - // room left for the chat + detail split. - const sideRailCount = (railSideDocked ? 1 : 0) + (backgroundTasks.collapsed ? 0 : 1); + // The workspace rail claims the side slot first; the tasks rail needs + // room for both columns before it may side-dock next to it. + const backgroundTasks = createBackgroundTasksProps(state, { + narrowLayout: + this.paneWidth < + WORKSPACE_RAIL_SIDE_MIN_PANE_WIDTH + (railSideDocked ? WORKSPACE_RAIL_MAX_WIDTH : 0), + onOpenSession: (sessionKey) => { + this.onPaneSessionChange?.(this.paneId, sessionKey); + }, + }); + const tasksSideDocked = !backgroundTasks.collapsed && !backgroundTasks.narrowLayout; + // Every side-docked rail narrows the room left for the chat + detail + // split; bottom strips do not. + const sideRailCount = (railSideDocked ? 1 : 0) + (tasksSideDocked ? 1 : 0); const detailSplitWidth = this.paneWidth - sideRailCount * WORKSPACE_RAIL_MAX_WIDTH; const props: ChatProps = { paneId: this.paneId, diff --git a/ui/src/pages/chat/chat-responsive.browser.test.ts b/ui/src/pages/chat/chat-responsive.browser.test.ts index 6f25183896f9..3510ed4a7b72 100644 --- a/ui/src/pages/chat/chat-responsive.browser.test.ts +++ b/ui/src/pages/chat/chat-responsive.browser.test.ts @@ -1464,17 +1464,23 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => { } }); - it("releases the hidden tasks-rail grid track on narrow viewports", async () => { - const page = await openBrowserPage(1000, 700); + it("docks the tasks rail as a full-width bottom strip in a narrow pane", async () => { + const page = await openBrowserPage(1200, 700); try { - // Below 1120px the background-tasks rail is display:none; its tasks-open - // grid templates must collapse so no empty column strip stays reserved. + // chat-pane sets --tasks-dock-bottom instead of --tasks-open when the + // pane is too narrow for a side column; the strip spans the pane and + // sits below the thread, composing with a bottom-docked workspace rail. await page.setContent( `
-