Commit graph inspector/clients/web/src/hooks/useInspectorStores.test.tsx
Author SHA1 Message Date
cliffhall
4d7dd180d9 fix: address Copilot review round 13 on #2251
- SkillsScreen: put the SESSION in the invalidation key. This screen stays
  mounted across a disconnect, so content alone cannot tell server A's entry
  from an identical-looking one on server B — A's in-flight verification
  could land afterwards and report `verified` for a file never read from B,
  and a retained batch entry could leave B's Verify all disabled.
  `useInspectorStores` now exposes a `sessionNonce`, bumped on both create
  and destroy so it never repeats across a reconnect, and App keys on
  `${activeServerId}:${sessionNonce}` — the server id alone would repeat,
  which is one of the crossings this exists to prevent.
- SkillsScreen: match the selection by `skillUriIdentity`, in both the
  lookup and the NavLink active check. A refresh that canonicalizes
  `skill://demo/%53KILL.md` names the same skill, and the detail pane was
  emptying out because the server changed its spelling — the last raw-string
  URI comparison left after round 12.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kfrH3uRNUM4JAk1reKtyY
Signed-off-by: cliffhall <cliff@futurescale.com>
2026-09-05 01:40:28 -04:00
cliffhall
63623b754b feat: support the Skills extension (SEP-2640) with digest verification
Adds detection, enumeration, and conformance checking for the Skills
extension (`io.modelcontextprotocol/skills`) — phases 1 and 2 of #2234,
which is everything its Acceptance list names.

Skills is a *server*-declared extension, read off the connecting server's
`capabilities.extensions`, so it is deliberately absent from
`ADVERTISABLE_EXTENSIONS` — that registry is what the Inspector advertises
and the user toggles, and an entry there would be a meaningless toggle.

`skills/list` and `skills/get` are consumer-owned extension methods that
neither era codec defines, so the SDK's era gate skips them and they go out
as ordinary `client.request` calls with explicit result schemas. The
raw-wire channel modern `tasks/*` needs is not used, and the Skills tab is
not era-gated: a legacy-era server that declares the extension is serving it.

The checks are the point, not the list view. `core/mcp/skills.ts` reports
each obligation SEP-2640 states — the name/path invariant, the digest
format, the 512-entry and 16 MiB limits, and `resources: "dynamic"`, which
means integrity cannot be verified at all. Digest verification hashes the
fetched bytes with WebCrypto and returns a mismatch with both digests
attached rather than throwing, because showing a mismatch loudly is the
whole value proposition.

Files are fetched on demand: SEP-2640 is explicit that a `resources/read`
of a `SKILL.md` is not a load and confers no standing, so none of the SEP's
host machinery is implemented.

The `skills-http` fixture serves four skills over two pages, three of them
deliberately non-conforming — without those the verification code is
untestable.

Phase 3 (CLI, TUI, `resources/directory/read`, paged mode) is #2248.

Closes #2234

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kfrH3uRNUM4JAk1reKtyY
Signed-off-by: cliffhall <cliff@futurescale.com>
2026-09-04 20:45:15 -04:00
cliffhall
edac58e304 chore(web): address Copilot review on #2128
Seven of the eight comments, plus the three suppressed ones (the same
double-cast class):

- Narrow the toast hooks to `InspectorClientEventSource`, the client's
  typed event-listener surface. A bare `EventTarget` cannot satisfy it
  (its listener takes an `Event`, the typed one a `TypedEventGeneric`
  with `detail`), so both tests now construct a real
  `InspectorClientEventTarget` and dispatch through `dispatchTypedEvent`
  rather than casting a stand-in. That failed to compile immediately and
  caught two bad fixtures: `task()` was a `Pick<Task, "status">` where
  the event map wants a full `Task`, and `toolCallTaskUpdated` was
  dispatched with no `task` at all.
- Narrow `useExportActions`'s three store parameters to the single
  clearing method each handler calls, so the test doubles are
  structurally checked instead of cast away.
- Build real `MessageEntry` fixtures (`timestamp` is a `Date`, not `0`),
  removing four more double casts.
- Fix a tautological assertion in useTabUiState.test: it compared a
  callback to itself, so it held however the identity changed. Every
  callback is now captured before the update and compared to those
  references.
- Reword `resetTabUiState`'s doc: `exhaustive-deps` cannot infer
  stability across a hook boundary, so the dependency IS named — the
  stability buys no churn, not omission.
- Justify the one remaining double cast in place, per the type-safety
  rule.

`useProgressToasts` still returns `void` rather than an object,
deliberately: it has nothing to return, and `{}` would imply an API that
is not there. Noted on the PR as the one deviation from #2128's
"returns one object" criterion.

Signed-off-by: cliffhall <cliff@futurescale.com>
2026-08-26 19:58:02 -04:00
cliffhall
e42968ba78 chore(web): extract App.tsx's leaf hooks (#2128)
Phase 1 of #2126. Six hooks move out of `clients/web/src/App.tsx`, taking
its component body from 4,891 to 4,267 lines with no behavior change:

- useThemeToggle       — the light/dark toggle
- useTabUiState        — the nine per-screen `ui` objects, the active tab,
                         the pinned-Protocol ids, and the session reset
- useInspectorStores   — the twelve state managers as one create/destroy
                         lifecycle, their `core/react` hooks, and the
                         pagination-mode selection
- useExportActions     — the eleven Clear / Export / Replay handlers
- useProgressToasts    — `notifications/progress` toasts
- useTaskToasts        — task-status toasts, the cancel confirmation, the
                         taskId -> progress map, and the in-flight
                         tool-call taskId ref

Holding the stores in one slot collapses `setupClientForServer`'s and
`onConfirmRemove`'s dependency arrays from twelve entries to two stable
callbacks; `destroyStores` reads the live set through a ref mirror rather
than the state it also writes.

All six hooks are at 100% on all four coverage dimensions, no `v8 ignore`
needed. Also refreshes the now-stale "~4.5k-line" description of App.tsx
in AGENTS.md and the coverage `include` comment.

Signed-off-by: cliffhall <cliff@futurescale.com>
2026-08-26 19:42:16 -04:00