From c02e527dff39d730d1347759dfbaedfeb247b39c Mon Sep 17 00:00:00 2001 From: Eduardo Cruz Date: Fri, 13 Mar 2026 01:17:11 -0300 Subject: [PATCH] fix(ui): respect section filters for virtual config sections and reconcile push state with gateway Virtual sections (__appearance__, __notifications__) now respect includeSections/excludeSections filters so they only appear in their intended tabs. Push initialization re-registers the local subscription with the gateway to handle state-dir loss scenarios. --- ui/src/ui/app.ts | 20 +++++++++++++++++++- ui/src/ui/views/config.ts | 19 +++++++++---------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/ui/src/ui/app.ts b/ui/src/ui/app.ts index 6d104fd9d10..b711f0733ca 100644 --- a/ui/src/ui/app.ts +++ b/ui/src/ui/app.ts @@ -963,7 +963,25 @@ export class OpenClawApp extends LitElement { try { const { getExistingSubscription } = await import("./push-subscription.ts"); const existing = await getExistingSubscription(); - this.webPushSubscribed = existing !== null; + if (existing && this.client) { + // Re-register with the gateway to reconcile local/server state. + // Handles the case where the gateway lost the subscription (e.g. + // state-dir reset) but the browser still has one locally. + const subJson = existing.toJSON(); + if (subJson.endpoint && subJson.keys?.p256dh && subJson.keys?.auth) { + try { + await this.client.request("push.web.subscribe", { + endpoint: subJson.endpoint, + keys: { p256dh: subJson.keys.p256dh, auth: subJson.keys.auth }, + }); + } catch { + // Best-effort — don't block init if gateway is unreachable. + } + } + this.webPushSubscribed = true; + } else { + this.webPushSubscribed = existing !== null; + } } catch { // ignore — just means we can't check } diff --git a/ui/src/ui/views/config.ts b/ui/src/ui/views/config.ts index 9d73fb07904..b4e7e562d46 100644 --- a/ui/src/ui/views/config.ts +++ b/ui/src/ui/views/config.ts @@ -969,16 +969,15 @@ export function renderConfig(props: ConfigProps) { const schemaProps = analysis.schema?.properties ?? {}; const VIRTUAL_SECTIONS = new Set(["__appearance__", "__notifications__"]); - const visibleCategories = SECTION_CATEGORIES.map((cat) => - Object.assign({}, cat, { - sections: cat.sections.filter( - (s) => - ((includeVirtualSections && VIRTUAL_SECTIONS.has(s.key)) || s.key in schemaProps) && - (!include || include.has(s.key)) && - (!exclude || !exclude.has(s.key)), - ), - }), - ).filter((cat) => cat.sections.length > 0); + const visibleCategories = SECTION_CATEGORIES.map((cat) => ({ + ...cat, + sections: cat.sections.filter( + (s) => + ((includeVirtualSections && VIRTUAL_SECTIONS.has(s.key)) || s.key in schemaProps) && + (!include || include.has(s.key)) && + (!exclude || !exclude.has(s.key)), + ), + })).filter((cat) => cat.sections.length > 0); // Catch any schema keys not in our categories const extraSections = Object.keys(schemaProps)