inspector/clients
Repository files (latest commit first)
Filename Latest commit message Latest commit date
cliffhall 104b3c0775 chore(web): collapse App.tsx's six session refs into useSessionRef (#2129)
Step 1 of phase 2 of the App.tsx decomposition, landed first and alone
because it is the seam the remaining clusters are extracted onto.

App.tsx kept six "latest value" refs so long-lived callbacks could read a
current value without listing it as a dependency and being re-created on
every change: `activeServerNameRef`, `activeServerIdRef`, `serversRef`,
`inspectorClientRef`, `pendingStepUpRef` and `pendingReauthRef`, plus the
three passive effects that synced them. `useSessionRef` replaces all nine
with one hook returning one stable ref onto a `SessionSnapshot`.

This is not a store: nothing renders off it, nothing subscribes to it, and
mutating it never schedules a render. It is not the prop-to-state sync
AGENTS.md forbids either — that rule is about `setState` in an effect, and
a ref write produces no render at all. It is exactly the pattern the file
already used, written once instead of six times.

What it buys the later steps is a *stable* dependency: a hook taking
`sessionRef` does not drag the other clusters into its argument list, so
`useOAuthRecovery`, `useConnectionLifecycle` and `useServerCommands` can
be extracted one PR at a time.

Behavior is unchanged:

- The snapshot is seeded from the first render's values, matching what
  each `useRef(value)` seeded.
- `activeServerName` keeps its one non-uniform rule — it holds the last
  server that *was* active and is never cleared, because a transport
  crash clears `activeServerId` before the failure toast is raised. It is
  derived from `servers`/`activeServerId` rather than taking the
  `activeServer` memo, which is the same value by definition and is what
  lets the hook be called ahead of that memo.
- The two pending-* syncs already ran ahead of the only effect that reads
  them, so merging them changes no effect's view. The other four synced
  below every effect that reads them, so hoisting that sync changes none
  either.
- The sync effect is deliberately un-gated rather than carrying a
  dependency list: re-assigning unchanged values costs nothing and
  removes a place for a newly-added field to be forgotten.

`sessionRef` is declared ahead of its first reader because a hook return
is not provably stable to `react-hooks/exhaustive-deps` — consumers must
list it, and a later declaration would put every one of them in the
temporal dead zone. Listing a ref that never changes identity re-runs
nothing.

The inline `pendingStepUp` state shape is named `PendingStepUp` and moved
beside `StepUpSource` in `utils/stepUp.ts`, so the snapshot can name it
without importing from the component that owns the state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZdpUwhHf943FoprcLVzEm
Signed-off-by: cliffhall <cliff@futurescale.com>
2026-08-26 20:43:29 -04:00
..
cli fix(oauth): walk the candidates in the CLI refresh path too 2026-08-26 00:34:27 -04:00
launcher fix: walk contentSchema, await the error envelope, correct the README claim 2026-08-25 03:26:39 -04:00
tui fix: address Copilot review round 41 — const does not demand a property, required-only branches 2026-08-26 16:08:24 -04:00
web chore(web): collapse App.tsx's six session refs into useSessionRef (#2129) 2026-08-26 20:43:29 -04:00