2 Commits

Author SHA1 Message Date
gsxdsm
f5752b662f 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 <noreply@anthropic.com>
2026-03-03 20:08:07 -08:00
gsxdsm
6f2394f17e Changes from fix/event-hook-endpoint 2026-03-03 19:59:55 -08:00
3 changed files with 26 additions and 6 deletions

View File

@@ -2763,6 +2763,21 @@ export class HttpApiClient implements ElectronAPI {
headers?: Record<string, string>;
enabled?: boolean;
}>;
eventHooks?: Array<{
id: string;
trigger: string;
enabled: boolean;
action: Record<string, unknown>;
name?: string;
}>;
ntfyEndpoints?: Array<{
id: string;
name: string;
serverUrl: string;
topic: string;
authType: string;
enabled: boolean;
}>;
};
error?: string;
}> => this.get('/api/settings/global'),

View File

@@ -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})`
);

View File

@@ -1506,7 +1506,11 @@ export const useAppStore = create<AppState & AppActions>()((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<AppState & AppActions>()((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);
}