perf(plugins): seed plugin routes from sessionStorage cache for instant render

- Plugin manifests are now cached in sessionStorage on fetch.
- On refresh, plugin routes are registered synchronously from cache, preventing unwanted redirects to /sessions.
- Removes the !pluginsLoading guard from the catch-all route in App.tsx, as plugin routes are now always available on first render.
- Background fetch always updates the cache and routes, so new/removed plugins are reflected after reload.
- Resolves the race condition where plugin pages would redirect to /sessions on hard refresh.
This commit is contained in:
Allard
2026-05-04 23:03:22 +08:00
committed by kshitij
parent 9645ea8d52
commit 050b9533e0
+37 -3
View File
@@ -17,17 +17,51 @@ import {
setPluginLoadError,
} from "./registry";
const MANIFEST_CACHE_KEY = "hermes:plugin-manifests";
function getCachedManifests(): PluginManifest[] | null {
try {
const raw = sessionStorage.getItem(MANIFEST_CACHE_KEY);
return raw ? (JSON.parse(raw) as PluginManifest[]) : null;
} catch {
return null;
}
}
function cacheManifests(manifests: PluginManifest[]): void {
try {
sessionStorage.setItem(MANIFEST_CACHE_KEY, JSON.stringify(manifests));
} catch {
// sessionStorage unavailable (private browsing, storage full, etc.)
}
}
export function usePlugins() {
const [manifests, setManifests] = useState<PluginManifest[]>([]);
// Lazy initialisers run once at mount — safe to read sessionStorage here.
// This avoids the "cannot access ref during render" lint error that would
// occur if we stored the cached value in a useRef and read .current in the
// useState initial value expression.
const [manifests, setManifests] = useState<PluginManifest[]>(
() => getCachedManifests() ?? [],
);
const [plugins, setPlugins] = useState<RegisteredPlugin[]>([]);
const [loading, setLoading] = useState(true);
// Start loading=false when the cache has manifests so plugin routes are
// registered synchronously on the first render after a refresh.
// The catch-all in App.tsx is only a safety net for the very first visit
// (no cache yet). On subsequent visits this flag starts false immediately.
const [loading, setLoading] = useState<boolean>(
() => getCachedManifests() === null,
);
const loadedScripts = useRef<Set<string>>(new Set());
// Fetch manifests on mount.
// Always re-fetch in the background to keep the cache fresh.
// This handles: new plugins added, plugins removed, manifest changes.
// setManifests(list) will update routes if the server list differs from cache.
useEffect(() => {
api
.getPlugins()
.then((list) => {
cacheManifests(list);
setManifests(list);
if (list.length === 0) setLoading(false);
})