feat(web): stale-while-revalidate caches to eliminate tab-switch flicker
Build / build (push) Failing after 4m51s
Build / build (push) Failing after 4m51s
Sidebar tabs, Settings, and drill-in detail pages re-fetched on every
visit (loading=true + onMount), flashing an empty skeleton frame on each
navigation. Add an SWR cache layer so revisiting a view renders cached
data instantly while refreshing in the background.
- resourceCache.ts: single-value + keyed (per-id) SWR cache factories
- caches.ts: per-resource cache instances; resetAllCaches() on logout
- eventsSnapshot.ts: warm-seed snapshot for the SSE/paginated events page
- List/sidebar pages read $cache.value via $derived, refresh() on mount;
mutations refresh the cache
- Settings forms seed once from settingsCache (edit-safe) and refetch
after save (PUT /api/settings returns {status}, not the Settings object)
- Detail [id] pages warm-seed per id; apps/[id] seeds {workload,containers},
resets non-seeded panels on warm nav, clears workload on 404, and
invalidates its cache entry on delete
Deferred (still cold-fetch): triggers/[id] (webhook secret + multi-fetch
body gate), apps/new (create wizard).
This commit is contained in:
@@ -6,7 +6,11 @@
|
||||
never within casual miss-click distance of general form fields.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getSettings, updateSettings, pruneImages, pruneBuildCache } from '$lib/api';
|
||||
import { onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { updateSettings, pruneImages, pruneBuildCache } from '$lib/api';
|
||||
import type { Settings } from '$lib/types';
|
||||
import { settingsCache } from '$lib/stores/caches';
|
||||
import FormField from '$lib/components/FormField.svelte';
|
||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||
import Skeleton from '$lib/components/Skeleton.svelte';
|
||||
@@ -26,19 +30,28 @@
|
||||
let statsIntervalSeconds = $state('15');
|
||||
let statsRetentionHours = $state('2');
|
||||
|
||||
// Seed form fields once from the shared settings cache (warm → no skeleton
|
||||
// on revisit); never re-applied during background refresh, so unsaved edits
|
||||
// are safe. See caches.ts / settings landing page for the pattern.
|
||||
function seed(s: Settings) {
|
||||
staleThresholdDays = String(s.stale_threshold_days ?? 7);
|
||||
imagePruneThresholdMb = String(s.image_prune_threshold_mb ?? 1024);
|
||||
statsIntervalSeconds = String(s.stats_interval_seconds ?? 15);
|
||||
statsRetentionHours = String(s.stats_retention_hours ?? 2);
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading = true;
|
||||
try {
|
||||
const s = await getSettings();
|
||||
staleThresholdDays = String(s.stale_threshold_days ?? 7);
|
||||
imagePruneThresholdMb = String(s.image_prune_threshold_mb ?? 1024);
|
||||
statsIntervalSeconds = String(s.stats_interval_seconds ?? 15);
|
||||
statsRetentionHours = String(s.stats_retention_hours ?? 2);
|
||||
} catch (err) {
|
||||
toasts.error(err instanceof Error ? err.message : $t('settingsGeneral.loadFailed'));
|
||||
} finally {
|
||||
const cached = get(settingsCache).value;
|
||||
if (cached) {
|
||||
seed(cached);
|
||||
loading = false;
|
||||
}
|
||||
await settingsCache.refresh();
|
||||
const fresh = get(settingsCache).value;
|
||||
if (fresh && loading) seed(fresh);
|
||||
loading = false;
|
||||
const { error } = get(settingsCache);
|
||||
if (error && !cached) toasts.error(error || $t('settingsGeneral.loadFailed'));
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
@@ -59,6 +72,8 @@
|
||||
stats_interval_seconds: interval,
|
||||
stats_retention_hours: retention
|
||||
});
|
||||
// PUT returns {status:"updated"}, not Settings — refetch the cache.
|
||||
await settingsCache.refresh();
|
||||
toasts.success($t('settingsGeneral.saved'));
|
||||
} catch (err) {
|
||||
toasts.error(err instanceof Error ? err.message : $t('settingsGeneral.saveFailed'));
|
||||
@@ -91,7 +106,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => { load(); });
|
||||
onMount(load);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
Reference in New Issue
Block a user