chore(web): extract App.tsx's server commands into useServerCommands - #2173
Conversation
Phase-2 step 4 of the App.tsx decomposition (#2155, under #2129/#2126). Every command the screens issue against the live server moves into `useServerCommands`: calling a tool, getting a prompt, reading and subscribing to a resource, completing an argument, cancelling a task or a call, setting either log level, refreshing or paging a list, and flipping the pagination mode. All of them route through the command-scoped auth recovery `useOAuthRecovery` publishes, which is why this cluster cannot precede the OAuth one. The three in-flight result panels move with them, behind a small `useResultPanels` in the same file — split out only because of call order, the same reason `useHandshakeTelemetry` sits beside `useConnectionLifecycle`: `useOAuthRecovery` drops those panels on a session reset and routes a step-up failure into whichever one issued the command, and it runs before the commands hook. The move is inert — no behavior change, #2095's per-server pagination override included. 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>
There was a problem hiding this comment.
Pull request overview
Extracts live-server command handling from App.tsx into a dedicated, tested hook as part of the ongoing application decomposition.
Changes:
- Adds
useServerCommandsanduseResultPanels. - Moves command, pagination, logging, and result-panel logic out of
App.tsx. - Adds comprehensive hook tests.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
clients/web/src/App.tsx |
Wires the extracted hooks into the composition root. |
clients/web/src/hooks/useServerCommands.tsx |
Implements server commands and result-panel state. |
clients/web/src/hooks/useServerCommands.test.tsx |
Tests command and pagination behavior. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Give the test's paginated-list fixture a precise return type instead of an unjustified double cast. The cast was hiding that the fixture omitted `error`, so an interface change would not have been type-checked there. 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>
Copilot review round 1 — responsesMirrored at PR level because inline replies get hidden once the fix is pushed and the thread goes outdated. 1. Unjustified double cast in the paginated-list test fixture — fixed (5c7c518)Right, and for a better reason than the style rule: the 2 & 3.
|
Copilot round 2. The stand-in `runWithCommandAuthRecovery` was a bare pass-through while its comment claimed it reproduced the real one, so `onCompleteArgument` and `onCancelTask` — whose only auth handling *is* that wrapper — would have passed every test here while bypassing the shared recovery entirely. That routing is the integration this extraction exists to preserve. The stand-in now matches `useOAuthRecovery`'s: it catches an `AuthRecoveryRequiredError`, hands it to `handleCommandScopedAuthRecovery` with the call site's `source` and a `retryOperation`, retries once when the recovery reports satisfied, and rethrows anything else. `runCommandInBackground` is built on it, as the real one is. Three tests pin the source and the retry. 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>
|
Correct, and a good catch — the comment above that stand-in claimed something it did not do. Fixed in f7fe4bc. The gap was exactly as described: The stand-in now mirrors Three tests pin it, asserting the
The old "stays silent when the cancel raised a recovery" test was keeping its own name while testing the no-active-server path by accident; it is renamed to say what it actually covers. 75 tests, still 100% statements/lines/functions and 96.58% branches on the hook. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
clients/web/src/hooks/useServerCommands.tsx:895
- Remove the duplicated word in this comment.
// Wrap Load-next-page in ambient auth recovery too, so a paginated
// paginated fetch that hits a 401 recovers like the all-pages path (#1721).
Copilot round 3, from its suppressed set: "a paginated paginated fetch". The line came across verbatim with `onLoadMoreTools`, so the typo predates this PR — fixed here since the comment is the thing being moved. 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>
Copilot review round 3 — responseNo new comments. The one suppressed comment was real and is fixed in 71cd113:
The line came across verbatim with
Round summary
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
clients/web/src/App.tsx:781
- “Every one” is false while the extracted subscribe/unsubscribe handlers bypass command-scoped recovery. Reword this ordering comment to describe the commands that participate in recovery rather than asserting coverage the hook does not yet provide.
// --- Action handlers that route directly to the InspectorClient. Every one
// of them routes through the command-scoped auth recovery above, which is
// why they sit after it (#2155). ---
clients/web/src/hooks/useServerCommands.tsx:249
- This rationale is currently inaccurate:
onSubscribeResourceandonUnsubscribeResourcebelow still call the client directly and do not use the shared recovery surface (the follow-up is tracked separately). Please document those exceptions so future callers do not assume subscription 401s already recover through this hook.
* It is one hook because every one of these routes through the same edge —
* the command-scoped auth recovery `useOAuthRecovery` publishes (#2153). A
* command that hits a lapsed authorization has to reach the *same* recovery,
* or two of them would prompt differently for one server. That dependency is
* also why this cluster cannot precede the OAuth one.
Closes #2155
Phase-2 step 4 of the
App.tsxdecomposition (#2129 for the phase, #2126 for the whole effort). Step 3 (#2154 / PR #2169) has landed, so this targetsv2/mainrather than that branch.What moved
clients/web/src/hooks/useServerCommands.tsx— every command the screens issue against the live server:onCallTool,onClearToolResult,onToolsUiChangeonGetPrompt,onReadResource,onReadResourceContents,onSubscribeResource,onUnsubscribeResource,onCompleteArgumentonCancelTask,onCancelToolCall,onClearCompletedTasks,onSetLogLevel,onSetModernLogLevelonRefreshTasks, andonTogglePaginatedListsIt is one hook because every one of these routes through the command-scoped auth recovery
useOAuthRecoverypublishes (#2153). A command that hits a lapsed authorization has to reach the same recovery, or two of them would prompt differently for one server — which is also why this cluster cannot precede the OAuth one.useResultPanelstoolCallState/getPromptState/readResourceStatemove too, but behind a smalluseResultPanelsexported from the same file. That split is call order, not taste — the same reasonuseHandshakeTelemetrysits besideuseConnectionLifecycle:useOAuthRecoverydrops those panels on a session reset (clearResultPanels) and routes a step-up failure into whichever one issued the command (setSourceScopedError), and it runs before the commands hook that consumes its wrappers. So the state has to exist earlier than the hook that otherwise owns it.App.tsxcallsuseResultPanels()up top and hands the whole surface down.Inert by construction
No behavior change. In particular #2095's per-server pagination-override behavior is moved verbatim — the override record, the
lastPersistedSettingsbaseline, the settle/rollback pair, and thesessionRefguard that keeps a rollback from pushing one server's value into another server's live client. Anything further belongs on top of this, per the constraint carried from #2129: a behavior change buried in a 135-line relocation is unreviewable.Numbers
App.tsx: 2395 → 1811 lines (−681 / +82)useServerCommands.tsx: 872 lines,useServerCommands.test.tsx: 72 testsVerification
npm run local:gategreen end to end — validate (all three durable guards,test:scripts,validate:core, four client validations), the per-file ≥90 coverage gate,verify:build-gate,verify:bundle-externals, the smokes including all three web smokes,smoke:web:firefox, and the Storybook play functions.No screenshots: this is a pure relocation with no rendered change.
🤖 Generated with Claude Code
https://claude.ai/code/session_013hzwzS8UvBsr4yZm7o7sev