From 6f2394f17e5f60ff9a2541b113c3d32c42f83572 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Tue, 3 Mar 2026 19:59:55 -0800 Subject: [PATCH 1/2] Changes from fix/event-hook-endpoint --- apps/ui/src/lib/http-api-client.ts | 15 +++++++++++++++ apps/ui/src/routes/__root.tsx | 5 ++++- apps/ui/src/store/app-store.ts | 12 ++++++++++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/apps/ui/src/lib/http-api-client.ts b/apps/ui/src/lib/http-api-client.ts index e92ab311f..9b06d75ef 100644 --- a/apps/ui/src/lib/http-api-client.ts +++ b/apps/ui/src/lib/http-api-client.ts @@ -2763,6 +2763,21 @@ export class HttpApiClient implements ElectronAPI { headers?: Record; enabled?: boolean; }>; + eventHooks?: Array<{ + id: string; + trigger: string; + enabled: boolean; + action: Record; + name?: string; + }>; + ntfyEndpoints?: Array<{ + id: string; + name: string; + serverUrl: string; + topic: string; + authType: string; + enabled: boolean; + }>; }; error?: string; }> => this.get('/api/settings/global'), diff --git a/apps/ui/src/routes/__root.tsx b/apps/ui/src/routes/__root.tsx index 6f5d0758e..403c31e30 100644 --- a/apps/ui/src/routes/__root.tsx +++ b/apps/ui/src/routes/__root.tsx @@ -613,7 +613,10 @@ function RootLayoutContent() { // Reconcile ntfy endpoints from server (same rationale as eventHooks) const serverEndpoints = (finalSettings as GlobalSettings).ntfyEndpoints ?? []; const currentEndpoints = useAppStore.getState().ntfyEndpoints; - if (JSON.stringify(serverEndpoints) !== JSON.stringify(currentEndpoints)) { + if ( + JSON.stringify(serverEndpoints) !== JSON.stringify(currentEndpoints) && + serverEndpoints.length > 0 + ) { logger.info( `[FAST_HYDRATE] Reconciling ntfyEndpoints from server (server=${serverEndpoints.length}, store=${currentEndpoints.length})` ); diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts index 8e4918d19..e4b96eb27 100644 --- a/apps/ui/src/store/app-store.ts +++ b/apps/ui/src/store/app-store.ts @@ -1506,7 +1506,11 @@ export const useAppStore = create()((set, get) => ({ set({ eventHooks: hooks }); try { const httpApi = getHttpApiClient(); - await httpApi.settings.updateGlobal({ eventHooks: hooks }); + await httpApi.settings.updateGlobal({ + eventHooks: hooks, + // Signal the server that an empty array is intentional (not a wipe from stale state) + ...(hooks.length === 0 ? { __allowEmptyEventHooks: true } : {}), + }); } catch (error) { logger.error('Failed to sync event hooks:', error); } @@ -1517,7 +1521,11 @@ export const useAppStore = create()((set, get) => ({ set({ ntfyEndpoints: endpoints }); try { const httpApi = getHttpApiClient(); - await httpApi.settings.updateGlobal({ ntfyEndpoints: endpoints }); + await httpApi.settings.updateGlobal({ + ntfyEndpoints: endpoints, + // Signal the server that an empty array is intentional (not a wipe from stale state) + ...(endpoints.length === 0 ? { __allowEmptyNtfyEndpoints: true } : {}), + }); } catch (error) { logger.error('Failed to sync ntfy endpoints:', error); } From f5752b662fff3470f5ab79ff0e630f820fc88d41 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Tue, 3 Mar 2026 20:08:07 -0800 Subject: [PATCH 2/2] fix: Allow empty eventHooks/ntfyEndpoints to reconcile from server Remove the `length > 0` guards in fast-hydrate reconciliation that prevented intentional empty-array clears from syncing across clients. Server-side wipe protection (`__allowEmpty*` escape hatches) already ensures empty arrays in the server are intentional. Addresses PR #831 review feedback from CodeRabbit and Gemini. Co-Authored-By: Claude Opus 4.6 --- apps/ui/src/routes/__root.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/ui/src/routes/__root.tsx b/apps/ui/src/routes/__root.tsx index 403c31e30..379c7b427 100644 --- a/apps/ui/src/routes/__root.tsx +++ b/apps/ui/src/routes/__root.tsx @@ -600,10 +600,7 @@ function RootLayoutContent() { // so updating them won't cause a visible re-render flash. const serverHooks = (finalSettings as GlobalSettings).eventHooks ?? []; const currentHooks = useAppStore.getState().eventHooks; - if ( - JSON.stringify(serverHooks) !== JSON.stringify(currentHooks) && - serverHooks.length > 0 - ) { + if (JSON.stringify(serverHooks) !== JSON.stringify(currentHooks)) { logger.info( `[FAST_HYDRATE] Reconciling eventHooks from server (server=${serverHooks.length}, store=${currentHooks.length})` ); @@ -613,10 +610,7 @@ function RootLayoutContent() { // Reconcile ntfy endpoints from server (same rationale as eventHooks) const serverEndpoints = (finalSettings as GlobalSettings).ntfyEndpoints ?? []; const currentEndpoints = useAppStore.getState().ntfyEndpoints; - if ( - JSON.stringify(serverEndpoints) !== JSON.stringify(currentEndpoints) && - serverEndpoints.length > 0 - ) { + if (JSON.stringify(serverEndpoints) !== JSON.stringify(currentEndpoints)) { logger.info( `[FAST_HYDRATE] Reconciling ntfyEndpoints from server (server=${serverEndpoints.length}, store=${currentEndpoints.length})` );