diff --git a/packages/react/src/api/atoms.tsx b/packages/react/src/api/atoms.tsx index c0b79bd57..d84a3c2a1 100644 --- a/packages/react/src/api/atoms.tsx +++ b/packages/react/src/api/atoms.tsx @@ -169,10 +169,10 @@ export const refreshConnection = ExecutorApiClient.mutation("connections", "refr /** Probe a SAVED connection's health on demand (the "Check now" button). The * server persists the verdict on the connection row (`last_health`), so a check * that changes the verdict must invalidate the connections cache or a later - * read within the atom TTL serves the pre-check state. Callers pass - * `reactivityKeys: connectionCheckKeys` (see `use-connection-health.ts` for the - * manual-vs-automatic split that keeps the automatic path from churning the - * cache on every load). */ + * read within the atom TTL serves the pre-check state. Callers either pass + * `reactivityKeys: connectionCheckKeys` when a broad refresh is correct, or + * refresh the exact connection read surfaces after folding the returned verdict + * into local row state (see `use-connection-health.ts`). */ export const checkConnectionHealth = ExecutorApiClient.mutation("connections", "checkHealth"); /** Validate an IN-FLIGHT credential without saving it (the key-first connect diff --git a/packages/react/src/api/reactivity-keys.tsx b/packages/react/src/api/reactivity-keys.tsx index 9f3a001ab..4d40ce1da 100644 --- a/packages/react/src/api/reactivity-keys.tsx +++ b/packages/react/src/api/reactivity-keys.tsx @@ -48,8 +48,9 @@ export const connectionWriteKeys = [ReactivityKey.connections, ReactivityKey.too /** A connection health check persists only the connection's `last_health` * verdict, never its tools, so it invalidates `connections` alone (no `tools` - * churn). Passed at the manual "Check now" call site; the automatic mount-time - * probe invalidates conditionally instead (only when the verdict changed). */ + * churn). Use this for flows where every connection read should reconcile from + * persisted health immediately; row-level checks can refresh narrower read + * surfaces after applying their own local probe result. */ export const connectionCheckKeys = [ReactivityKey.connections] as const; /** Mutations that register / replace an OAuth client (app). */ diff --git a/packages/react/src/lib/use-connection-health.ts b/packages/react/src/lib/use-connection-health.ts index a6c50c847..9079396c4 100644 --- a/packages/react/src/lib/use-connection-health.ts +++ b/packages/react/src/lib/use-connection-health.ts @@ -11,8 +11,7 @@ import { RegistryContext, useAtomSet } from "@effect/atom-react"; import * as Exit from "effect/Exit"; import type { Connection, HealthCheckResult, HealthStatus, Owner } from "@executor-js/sdk/shared"; -import { checkConnectionHealth, connectionsOptimisticAtom } from "../api/atoms"; -import { connectionCheckKeys } from "../api/reactivity-keys"; +import { checkConnectionHealth, connectionsAllAtom, connectionsOptimisticAtom } from "../api/atoms"; /** Freshness window for automatic revalidation: a HEALTHY verdict younger * than this renders as-is; anything else (stale, missing, or non-healthy) @@ -47,13 +46,17 @@ const revalidateQuery = ( * persists every verdict on `last_health`, so after a check we must re-read the * connection rows or a later render within the atom TTL serves the pre-check * state. Returns a stable callback usable from a probe's `.then` for any owner - * (the loop surface probes across both owners), refreshing the optimistic atom - * every connections view derives from. + * (the loop surface probes across both owners), refreshing the owner-scoped + * optimistic atom plus the all-connections view that provider accounts derive + * from. */ function useInvalidateConnections(): (owner: Owner) => void { const registry = useContext(RegistryContext); return useCallback( - (owner: Owner) => registry.refresh(connectionsOptimisticAtom(owner)), + (owner: Owner) => { + registry.refresh(connectionsOptimisticAtom(owner)); + registry.refresh(connectionsAllAtom); + }, [registry], ); } @@ -107,17 +110,19 @@ export function useConnectionHealth(connection: Connection): { }, [connection, doCheck, invalidateConnections]); const runCheck = useCallback(async () => { - // Manual "Check now": invalidate the connections cache unconditionally so - // every surface picks up the freshly persisted verdict. Re-running this - // effect after the refetch is harmless: the ref guard blocks a re-probe. + // Manual "Check now": refresh connection reads after folding the returned + // verdict into this row. Re-running this effect after the refetch is + // harmless: the ref guard blocks a re-probe. const exit = await doCheck({ params: connectionParams(connection), query: {}, - reactivityKeys: connectionCheckKeys, }); - if (Exit.isSuccess(exit)) setLiveProbe(exit.value); + if (Exit.isSuccess(exit)) { + setLiveProbe(exit.value); + invalidateConnections(connection.owner); + } return exit; - }, [connection, doCheck]); + }, [connection, doCheck, invalidateConnections]); return { probe, status, runCheck }; }