diff --git a/ui/src/pages/chat/background-tasks.e2e.test.ts b/ui/src/pages/chat/background-tasks.e2e.test.ts index 0a4597d5534c..ee97ed3cd668 100644 --- a/ui/src/pages/chat/background-tasks.e2e.test.ts +++ b/ui/src/pages/chat/background-tasks.e2e.test.ts @@ -160,6 +160,12 @@ describeControlUiE2e("Control UI chat background-tasks rail mocked Gateway E2E", const rail = page.locator(".chat-tasks-rail"); await rail.locator('[data-task-id="task-subagent"]').waitFor({ state: "visible" }); await rail.locator('[data-task-id="task-cron"]').waitFor({ state: "visible" }); + // Finished history starts collapsed: only the section header with the + // count renders until it is expanded. + const finishedToggle = rail.getByRole("button", { name: "Finished (1)" }); + await finishedToggle.waitFor({ state: "visible" }); + expect(await rail.locator('[data-task-id="task-cli"]').count()).toBe(0); + await finishedToggle.click(); await rail.locator('[data-task-id="task-cli"]').waitFor({ state: "visible" }); const railText = await rail.textContent(); expect(railText).toContain("Reading provider catalogs"); diff --git a/ui/src/pages/chat/components/chat-background-tasks.test.ts b/ui/src/pages/chat/components/chat-background-tasks.test.ts index 77c6f67cd783..2248155aa746 100644 --- a/ui/src/pages/chat/components/chat-background-tasks.test.ts +++ b/ui/src/pages/chat/components/chat-background-tasks.test.ts @@ -87,6 +87,7 @@ describe("background tasks rail state", () => { const props = createBackgroundTasksProps(host, openSession); expect(props.collapsed).toBe(true); + expect(props.finishedCollapsed).toBe(true); expect(request).toHaveBeenCalledTimes(2); expect(props.tasks?.map((task) => task.id)).toEqual(["task-1"]); }); @@ -201,9 +202,10 @@ describe("background tasks rail state", () => { ); }; host.requestUpdate = renderRail; + // finishedCollapsed defaults to true; back-navigation from a finished + // detail must expand the section so the returned-to row stays visible. const initialProps = createBackgroundTasksProps(host, openSession); initialProps.onToggleCollapsed(); - initialProps.onToggleFinished(); renderRail(); const disclosure = container.querySelector( diff --git a/ui/src/pages/chat/components/chat-background-tasks.ts b/ui/src/pages/chat/components/chat-background-tasks.ts index 3fb401cfcbb0..262ee0bc8bdd 100644 --- a/ui/src/pages/chat/components/chat-background-tasks.ts +++ b/ui/src/pages/chat/components/chat-background-tasks.ts @@ -82,7 +82,9 @@ function getBackgroundTasksState(host: BackgroundTasksHost): BackgroundTasksStat // agent switches and only reload the task list for the new scope. collapsed: current?.collapsed ?? true, error: null, - finishedCollapsed: current?.finishedCollapsed ?? false, + // Finished history starts collapsed so active work owns the rail; the + // section header still shows the count for discoverability. + finishedCollapsed: current?.finishedCollapsed ?? true, loadedClient: null, loading: false, pendingReload: false, @@ -588,7 +590,7 @@ export function renderBackgroundTasksRail( ${empty ? html`
${t("chat.backgroundTasks.empty")}
` : nothing} -
+
${active.length > 0 ? html`
diff --git a/ui/src/styles/chat/sidebar.css b/ui/src/styles/chat/sidebar.css index 7822948b90ca..07b2f8bdff0c 100644 --- a/ui/src/styles/chat/sidebar.css +++ b/ui/src/styles/chat/sidebar.css @@ -39,13 +39,13 @@ rules sit after the collapse/dock modifiers so the extra column wins at equal specificity. */ .chat-workbench--tasks-open { - grid-template-columns: minmax(0, 1fr) minmax(230px, 280px); + grid-template-columns: minmax(0, 1fr) minmax(270px, 330px); } .chat-workbench--tasks-open:not(.chat-workbench--workspace-collapsed):not( .chat-workbench--dock-bottom ) { - grid-template-columns: minmax(0, 1fr) minmax(230px, 280px) minmax(230px, 280px); + grid-template-columns: minmax(0, 1fr) minmax(230px, 280px) minmax(270px, 330px); } /* Compact icon button shared by the pane header actions. Sizing only — chrome @@ -873,6 +873,13 @@ openclaw-chat-sidebar-region, overflow: auto; } +/* List mode splits scrolling per section: running work owns the rail and the + finished history pins to the bottom, so a long history can never clip the + active tasks (each section scrolls on its own instead of the container). */ +.chat-tasks-rail__scroll--split { + overflow: hidden; +} + .chat-tasks-rail__section { display: flex; flex: 0 1 auto; @@ -882,6 +889,16 @@ openclaw-chat-sidebar-region, padding-top: 8px; } +.chat-tasks-rail__scroll--split > [data-tasks-section="running"] { + flex: 1 1 auto; +} + +/* Cap only when running work is present; alone, finished may fill the rail. */ +.chat-tasks-rail__scroll--split > [data-tasks-section="running"] + [data-tasks-section="finished"] { + max-height: 50%; + border-top: 1px solid color-mix(in srgb, var(--border) 42%, transparent); +} + .chat-tasks-rail__section-title { color: var(--muted); font-size: var(--control-ui-text-xs); @@ -1836,6 +1853,10 @@ openclaw-session-discussion { flex: 1 0 260px; min-width: 0; overflow-y: auto; + /* Undo the vertical split's bottom-pin cap: strip sections are full-height + columns and separate with border-left instead. */ + max-height: none; + border-top: none; } .chat-workbench--tasks-dock-bottom