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>
This commit is contained in:
parent
5c26da7433
commit
104b3c0775
1 changed files with 374 additions and 93 deletions
|
|
@ -29,3 +29,17 @@ export function isStepUpConfirmation(
|
|||
|
||||
/** Which in-flight action opened the step-up modal (for scoped cancel UX). */
|
||||
export type StepUpSource = "tool" | "prompt" | "resource" | "ambient" | "app";
|
||||
|
||||
/**
|
||||
* The in-flight step-up prompt App.tsx holds while the user completes (or
|
||||
* cancels) an interactive step-up authorization. Named here beside
|
||||
* `StepUpSource` rather than inline in App.tsx so `useSessionRef`'s snapshot
|
||||
* can name it without importing from the component that owns the state.
|
||||
*/
|
||||
export interface PendingStepUp {
|
||||
challenge: AuthChallenge;
|
||||
authorizationUrl: URL;
|
||||
serverId: string;
|
||||
source: StepUpSource;
|
||||
enterpriseManaged?: boolean;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue