From cf1bafae0039b41bd63246d06d0a42b346071d45 Mon Sep 17 00:00:00 2001 From: ClawSweeper Date: Sat, 25 Jul 2026 14:16:00 -0700 Subject: [PATCH] fix(ui): space coding session groups (#113840) Co-authored-by: Peter Steinberger --- ui/src/e2e/codex-sessions.e2e.test.ts | 100 ++++++++++++++++++++++++++ ui/src/styles/layout.css | 6 ++ 2 files changed, 106 insertions(+) diff --git a/ui/src/e2e/codex-sessions.e2e.test.ts b/ui/src/e2e/codex-sessions.e2e.test.ts index 86c7d1c285e0..69a853738843 100644 --- a/ui/src/e2e/codex-sessions.e2e.test.ts +++ b/ui/src/e2e/codex-sessions.e2e.test.ts @@ -99,6 +99,106 @@ suite("Codex native session catalog", () => { await page.close(); }); + it("separates native catalogs from live Coding rows", async () => { + const page = await browser.newPage({ + deviceScaleFactor: 2, + viewport: { height: 900, width: 1280 }, + }); + await page.addInitScript( + (key) => localStorage.removeItem(key), + collapsedSessionSectionsStorageKey, + ); + await installMockGateway(page, { + featureMethods: ["chat.metadata", "chat.startup", "sessions.catalog.list"], + methodResponses: { + "sessions.list": { + count: 1, + defaults: { + contextTokens: null, + model: "gpt-5.5", + modelProvider: "openai", + }, + path: "", + sessions: [ + { + contextTokens: null, + displayName: "Understanding Startup Phases and Delays", + hasActiveRun: true, + key: "agent:main:startup-phases", + kind: "direct", + label: "Understanding Startup Phases and Delays", + model: "gpt-5.5", + modelProvider: "openai", + status: "running", + totalTokens: 0, + updatedAt: Date.now(), + worktree: { + id: "startup-phases", + branch: "startup-phases", + repoRoot: "/workspace/openclaw", + }, + }, + ], + ts: Date.now(), + }, + "sessions.catalog.list": { + catalogs: [ + { + id: "codex", + label: "Codex", + capabilities: { continueSession: true, archive: true, createSession: true }, + hosts: [ + { + hostId: "gateway:local", + label: "Local Codex", + kind: "gateway", + connected: true, + sessions: [ + { + threadId: "thread-startup", + name: "Trace startup labels to code paths", + cwd: "/workspace/openclaw", + status: "idle", + archived: false, + canContinue: true, + canArchive: true, + }, + ], + }, + ], + }, + ], + }, + }, + }); + + try { + await page.goto(`${server.baseUrl}chat`); + await page.evaluate(() => document.documentElement.setAttribute("data-theme-mode", "dark")); + await expandCodingSection(page); + const workSection = page.locator('[data-session-section="work"]'); + const liveRows = workSection.locator(":scope > .sidebar-recent-sessions__list"); + const catalog = workSection.locator(':scope > [data-session-section="catalog:codex"]'); + await catalog.waitFor({ state: "visible" }); + const [liveRowsBox, catalogBox] = await Promise.all([ + liveRows.boundingBox(), + catalog.boundingBox(), + ]); + expect(liveRowsBox).not.toBeNull(); + expect(catalogBox).not.toBeNull(); + expect(Math.round(catalogBox!.y - (liveRowsBox!.y + liveRowsBox!.height))).toBe(10); + if (captureUiProofEnabled) { + await mkdir(uiProofArtifactDir, { recursive: true }); + await workSection.screenshot({ + animations: "disabled", + path: path.join(uiProofArtifactDir, "06-coding-catalog-spacing.png"), + }); + } + } finally { + await page.close(); + } + }); + it("shows a completed host while the aggregate catalog request is still pending", async () => { const page = await browser.newPage({ viewport: { height: 900, width: 1280 } }); const gateway = await installMockGateway(page, { diff --git a/ui/src/styles/layout.css b/ui/src/styles/layout.css index 2dab71366d74..d40b33508a8a 100644 --- a/ui/src/styles/layout.css +++ b/ui/src/styles/layout.css @@ -1250,6 +1250,12 @@ html.openclaw-native-macos box-shadow var(--duration-fast) ease; } +/* Catalog sections are nested inside Coding, so add to its 2px item gap to + match the 10px rhythm between top-level session groups. */ +.sidebar-recent-sessions__group--zone-coding > .sidebar-recent-sessions__group { + margin-top: 8px; +} + .sidebar-recent-sessions__group--session-drop { background: color-mix(in srgb, var(--accent) 10%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent);