From 73228114b9353210fe5e9d8505f77efd8d118027 Mon Sep 17 00:00:00 2001 From: archipelago Date: Tue, 28 Jul 2026 06:49:36 -0400 Subject: [PATCH] =?UTF-8?q?feat(ui):=20B5=20=E2=80=94=20/ws/db=20pushes=20?= =?UTF-8?q?revalidate=20cached=20resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bridge WebSocket patches into the resource layer: a /peer-health/ patch invalidates that peer's cloud.peer-browse entry and the federation node list; /package-data patches invalidate the tor-services list. invalidate() debounces 800ms and refetches only keys with mounted subscribers, so patch storms cost one revalidation per key; the 30s staleness reconciliation remains the backstop for unmapped data. Co-Authored-By: Claude Fable 5 --- neode-ui/src/stores/sync.ts | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/neode-ui/src/stores/sync.ts b/neode-ui/src/stores/sync.ts index fcf1cd1f..fef77f6a 100644 --- a/neode-ui/src/stores/sync.ts +++ b/neode-ui/src/stores/sync.ts @@ -2,9 +2,38 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' -import type { DataModel } from '../types/api' +import type { DataModel, PatchOperation } from '../types/api' import { wsClient, applyDataPatch } from '../api/websocket' import { rpcClient } from '../api/rpc-client' +import { useResourcesStore } from './resources' + +/** Unescape one JSON-pointer segment (RFC 6901: ~1 → '/', ~0 → '~'). */ +function pointerSegment(path: string, prefix: string): string { + const seg = path.slice(prefix.length).split('/')[0] ?? '' + return seg.replace(/~1/g, '/').replace(/~0/g, '~') +} + +/** B5: bridge /ws/db pushes into the cached-resource layer. Each patch op + * maps to the resource keys whose backing data it changes; invalidate() + * debounces (800ms) and only refetches keys with mounted subscribers, so a + * patch storm costs one revalidation per key. The 30s staleness + * reconciliation stays as the backstop for anything unmapped. */ +function invalidateResourcesForPatch(patch: PatchOperation[]): void { + const resources = useResourcesStore() + for (const op of patch) { + const path = op.path ?? '' + if (path.startsWith('/peer-health/')) { + // A peer flipping reachability changes both its browse result and the + // federation node list's online state. + const onion = pointerSegment(path, '/peer-health/') + if (onion) resources.invalidate(`cloud.peer-browse:${onion}`) + resources.invalidate('federation.nodes') + } else if (path.startsWith('/package-data/')) { + // App installs/uninstalls add or remove their tor services. + resources.invalidate('server.tor-services') + } + } +} export const useSyncStore = defineStore('sync', () => { // State @@ -108,6 +137,7 @@ export const useSyncStore = defineStore('sync', () => { try { if (import.meta.env.DEV) console.log('[Store] Applying patch at revision', update.rev || 'unknown') data.value = applyDataPatch(data.value, update.patch) + invalidateResourcesForPatch(update.patch) // Mark as connected once we receive any valid patch if (!isConnected.value) { isConnected.value = true