feat: refetch filters and vacation on push and fan state changes out to screens

This commit is contained in:
Linus Rath
2026-08-29 18:03:24 +02:00
parent 6f747db858
commit a071d0581c
2 changed files with 50 additions and 0 deletions
+12
View File
@@ -10,6 +10,9 @@ import { Mail, Calendar, BookUser, HardDrive, Settings } from 'lucide-react-nati
import { startLiveUpdates, type LiveUpdatesHandle } from './src/api/push-stream';
import { jmapClient } from './src/api/jmap-client';
import type { StateChange } from './src/api/types';
import { dispatchStateChange } from './src/lib/state-change-bus';
import { useFilterStore } from './src/stores/filter-store';
import { useVacationStore } from './src/stores/vacation-store';
import {
addMessageListener,
addNotificationTapListener,
@@ -406,10 +409,19 @@ export default function App() {
let appActive = AppState.currentState !== 'background' && AppState.currentState !== 'inactive';
const onStateChange = async (change: StateChange) => {
// Filters / vacation edited elsewhere (webmail, another device): refetch
// so the settings panes don't save stale state over the newer script.
const primary = (() => { try { return jmapClient.accountId; } catch { return null; } })();
const own = primary ? change.changed?.[primary] : undefined;
const extra: Promise<unknown>[] = [];
if (own?.SieveScript) extra.push(useFilterStore.getState().fetchFilters().catch(() => undefined));
if (own?.VacationResponse) extra.push(useVacationStore.getState().fetch().catch(() => undefined));
dispatchStateChange(change);
await Promise.all([
useEmailStore.getState().handleStateChange(change),
useContactsStore.getState().handleStateChange(change),
useCalendarStore.getState().handleStateChange(change),
...extra,
]);
};
+38
View File
@@ -0,0 +1,38 @@
// Fan-out of JMAP StateChange events to screens that have no store of their
// own (Scheduled list, Files). The mail/contacts/calendar stores subscribe
// directly in App.tsx; anything else listens here for the type it cares about.
import type { StateChange } from '../api/types';
type Listener = (accountId: string, state: string, change: StateChange) => void;
const listeners = new Map<string, Set<Listener>>();
/** Subscribe to changes of one JMAP type (e.g. `EmailSubmission`, `FileNode`). */
export function onStateChangeType(type: string, listener: Listener): () => void {
let set = listeners.get(type);
if (!set) {
set = new Set();
listeners.set(type, set);
}
set.add(listener);
return () => {
set?.delete(listener);
};
}
export function dispatchStateChange(change: StateChange): void {
for (const [accountId, types] of Object.entries(change.changed ?? {})) {
for (const [type, state] of Object.entries(types ?? {})) {
const set = listeners.get(type);
if (!set) continue;
for (const l of set) {
try {
l(accountId, state, change);
} catch {
// a broken listener must not break the others
}
}
}
}
}