feat(ui): Federation adopts the cached-resource store (B4)
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m7s

federation nodes + dwn.status become useCachedResource entries: revisits
paint the node list instantly, `loading` fires on true first-load only
(the old showLoader semantics), the 5s poll refreshes silently like the
old surfaceErrors:false path, and explicit reloads after mutations still
surface failures in the error banner. Replaces the hand-rolled
loadNodesWithOptions SWR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-28 05:36:26 -04:00
parent a969f892ea
commit 1a306c7450

View File

@ -229,6 +229,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { rpcClient } from '@/api/rpc-client'
import { useCachedResource } from '@/composables/useCachedResource'
import { useTransportStore } from '@/stores/transport'
import { useAppStore } from '@/stores/app'
import { useSyncStore } from '@/stores/sync'
@ -250,8 +251,15 @@ const transportStore = useTransportStore()
const appStore = useAppStore()
const syncStore = useSyncStore()
const nodes = ref<FederatedNode[]>([])
const loading = ref(true)
// Cached: revisits paint the node list instantly; the 5s poll and mutation
// refreshes revalidate behind it. `loading` is initial-load only (background
// refreshes keep content on screen the old showLoader:false semantics).
const nodesRes = useCachedResource<FederatedNode[]>({
key: 'federation.nodes',
fetcher: async () => (await rpcClient.federationListNodes()).nodes,
})
const nodes = computed(() => nodesRes.data.value ?? [])
const loading = computed(() => nodesRes.loadState.value === 'loading')
const error = ref('')
const selectedNode = ref<FederatedNode | null>(null)
const inviteType = ref<'trusted' | 'observer'>('trusted')
@ -320,7 +328,12 @@ const mapLinks = computed(() => {
}))
})
const dwnStatus = ref<DwnStatus | null>(null)
const dwnStatusRes = useCachedResource<DwnStatus>({
key: 'federation.dwn-status',
fetcher: (signal) => rpcClient.call<DwnStatus>({ method: 'dwn.status', signal, dedup: true, maxRetries: 1 }),
immediate: false,
})
const dwnStatus = computed(() => dwnStatusRes.data.value)
const dwnSyncing = ref(false)
const dwnSyncDotClass = computed(() => {
@ -500,25 +513,13 @@ function isOnlineCheck(node: FederatedNode): boolean {
return lastSeen > tenMinutesAgo
}
/** Explicit reload (mutations, retry): surfaces a load failure in the error
* banner. The background poll calls nodesRes.refresh() directly and stays
* silent, like the old surfaceErrors:false path. */
async function loadNodes() {
return loadNodesWithOptions()
}
async function loadNodesWithOptions(options: { showLoader?: boolean; surfaceErrors?: boolean } = {}) {
const showLoader = options.showLoader ?? nodes.value.length === 0
const surfaceErrors = options.surfaceErrors ?? true
try {
if (showLoader) loading.value = true
const result = await rpcClient.federationListNodes()
nodes.value = result.nodes
error.value = ''
} catch (e) {
if (surfaceErrors) {
error.value = e instanceof Error ? e.message : 'Failed to load nodes'
}
} finally {
if (showLoader) loading.value = false
}
await nodesRes.refresh()
if (nodesRes.error.value) error.value = nodesRes.error.value
else error.value = ''
}
function handleGenerateInvite(type: 'trusted' | 'observer') {
@ -610,13 +611,8 @@ async function deployApp(did: string, appId: string) {
}
}
async function loadDwnStatus() {
try {
const result = await rpcClient.call<DwnStatus>({ method: 'dwn.status' })
dwnStatus.value = result
} catch {
dwnStatus.value = null
}
function loadDwnStatus() {
return dwnStatusRes.refresh()
}
async function triggerDwnSync() {
@ -681,7 +677,6 @@ async function rotateDid(password: string) {
let autoRefreshTimer: ReturnType<typeof setInterval> | null = null
onMounted(async () => {
loadNodesWithOptions({ showLoader: true })
loadDwnStatus()
loadDiscoveryState()
loadPendingRequests()
@ -694,7 +689,7 @@ onMounted(async () => {
// Self DID not available
}
autoRefreshTimer = setInterval(() => {
loadNodesWithOptions({ showLoader: false, surfaceErrors: false })
void nodesRes.refresh()
loadPendingRequests()
}, 5000)
})