From 7d1958bf616fbadd3ce5b1846e877dc9b6e39dcf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 9 Aug 2026 22:31:17 -0700 Subject: [PATCH] fix(ui): center the onboarding dashboard and fix its card chrome (#121416) * fix(ui): center onboarding dashboard and quiet its chrome The custodian onboarding route carried the settings takeover class, so the shell reserved a hidden 288px sidebar column and the whole page rendered off-center. Onboarding now opts out of the takeover, drops the duplicate header identity (the thread already introduces the mascot), and option-card hover no longer lifts or re-tints cards. * fix(ui): stop next-step cards overlaying the message meta row The option-card and wizard-step thread attachments pulled themselves up with a -12px top margin, covering the preceding message's sender/timestamp line. * test(ui): align nudge e2e with the minimal onboarding header --- ui/src/app/app-shell-view.ts | 4 ++- ui/src/e2e/custodian-event-nudge.e2e.test.ts | 3 +- ui/src/e2e/model-setup.e2e.test.ts | 4 ++- ui/src/pages/custodian/custodian-page.test.ts | 3 +- ui/src/pages/custodian/custodian-page.ts | 32 +++++++++++-------- ui/src/pages/custodian/route.test.ts | 7 ++-- ui/src/styles/custodian.css | 15 +++++++-- ui/src/styles/option-card.css | 12 +++++-- 8 files changed, 54 insertions(+), 26 deletions(-) diff --git a/ui/src/app/app-shell-view.ts b/ui/src/app/app-shell-view.ts index dabc7b3399d0..b9d7a25ef3db 100644 --- a/ui/src/app/app-shell-view.ts +++ b/ui/src/app/app-shell-view.ts @@ -125,7 +125,9 @@ export function renderApplicationShell(host: ShellViewHost) { ? pluginTabRefFromSearch(host.routeState.location?.search ?? "") : null; const activePluginTabId = activePluginRef ? pluginTabKey(activePluginRef) : ""; - const settingsTakeover = isSettingsNavigationRoute(activeRoute); + // Onboarding renders without any navigation chrome, so the settings takeover + // must not reserve its fixed sidebar column (the grid would stay off-center). + const settingsTakeover = isSettingsNavigationRoute(activeRoute) && !host.onboardingMode; const runtimeConfig = context.runtimeConfig.state; const settingsSearchBlocks = findSettingsSearchBlocks({ query: host.settingsSearchQuery, diff --git a/ui/src/e2e/custodian-event-nudge.e2e.test.ts b/ui/src/e2e/custodian-event-nudge.e2e.test.ts index f64ed0fbf2d0..abf23ef19ecb 100644 --- a/ui/src/e2e/custodian-event-nudge.e2e.test.ts +++ b/ui/src/e2e/custodian-event-nudge.e2e.test.ts @@ -423,7 +423,8 @@ suite.define(() => { const response = await page.goto(`${suite.server.baseUrl}custodian?onboarding=1`); expect(response?.status()).toBe(200); - await page.getByRole("heading", { name: "OpenClaw", exact: true }).waitFor(); + // Onboarding chrome keeps only the header actions; no identity heading. + await page.locator(".custodian__header--minimal").waitFor(); await gateway.emitGatewayEvent("health", { channelLabels: { telegram: "Telegram" }, channels: { diff --git a/ui/src/e2e/model-setup.e2e.test.ts b/ui/src/e2e/model-setup.e2e.test.ts index c385876c6131..2caeb4392817 100644 --- a/ui/src/e2e/model-setup.e2e.test.ts +++ b/ui/src/e2e/model-setup.e2e.test.ts @@ -119,7 +119,9 @@ suite.define(() => { await page.getByRole("button", { name: "Continue setup" }).click(); await expect.poll(() => new URL(page.url()).pathname).toBe("/custodian"); expect(new URL(page.url()).searchParams.get("onboarding")).toBe("1"); - await page.getByRole("heading", { name: "OpenClaw", exact: true }).waitFor(); + // Onboarding chrome keeps only the header actions; no identity heading. + await page.locator(".custodian__header--minimal").waitFor(); + await page.getByRole("button", { name: "Exit setup" }).waitFor(); await expect .poll(() => page.locator(".shell").getAttribute("class")) .toContain("shell--onboarding"); diff --git a/ui/src/pages/custodian/custodian-page.test.ts b/ui/src/pages/custodian/custodian-page.test.ts index 10cab0bef496..aa278c161d99 100644 --- a/ui/src/pages/custodian/custodian-page.test.ts +++ b/ui/src/pages/custodian/custodian-page.test.ts @@ -57,7 +57,8 @@ describe("custodian page", () => { .querySelector("img.chat-avatar.assistant") ?.getAttribute("src"), ).toBe("/favicon.svg"); - expect(page.querySelector(".custodian__mark openclaw-mascot")).not.toBeNull(); + // Onboarding strips the header identity; the thread avatar is the only mascot. + expect(page.querySelector(".custodian__mark openclaw-mascot")).toBeNull(); const card = page.querySelector("openclaw-option-card")!; await card.updateComplete; expect(page.querySelector(".option-card__choice--recommended")?.textContent).toContain( diff --git a/ui/src/pages/custodian/custodian-page.ts b/ui/src/pages/custodian/custodian-page.ts index cb035aa6926c..b45e5c417a2e 100644 --- a/ui/src/pages/custodian/custodian-page.ts +++ b/ui/src/pages/custodian/custodian-page.ts @@ -224,19 +224,25 @@ export class CustodianPage extends OpenClawLightDomElement { ? "custodian--setup-required" : ""}" > -
-
- -
-

${t("custodian.title")}

-

${t(this.onboarding ? "custodian.subtitle" : "custodian.subtitleCaretaker")}

-
-
+
+ ${this.onboarding + ? nothing + : html`
+ +
+

${t("custodian.title")}

+

${t("custodian.subtitleCaretaker")}

+
+
`}
${this.historyAvailable ? html`