fix(ui): show New Session failures in a centered alert (#107997)

* fix(ui): center new session errors

* chore: remove release-owned changelog entry
This commit is contained in:
Peter Steinberger
2026-07-14 21:37:45 -07:00
committed by GitHub
parent 3932a6038c
commit 4a6dc1fe1d
3 changed files with 73 additions and 6 deletions

View File

@@ -798,6 +798,22 @@ describeControlUiE2e("Control UI new-session page mocked Gateway E2E", () => {
await expect
.poll(() => page.locator(".new-session-page__error").textContent())
.toContain("cloud worker placement could not be verified");
const alert = page.locator(".new-session-page__alert");
await expect.poll(() => alert.getAttribute("role")).toBe("alert");
await expect.poll(() => alert.locator("svg").count()).toBe(1);
const [alertBox, composerBox] = await Promise.all([
alert.boundingBox(),
page.locator(".new-session-page__composer").boundingBox(),
]);
expect(alertBox).not.toBeNull();
expect(composerBox).not.toBeNull();
expect(
Math.abs(
(alertBox?.x ?? 0) +
(alertBox?.width ?? 0) / 2 -
((composerBox?.x ?? 0) + (composerBox?.width ?? 0) / 2),
),
).toBeLessThanOrEqual(1);
await expect.poll(() => startButton.isDisabled()).toBe(false);
await page.getByRole("button", { name: "Start session" }).click();
await expect

View File

@@ -6,6 +6,7 @@ import { applicationContext, type ApplicationContext } from "../../app/context.t
import { beginNativeWindowDragFromTopInset } from "../../app/native-window-drag.ts";
import { hasOperatorAdminAccess } from "../../app/operator-access.ts";
import { loadSettings } from "../../app/settings.ts";
import { icons } from "../../components/icons.ts";
import "../../components/tooltip.ts";
import "../../components/web-awesome-popover.ts";
import "../../components/web-awesome-select.ts";
@@ -42,6 +43,15 @@ import { renderAgentSelect, renderFolderSelect, renderWhereSelect } from "./targ
const CATALOG_RETRY_DELAYS_MS = [0, 1_000, 3_000] as const;
function renderDraftError(message: string) {
return html`
<div class="callout danger new-session-page__error new-session-page__alert" role="alert">
<span class="new-session-page__alert-icon" aria-hidden="true">${icons.alertTriangle}</span>
<span class="callout__content new-session-page__alert-message">${message}</span>
</div>
`;
}
class NewSessionPage extends OpenClawLightDomElement {
@property({ attribute: false }) data: NewSessionRouteData | undefined;
@@ -1215,12 +1225,10 @@ class NewSessionPage extends OpenClawLightDomElement {
return html`
<div class="new-session-page__draft" aria-busy=${String(this.submitting)}>
${this.renderTargetBar()}
${worktreeNameInvalid
? html`<div class="new-session-page__error">${t("newSession.worktreeNameInvalid")}</div>`
: nothing}
${this.error ? html`<div class="new-session-page__error">${this.error}</div>` : nothing}
${worktreeNameInvalid ? renderDraftError(t("newSession.worktreeNameInvalid")) : nothing}
${this.error ? renderDraftError(this.error) : nothing}
${this.submissionOutcomeUnknown
? html`<div class="new-session-page__error">${t("newSession.createOutcomeUnknown")}</div>`
? renderDraftError(t("newSession.createOutcomeUnknown"))
: nothing}
${renderNewSessionDraftComposer({
agentDefaultModel: this.selectedAgent()?.model?.primary,

View File

@@ -361,11 +361,54 @@ wa-popover.new-session-page__select--folder::part(body) {
border-top: 1px solid var(--border);
}
.new-session-page__error {
.new-session-page__browser > .new-session-page__error {
padding: 8px 10px 0;
font-size: 12px;
color: var(--danger);
}
.new-session-page__alert {
display: flex;
align-items: flex-start;
gap: 10px;
width: fit-content;
max-width: min(48rem, calc(100% - 36px));
margin: 4px auto 2px;
padding: 10px 12px;
box-sizing: border-box;
text-align: left;
box-shadow: 0 8px 24px color-mix(in srgb, var(--danger) 8%, transparent);
}
.new-session-page__alert-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 26px;
height: 26px;
border-radius: 50%;
background: color-mix(in srgb, var(--danger) 14%, transparent);
color: var(--danger);
}
.new-session-page__alert-icon svg {
width: 15px;
height: 15px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
.new-session-page__alert-message {
min-width: 0;
padding-top: 3px;
line-height: 1.45;
overflow-wrap: anywhere;
}
.new-session-page__browser-use {
padding: 6px 14px;
border: 1px solid transparent;