chore(web): address Copilot review on #2153

- Publish `setupClientForServerRef` from a layout effect rather than during
  render. React runs every layout effect before any passive effect of the same
  commit, and the /oauth/callback effect is passive, so the seam is still
  published before its first read — without the render-phase ref mutation the
  compiler rule rejects. This was the one `react-hooks/refs` finding the PR
  introduced, so its suppression is gone rather than deferred to #2161 under a
  justification that did not describe it.
- Narrow the hook's `fetchLogRef` option to `FetchRequestSource`, the
  `getFetchRequests` slice it actually consumes, mirroring
  `InspectorClientEventSource` in useProgressToasts. That removes the test
  harness's `as unknown as` cast, and the narrowed contract is real: it
  immediately rejected the loosely-typed fixture, which is now a properly typed
  `FetchRequestEntry`.
- Make the test harness hold both refs across renders and publish them from a
  layout effect, as App.tsx does. The deferred-publication test previously
  passed because `Probe` built a fresh ref each render and the rerender also
  changed `servers`; it now asserts what the guard is actually for — a run that
  cannot exchange must not spend the single-use code — and re-triggers on the
  `servers` change that carries it in production.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hzwzS8UvBsr4yZm7o7sev
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-26 23:19:49 -04:00
commit 5f875e3393

View file

@ -1,6 +1,7 @@
import {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
@ -636,11 +637,9 @@ function App() {
);
/**
* Filled in below, once the connect path's `setupClientForServer` exists —
* Published below, once the connect path's `setupClientForServer` exists —
* it reads `onBeforeOAuthRedirect` and `sessionStorageAdapter` out of the
* hook we are about to call, so it cannot be declared before it. The
* `/oauth/callback` effect only ever reads this from an effect, well after
* the render that writes it.
* hook we are about to call, so it cannot be declared before it.
*/
const setupClientForServerRef = useRef<SetupClientForServer | null>(null);
@ -1228,11 +1227,16 @@ function App() {
);
// Publish it to the `/oauth/callback` effect, which needs to rebuild the
// client for the server that started the flow and cannot reach a callback
// declared this far down. Written during render — the same latest-ref shape
// `sandboxUrlRef` above uses — because the only reader is an effect, which
// React runs strictly after the render that writes this.
// eslint-disable-next-line react-hooks/refs -- pre-existing latest-ref pattern, unmasked when this component dropped below the React Compiler's bail-out (#2161)
setupClientForServerRef.current = setupClientForServer;
// declared this far down.
//
// A *layout* effect, not a render-phase write and not a passive one. React
// runs every layout effect before any passive effect of the same commit, and
// the callback effect inside `useOAuthRecovery` is passive — so this is
// always published before its first read, without the render-phase ref
// mutation the compiler rule (rightly) rejects.
useLayoutEffect(() => {
setupClientForServerRef.current = setupClientForServer;
}, [setupClientForServer]);
const onToggleConnection = useCallback(
async (id: string) => {