When async flows drive UI state (events, config reads, resume/load), never assume timing/order will be favorable. Make initialization idempotent and conditionally applied so missed/early events or late async completions can’t leave the UI stuck or clobber user intent.
Apply these rules: 1) Provide a “catch-up” path for state that may already be loaded.
2) Guard against out-of-order async updates.
Example pattern (session event + immediate catch-up):
const loadForSession = (id: string) => {
// load extensions for id (idempotent)
};
const onLoaded = (event: Event) => {
const targetSessionId = (event as CustomEvent<{ sessionId?: string }>).detail?.sessionId;
if (targetSessionId !== sessionId) return;
loadForSession(targetSessionId!);
};
window.addEventListener(AppEvents.SESSION_EXTENSIONS_LOADED, onLoaded);
// catch-up: don’t rely solely on whether the event already fired
loadForSession(sessionId);
Example pattern (async config read without clobbering user choice):
let userTouched = false;
// set userTouched = true in your dropdown onChange
const effortFromConfig = await readConfig('SOME_EFFORT_KEY');
if (effortFromConfig && !userTouched) {
setEffort(effortFromConfig);
}
Keep “ordering hazards” (e.g., user actions during resume vs backend load) scoped or explicitly disabled unless you can key off a reliable in-progress signal; otherwise treat them as separate follow-ups to avoid mixing multiple concurrency risks in one change.
Enter the URL of a public GitHub repository