From 495616d13e9c29ae1e30138e7dd577a1fb354f88 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 25 Jan 2026 05:52:32 +0000 Subject: [PATCH] fix(ui): refine config save guardrails (#1707) * fix: refine config save guardrails * docs: add changelog for config save guardrails (#1707) (thanks @Glucksberg) --- CHANGELOG.md | 1 + ui/src/ui/app-gateway.ts | 1 + ui/src/ui/views/config.browser.test.ts | 24 +++++++++++++++++++++++- ui/src/ui/views/config.ts | 6 +++--- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53bda7630..59ce99755 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Docs: https://docs.clawd.bot - BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing. - Web UI: hide internal `message_id` hints in chat bubbles. - Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent. +- Web UI: clear stale disconnect banners on reconnect; allow form saves with unsupported schema paths but block missing schema. (#1707) Thanks @Glucksberg. - Heartbeat: normalize target identifiers for consistent routing. - TUI: reload history after gateway reconnect to restore session state. (#1663) - Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639) diff --git a/ui/src/ui/app-gateway.ts b/ui/src/ui/app-gateway.ts index 471fcbeba..d9a267a98 100644 --- a/ui/src/ui/app-gateway.ts +++ b/ui/src/ui/app-gateway.ts @@ -124,6 +124,7 @@ export function connectGateway(host: GatewayHost) { mode: "webchat", onHello: (hello) => { host.connected = true; + host.lastError = null; host.hello = hello; applySnapshot(host, hello); void loadAssistantIdentity(host as unknown as ClawdbotApp); diff --git a/ui/src/ui/views/config.browser.test.ts b/ui/src/ui/views/config.browser.test.ts index bcd240fbb..f58f4f3dd 100644 --- a/ui/src/ui/views/config.browser.test.ts +++ b/ui/src/ui/views/config.browser.test.ts @@ -38,7 +38,7 @@ describe("config view", () => { onSubsectionChange: vi.fn(), }); - it("disables save when form is unsafe", () => { + it("allows save when form is unsafe", () => { const container = document.createElement("div"); render( renderConfig({ @@ -59,6 +59,28 @@ describe("config view", () => { container, ); + const saveButton = Array.from( + container.querySelectorAll("button"), + ).find((btn) => btn.textContent?.trim() === "Save") as + | HTMLButtonElement + | undefined; + expect(saveButton).not.toBeUndefined(); + expect(saveButton?.disabled).toBe(false); + }); + + it("disables save when schema is missing", () => { + const container = document.createElement("div"); + render( + renderConfig({ + ...baseProps(), + schema: null, + formMode: "form", + formValue: { gateway: { mode: "local" } }, + originalValue: {}, + }), + container, + ); + const saveButton = Array.from( container.querySelectorAll("button"), ).find((btn) => btn.textContent?.trim() === "Save") as diff --git a/ui/src/ui/views/config.ts b/ui/src/ui/views/config.ts index e8e090bec..c45849d56 100644 --- a/ui/src/ui/views/config.ts +++ b/ui/src/ui/views/config.ts @@ -233,10 +233,10 @@ export function renderConfig(props: ConfigProps) { const hasRawChanges = props.formMode === "raw" && props.raw !== props.originalRaw; const hasChanges = props.formMode === "form" ? diff.length > 0 : hasRawChanges; - // Save/apply buttons require actual changes to be enabled - // Note: formUnsafe warns about unsupported schema paths but shouldn't block saving + // Save/apply buttons require actual changes to be enabled. + // Note: formUnsafe warns about unsupported schema paths but shouldn't block saving. const canSaveForm = - Boolean(props.formValue) && !props.loading; + Boolean(props.formValue) && !props.loading && Boolean(analysis.schema); const canSave = props.connected && !props.saving &&