feat: upgrade toolchain and soften sync recovery after network blips
Bump Vite 8, matrix-js-sdk, and related deps; fix immer/vanilla-extract imports for new majors. Replace blocking sync error dialog with background recovery on wake/offline, and disable Vite auto-open in dev. Add feature handoff documentation index.
This commit is contained in:
@@ -143,13 +143,11 @@ const useBackgroundSync = (mx?: MatrixClient) => {
|
||||
type ClientRootProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
const MAX_SYNC_RETRIES = 3;
|
||||
|
||||
export function ClientRoot({ children }: ClientRootProps) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [syncError, setSyncError] = useState<Error | null>(null);
|
||||
const syncRetryCount = useRef(0);
|
||||
const mxRef = useRef<MatrixClient | undefined>(undefined);
|
||||
const recoverInFlight = useRef(false);
|
||||
const { baseUrl } = getCurrentSession() ?? {};
|
||||
|
||||
// Enable view transitions for smooth navigation
|
||||
@@ -202,31 +200,77 @@ export function ClientRoot({ children }: ClientRootProps) {
|
||||
return () => clearTimeout(timeout);
|
||||
}, [mx, loading]);
|
||||
|
||||
// Soft-recover from sync ERROR (e.g. sleep/wake with no network).
|
||||
// Never block the UI — SyncStatus / title bar already show "Connection Lost".
|
||||
useSyncState(
|
||||
mx,
|
||||
useCallback((state, prevState, data) => {
|
||||
useCallback((state) => {
|
||||
const client = mxRef.current;
|
||||
if (state === 'PREPARED') {
|
||||
if (state === SyncState.Prepared || state === SyncState.Syncing) {
|
||||
setLoading(false);
|
||||
setSyncError(null);
|
||||
syncRetryCount.current = 0;
|
||||
} else if (state === 'ERROR') {
|
||||
if (syncRetryCount.current < MAX_SYNC_RETRIES) {
|
||||
syncRetryCount.current += 1;
|
||||
const attempt = syncRetryCount.current;
|
||||
console.warn(`[ClientRoot] Sync error, auto-retrying (${attempt}/${MAX_SYNC_RETRIES})...`);
|
||||
setTimeout(() => {
|
||||
if (client) startClient(client).catch(() => {});
|
||||
}, 2000 * attempt);
|
||||
} else {
|
||||
const error = data instanceof Error ? data : new Error('Sync failed');
|
||||
console.error('[ClientRoot] Sync error after max retries:', error);
|
||||
setSyncError(error);
|
||||
}
|
||||
recoverInFlight.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (state !== SyncState.Error || !client || recoverInFlight.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
recoverInFlight.current = true;
|
||||
console.warn('[ClientRoot] Sync error after sleep/network blip, soft-recovering...');
|
||||
|
||||
const recover = () => {
|
||||
if (!mxRef.current) {
|
||||
recoverInFlight.current = false;
|
||||
return;
|
||||
}
|
||||
const c = mxRef.current;
|
||||
if (!c.clientRunning) {
|
||||
startClient(c)
|
||||
.catch((err) => console.warn('[ClientRoot] Soft recover startClient failed:', err))
|
||||
.finally(() => {
|
||||
recoverInFlight.current = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
c.retryImmediately();
|
||||
} catch (err) {
|
||||
console.warn('[ClientRoot] Soft recover retryImmediately failed:', err);
|
||||
}
|
||||
recoverInFlight.current = false;
|
||||
};
|
||||
|
||||
// Give Wi‑Fi a moment to come back after lid open / resume
|
||||
window.setTimeout(recover, 1500);
|
||||
}, [])
|
||||
);
|
||||
|
||||
// Also nudge sync when the laptop wakes / network returns
|
||||
useEffect(() => {
|
||||
if (!mx) return;
|
||||
|
||||
const nudge = () => {
|
||||
if (document.visibilityState !== 'visible') return;
|
||||
const syncState = mx.getSyncState();
|
||||
if (syncState === SyncState.Error || syncState === SyncState.Reconnecting || !mx.clientRunning) {
|
||||
console.log('[ClientRoot] Online/visible again, nudging sync', syncState);
|
||||
if (!mx.clientRunning) {
|
||||
startClient(mx).catch(() => {});
|
||||
} else {
|
||||
mx.retryImmediately();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('online', nudge);
|
||||
document.addEventListener('visibilitychange', nudge);
|
||||
return () => {
|
||||
window.removeEventListener('online', nudge);
|
||||
document.removeEventListener('visibilitychange', nudge);
|
||||
};
|
||||
}, [mx]);
|
||||
|
||||
return (
|
||||
<SpecVersions baseUrl={baseUrl!}>
|
||||
<TitleBar mx={mx} />
|
||||
@@ -251,23 +295,6 @@ export function ClientRoot({ children }: ClientRootProps) {
|
||||
</Box>
|
||||
</SplashScreen>
|
||||
)}
|
||||
{syncError && (
|
||||
<SplashScreen>
|
||||
<Box direction="Column" grow="Yes" alignItems="Center" justifyContent="Center" gap="400">
|
||||
<Dialog>
|
||||
<Box direction="Column" gap="400" style={{ padding: config.space.S400 }}>
|
||||
<Text>{`Sync failed: ${syncError.message}`}</Text>
|
||||
<Text size="T300">Check your internet connection and homeserver status.</Text>
|
||||
<Button variant="Critical" onClick={() => window.location.reload()}>
|
||||
<Text as="span" size="B400">
|
||||
Retry
|
||||
</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Dialog>
|
||||
</Box>
|
||||
</SplashScreen>
|
||||
)}
|
||||
{loading || !mx ? (
|
||||
<ClientRootLoading />
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user