mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
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
This commit is contained in:
committed by
GitHub
parent
4b6a9785c9
commit
7d1958bf61
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -57,7 +57,8 @@ describe("custodian page", () => {
|
||||
.querySelector<HTMLImageElement>("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(
|
||||
|
||||
@@ -224,19 +224,25 @@ export class CustodianPage extends OpenClawLightDomElement {
|
||||
? "custodian--setup-required"
|
||||
: ""}"
|
||||
>
|
||||
<header class="custodian__header custodian__column">
|
||||
<div class="custodian__identity">
|
||||
<div class="custodian__mark" aria-hidden="true">
|
||||
<openclaw-mascot
|
||||
.mood=${this.store.sending ? "thinking" : "idle"}
|
||||
.size=${38}
|
||||
></openclaw-mascot>
|
||||
</div>
|
||||
<div>
|
||||
<h1>${t("custodian.title")}</h1>
|
||||
<p>${t(this.onboarding ? "custodian.subtitle" : "custodian.subtitleCaretaker")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<header
|
||||
class="custodian__header custodian__column ${this.onboarding
|
||||
? "custodian__header--minimal"
|
||||
: ""}"
|
||||
>
|
||||
${this.onboarding
|
||||
? nothing
|
||||
: html`<div class="custodian__identity">
|
||||
<div class="custodian__mark" aria-hidden="true">
|
||||
<openclaw-mascot
|
||||
.mood=${this.store.sending ? "thinking" : "idle"}
|
||||
.size=${38}
|
||||
></openclaw-mascot>
|
||||
</div>
|
||||
<div>
|
||||
<h1>${t("custodian.title")}</h1>
|
||||
<p>${t("custodian.subtitleCaretaker")}</p>
|
||||
</div>
|
||||
</div>`}
|
||||
<div class="custodian__header-actions">
|
||||
${this.historyAvailable
|
||||
? html`<button
|
||||
|
||||
@@ -96,8 +96,9 @@ describe("custodian route", () => {
|
||||
>("openclaw-custodian-page");
|
||||
await onboardingPage?.updateComplete;
|
||||
expect(onboardingPage?.querySelector(".custodian__header .btn")).not.toBeNull();
|
||||
expect(onboardingPage?.querySelector(".custodian__header p")?.textContent?.trim()).toBe(
|
||||
"Your system setup guide",
|
||||
);
|
||||
// Onboarding renders the minimal header: actions only, no identity block.
|
||||
expect(onboardingPage?.querySelector(".custodian__header--minimal")).not.toBeNull();
|
||||
expect(onboardingPage?.querySelector(".custodian__header p")).toBeNull();
|
||||
expect(onboardingPage?.querySelector(".custodian__identity")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -109,6 +109,14 @@ openclaw-custodian-page {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Onboarding: the thread already introduces the mascot, so the header keeps
|
||||
only its actions as a quiet top-right row. */
|
||||
.custodian__header--minimal {
|
||||
justify-content: flex-end;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.custodian__identity {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -252,14 +260,15 @@ openclaw-custodian-page {
|
||||
|
||||
.custodian__option-card {
|
||||
/* Left offset = avatar column (36px) + group gap (10px) so cards align with
|
||||
the message text column, not the avatar gutter. */
|
||||
margin: -12px 16px 14px 46px;
|
||||
the message text column, not the avatar gutter. No negative top margin:
|
||||
it overlays the preceding message's sender/timestamp row. */
|
||||
margin: 4px 16px 14px 46px;
|
||||
}
|
||||
|
||||
.custodian__wizard-step {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin: -12px 16px 14px 46px;
|
||||
margin: 4px 16px 14px 46px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
|
||||
@@ -48,14 +48,12 @@
|
||||
transition:
|
||||
border-color var(--duration-fast) var(--ease-out),
|
||||
background var(--duration-fast) var(--ease-out),
|
||||
box-shadow var(--duration-fast) var(--ease-out),
|
||||
transform var(--duration-fast) var(--ease-out);
|
||||
box-shadow var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.option-card__choice:hover:not(:disabled) {
|
||||
border-color: var(--border-strong);
|
||||
background: var(--bg-hover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.option-card__choice:focus-visible {
|
||||
@@ -69,6 +67,14 @@
|
||||
background: var(--accent-subtle);
|
||||
}
|
||||
|
||||
/* Hover must not swap the accent tint for the neutral one; the mismatched
|
||||
layers read as the card overlapping itself. */
|
||||
.option-card__choice--recommended:hover:not(:disabled),
|
||||
.option-card__choice--selected:hover:not(:disabled) {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent-subtle);
|
||||
}
|
||||
|
||||
.option-card__choice-copy {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
|
||||
Reference in New Issue
Block a user