Commit graph inspector/clients/web/src/components/groups/TaskCard
Author SHA1 Message Date
Cliff Hall
834fd2d77d
chore(deps): migrate to eslint 10 to clear the brace-expansion DoS advisory (#1900)
* chore(deps): migrate to eslint 10 to clear the brace-expansion DoS advisory (#1838)

eslint 9 pulled `minimatch@3` → `brace-expansion@1.1.16`, which carries
GHSA-mh99-v99m-4gvg (high, DoS via unbounded expansion length). It appeared in
all five lockfiles — the root plus each of the four clients, none of which is an
npm workspace, so all five had to move together.

Deferred from the pre-2.0.0 sweep because it is dev-scope, not attacker-
controlled, and a major bump across five packages was the wrong thing to attempt
immediately before an irreversible publish. This is that work.

## What moved

- `eslint` `^9.39.4/5` → `^10.8.0` and `@eslint/js` → `^10.0.1`, in all five
  manifests.
- `eslint-plugin-react-hooks` `^7.0.1` → `^7.1.1` in web and tui. Required, not
  cosmetic: 7.0.1's peer range stops at `^9.0.0`, so installing it alongside
  eslint 10 fails `ERESOLVE`.
- Everything else already declared eslint 10 support and stayed put:
  `typescript-eslint@^8.56.1` (`^8.57 || ^9 || ^10`),
  `eslint-plugin-react-refresh@^0.5.2` (`^9 || ^10`),
  `eslint-plugin-storybook@^10.2.19` (`>=8`).

## Config migration

None was needed. All five configs were already flat (`eslint.config.js` with
`defineConfig`/`globalIgnores` from `eslint/config`), which is the format v10
keeps and the only one it still supports. The root config's `core/` and shared-
surface blocks — including the `_`-prefix `argsIgnorePattern` /
`varsIgnorePattern` / `caughtErrorsIgnorePattern` — are unchanged and still gate
`lint:core` and `lint:shared`.

No rule was disabled, downgraded, or scoped away to make the new eslint pass.
v10 surfaced 21 real violations across three rules; all 21 are fixed in the code.

### `preserve-caught-error` (new in `eslint:recommended`) — 15 sites

A `throw new Error(msg)` inside a `catch` silently drops the original error.
Each now passes `{ cause }`, so the underlying failure survives into the chain.
Messages are unchanged, so nothing that asserts on them moves. 12 in `core/`,
2 in `clients/cli`, 1 in `test-servers/src`.

### `no-useless-assignment` (new in `eslint:recommended`) — 2 sites

`clients/cli/src/cli.ts`'s `optionArgs` and `test-servers/src/test-server-oauth`
's `valid` were initialized and then unconditionally overwritten on every path.
Both are now declared without the dead initializer; TypeScript's definite-
assignment analysis covers the remaining paths.

### `react-hooks/set-state-in-effect` — 8 sites (web)

These were false negatives the plugin fixed in 7.1.0, not a rule we newly opted
into: `set-state-in-effect` has been `error` in `flat/recommended` since 7.0.1,
and the web config has always extended it. Every one was the same anti-pattern —
resetting or re-syncing local state from a prop inside a `useEffect`, which
paints the stale value for one frame and then renders again to correct it.

All eight now use a new `useValueChange(value, onChange)` hook
(`clients/web/src/hooks/useValueChange.ts`), which is React's documented
"adjusting state during render": compare against the previous render's value
with `Object.is` and call back *during* render, so React discards the
in-progress output before it reaches the DOM.

- `MrtrConversation`, `ProtocolEntry`, `TaskCard` — mirror `isListExpanded`.
- `NetworkEntry` — same mirror, plus the "Reveal in Network" force-open. The
  reveal's rAF scroll stays an effect (real external-system work); only the
  `setIsExpanded(true)` moved. `isExpanded` is now seeded from
  `isListExpanded || revealed`, since a render-time sync fires on change and so
  cannot cover an entry that mounts already revealed.
- `PromptArgumentsForm`, `ResourceTemplatePanel` — reset on template/prompt
  switch.
- `ServerConfigModal` — reset on open. Keyed on `opened ? initial : undefined`,
  which collapses "opened flipped" and "initial changed while open" into one
  value; `initial` is already memoized, so it cannot thrash.

`useValueChange` has its own test file, and `ServerConfigModal` gains an
open→edit→close→reopen test (the close leg was previously untested, which is
what left the new guard uncovered).

## Advisory delta

`npm audit`, per package, before → after:

| Package             | before                     | after                      |
| ------------------- | -------------------------- | -------------------------- |
| root                | 9 (1 low, 6 mod, 2 high)   | 8 (1 low, 6 mod, 1 high)   |
| `clients/web`       | 19 (3 low, 6 mod, 6 high, 4 crit) | 17 (3 low, 6 mod, 4 high, 4 crit) |
| `clients/cli`       | 6 (2 low, 4 high)          | 4 (2 low, 2 high)          |
| `clients/tui`       | 4 (1 low, 3 high)          | 2 (1 low, 1 high)          |
| `clients/launcher`  | 3 (3 high)                 | 1 (1 high)                 |

`brace-expansion` is gone from all five — `eslint@10` resolves `minimatch@10` →
`brace-expansion@5.0.9`. Dropping `@eslint/eslintrc` (removed in v10) also took
`js-yaml` with it, clearing GHSA-52cp-r559-cp3m (high) from all four clients as
a bonus. The remainder are unrelated and tracked separately.

`npm run ci` passes end to end — validate, the per-file ≥90 coverage gate, the
build gate, all five smokes (including `smoke:tui`, run locally on a real TTY),
and 462 Storybook tests.

Also documents the effect rule in AGENTS.md and mirrors it into
`.github/copilot-instructions.md`, since it is now something a reviewer cites.

Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: state useValueChange's referential-stability requirement (#1838)

Copilot review on #1900: the hook compares with `Object.is`, so an object or
array literal rebuilt in the component body compares unequal every render.
Because the callback is what updates state, that is not merely a wasted render —
it is an infinite loop.

Every current caller passes a primitive (`boolean`, `string`) or an already-
memoized value, so nothing changes behaviorally. But the hazard was implicit,
and the next caller is the one it would bite.

Documented on the hook itself, and one clause added to the AGENTS.md rule with
its mirror in `.github/copilot-instructions.md`. No runtime guard: the
requirement is the same one a `useEffect` dependency array carries, and here the
failure is loud and immediate rather than silent.

`npm run ci` passes.

Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(cli): stop asserting the --tool-args-json catch binding is an Error (#1838)

Copilot review round 2 on #1900: the `--tool-args-json` parse handler formatted
its message with `(e as Error).message`. A `catch` binding is `unknown`, so that
cast is an assertion rather than a check — a thrown non-Error would render
`--tool-args-json is not valid JSON: undefined`, which points the user at
nothing.

`JSON.parse` does only throw `SyntaxError` today, so this is not a live bug. But
the cast is the kind AGENTS.md asks us not to leave unjustified, the same
handler was already being touched here to add `{ cause: e }`, and every other
site in this PR uses the guarded form. Now it does too.

`npm run ci` passes.

Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: require useValueChange's onChange to be render-pure (#1838)

Copilot review round 3 on #1900: `onChange` is called during render, so it is
bound by render-phase purity — and the web client mounts under `StrictMode`,
which deliberately double-renders in development. A callback doing anything
external would run an unpredictable number of times, and concurrent React can
abandon an in-progress render entirely.

Every current caller already passes a callback that only calls `setState`, so
nothing changes. But "runs during render" is the whole point of the hook and the
constraint that follows from it was left implicit.

Stated on the hook, with the same clause in the AGENTS.md rule and its
`.github/copilot-instructions.md` mirror. All three now point at `NetworkEntry`
as the worked example of the split: the reveal's force-open is a state update
and lives in `useValueChange`, while its `requestAnimationFrame` scroll stays a
`useEffect`.

`npm run ci` passes.

Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:13:30 -04:00
Cliff Hall
2259b1a3e1
Tasks: io.modelcontextprotocol/tasks extension on modern servers (era fork) (#1731)
Some checks failed
Run install, format, lint, build, and test on every push / build (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish-github-container-registry (push) Has been cancelled
* Tasks: modern io.modelcontextprotocol/tasks extension (era fork core)

Core + test-server implementation of the SEP-2663 tasks extension for modern
(2026-07-28) connections, forked from the legacy 2025-11-25 tasks path.

SDK v2 removed all tasks support AND era-gates the tasks/* spec methods out of
the modern era on both the client (outbound) and server (inbound) sides, and its
codec rejects a resultType:"task" result outright. So the Inspector drives the
extension itself: task creation rides tools/call (a modern method) with the
resultType:"task" frame rewritten at the transport to a CallToolResult carrying
the handle in _meta; tasks/get/update/cancel go over a raw-wire request channel
that bypasses the era gate (full modern envelope, string ids, consumed by the
transport). input_required tasks reuse the existing pending-request modal via
fulfilInputRequests, answered with tasks/update. No tasks/list, no tasks/result.

Tab + run-as-task gate on the negotiated extension; the managed task store's
refresh re-polls known handles on modern (no server list). Test server serves
the extension via an Express interceptor (SDK can't) + a shared poll-driven
runtime. Full integration coverage of both eras.

Refs #1631

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* Tasks modern era: tests + coverage (raw-wire, schemas, era integration)

Adds unit coverage for modernTaskSchemas and the raw-wire request channel
(transport-null guard, send failure, timeout, error response, teardown), plus
end-to-end era integration tests (create/poll/input-required/update/cancel,
synchronous passthrough, unknown-id error, modern store re-poll). Fixes the App
and constructor-capabilities tests for the always-on tasks extension.

Refs #1631

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* Tasks modern era: docs + share test servers per era to de-flake coverage

Document the tasks era-fork test configs in README and the new core/test-server
files in AGENTS.md. Share one modern + one legacy test server across the era
integration block (started lazily, stopped in afterAll) — starting a fresh
Express server per test made connection negotiation flaky under the heavy
concurrent coverage run.

Refs #1631

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* Restore pr-screenshots deleted inadvertently

The logging/subscriptions PR screenshots + README were staged-deleted in the
working tree before this branch; restore them from v2/main so this PR doesn't
remove other PRs' artifacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* Tasks modern era: offer Run-as-task for any tool + PR screenshots

On modern (SEP-2663) task creation is server-directed — the client declares the
extension once and any tool may return a task — so the Tools screen now offers
'Run as task' for every tool on a modern connection (threaded via a modernTasks
prop through InspectorView → ToolsScreen → ToolDetailPanel), rather than gating
on the legacy per-tool taskSupport. Verified end-to-end in the web UI (remote
proxy): create → poll tasks/get → input_required elicitation → tasks/update →
inline result, plus the unchanged legacy tasks/list/result flow. Adds proof
screenshots.

Refs #1631

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* Tasks modern era: address two follow-ups (task-input note + ordinary-path auto-poll)

1. The pending-request note now distinguishes a modern task round from an MRTR
   round: a new 'task-input-required' PendingRequestOrigin threads through
   fulfilInputRequests → the pending-request modal → MrtrOriginNote, which shows
   'submitted via a tasks/update request (SEP-2663), not a retry' instead of the
   MRTR retry copy.

2. The ordinary (non-run-as-task) modern callTool path now auto-polls an
   unsolicited task handle: attemptToolCall detects the rewritten handle in the
   result _meta and drives it to termination (get → input_required/update →
   inline result), dispatching task events so the Tasks tab + toasts track it and
   the tool result resolves to the task's final result. The poll loop is shared
   with the streaming path via extracted helpers (submitModernTaskInput /
   modernTaskTerminalOutcome / modernTaskPollInterval).

Refs #1631

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* Tasks modern era: address review (negotiated envelope version, input-round cap, clarifying comment) + fresh smoke screenshots

Review of #1731:
1. withModernTaskEnvelope now stamps the NEGOTIATED protocol version
   (getProtocolVersion() ?? MODERN_PROTOCOL_VERSION) so the envelope matches the
   MCP-Protocol-Version header instead of a hardcoded constant.
2. A never-completing input_required task is now bounded by MRTR_MAX_ROUNDS in the
   shared submitModernTaskInput (threaded through both poll loops), so a
   non-conformant server can't re-prompt the user forever. New modern_loop_task
   test-server tool + integration test cover the cap.
3. ToolDetailPanel: comment clarifying that per-tool taskSupport:"forbidden" is
   deliberately ignored on modern (task creation is server-directed under SEP-2663).

(The abort-during-poll observation is addressed in the PR reply: the cancel path
uses tasks/cancel and disconnect rejects the in-flight raw request, so the poll
stops; matches legacy.)

Fresh web-UI smoke screenshots (modern connected, task completed, input_required
with the corrected tasks/update note, input completed).

Refs #1631

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* Tasks modern era: fix uncancelable input-required loop + sticky cancel

Two bug fixes reported during manual testing of the tasks era-fork (#1631):

1. A modern task paused at `input_required` (e.g. `modern_loop_task`, which
   never advances) could not be cancelled: the poll loop blocked on the
   pending-request modal, so `cancelRequestorTask` sent `tasks/cancel` but the
   modal kept re-prompting. Now each modern poll loop registers a per-task
   AbortController (`registerTaskInputAbort`), `cancelRequestorTask` aborts it
   to reject the pending elicitation and unblock the poll, and the embedded
   input requests are tagged with their owning task id
   (`RELATED_TASK_META_KEY`) so the pending-request UI's Cancel action cancels
   the TASK rather than sending a "cancel" answer the server would re-prompt on.

2. On the legacy server, cancelling a task briefly showed CANCELLED then
   flipped to COMPLETED when the in-flight tool call resolved. Cancellation is
   a deliberate terminal decision, so `ManagedRequestorTasksState` now remembers
   cancelled task ids and ignores any later non-cancelled status update.

Also: move the compact task id onto its own line in the embedded monitoring
sidebar so the status badge / Cancel Task control don't wrap; DRY the duplicated
task poll-interval logic into a shared `taskPollInterval` helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* Refresh tasks PR screenshots + capture both bug-fix scenarios

Recapture the five tasks flow screenshots against the current build and add
two demonstrating the post-review fixes: modern_loop_task cancelled from the
pending-request modal (uncancelable-loop fix) and a legacy task staying
CANCELLED after the in-flight call resolves (sticky-cancel fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* Address re-review: sticky cancel on legacy refresh + App cancel-routing test

Re-review of the cancelable-loop / sticky-cancel fix raised two non-blocking
hardening items:

1. The legacy tasks/list refresh() rebuilt the list from the server, honoring
   only dismissedTaskIds — so a cooperative-cancel server that re-lists a
   user-cancelled task as completed could un-stick the cancel on a manual
   Refresh. The refresh path now pins a cancelledTaskIds task to "cancelled"
   (keeping it visible-as-cancelled, matching the live-merge handlers) rather
   than dropping it. Covered by a new state test.

2. Added an App.test.tsx case locking in the user-facing half of the fix: Cancel
   on a task-input-required elicitation calls cancelRequestorTask (not respond).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 19:17:04 -04:00
cliffhall
19a6df7ad3 SDK v2 migration (behavior-neutral, versionNegotiation: 'legacy') (#1624)
Migrate all three clients + core + test-servers from
@modelcontextprotocol/sdk@1.29 to SDK v2 (2.0.0-beta.4), keeping behavior
identical against legacy (2025-11-25) servers. Foundation for the SDK V2 +
New Spec stack.

Packages
- Split @modelcontextprotocol/sdk -> @modelcontextprotocol/client (Client,
  transports, OAuth, error taxonomy, protocol types) + core (Zod schemas);
  stdio -> client/stdio, Ajv -> client/validators/ajv. Test-servers ->
  @modelcontextprotocol/server (+ server-legacy/sse). Deterministic import
  rewrite (schemas -> core, everything else -> client); the codemod misroutes
  client code to /server so it was not used for path routing.
- new Client(..., { versionNegotiation: { mode: 'legacy' } }) — nothing
  2026-era goes on the wire.

Runtime (core/mcp/inspectorClient.ts)
- Error taxonomy: McpError -> ProtocolError, ErrorCode -> ProtocolErrorCode;
  RequestTimeout -> SdkError/SdkErrorCode.
- Handlers take method strings; task methods (tasks/*, notifications/tasks/*)
  use the 3-arg custom form (excluded from v2's spec-method set).
- Legacy tasks kept working (per project requirement): requestor tasks re-
  implemented over raw client.request + explicit deprecated schemas;
  experimental.tasks.callToolStream replaced by a local pollTaskToolCall
  async generator (with _progressHandlers revival so task-execution progress
  still reaches the caller). Receiver (server-initiated) tasks: the v2 Client
  validates a sampling/elicit handler result and rejects the legacy { task }
  response with -32602 — installReceiverTaskResponseBypass swaps the wrapped
  entry in the private _requestHandlers map for the task-augmented branch so
  { task } reaches the wire (fixes server-initiated sampling-via-task).
- Pagination: list verbs use raw client.request({method}, List*ResultSchema)
  for single-page behavior (v2's client.listX() auto-aggregate all pages).

Web / auth / apps
- Rename local JsonSchemaType -> InspectorFormSchema + add toFormSchema()
  structural narrowing at the SDK-schema -> form boundary.
- ext-apps still peers on SDK v1: one compat boundary with documented casts
  (remove when ext-apps#702 lands a v2 peer).
- OAuth provider saveClientInformation reconciled with v2's
  OAuthClientInformationContext; InvalidClientMetadataError re-homed as a
  local class (server-legacy/auth is not browser-safe).
- vitest.shared.mts dedupes @modelcontextprotocol/client+core to one copy so
  vi.mock intercepts the SUT's import.

Accepted v2 behavior changes (per #1624 / spec §9.2): unknown-tool call
rejects with -32602; cancellation is stream-abort (no guaranteed
notifications/cancelled frame); output-schema strictness restored via the
Inspector's own Ajv check on the default path.

npm run ci green: validate (web 3087 / cli 212 / tui 274 / launcher 5),
coverage (>=90 per-file, all clients), smoke (launcher/cli/web), storybook
(410).

Closes #1624

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 10:44:24 -04:00
Cliff Hall
3d75ef5f8e
web: Accessibility — accessible palette + WCAG AA contrast, labels, names, roles (#1679)
* web: accessible light/dark color palette + dark-mode legibility fixes (#1678)

Ports the design system's accessible palette. Colors are defined once as Mantine
color tuples in theme.ts (shade 0 lightest → 9 darkest); every `--inspector-*`
token in App.css references `--mantine-color-*`, so improving the tuples cascades
through the whole UI.

- theme.ts: full accessible tuple set (inspector/gray/dark/green/red/yellow/blue/
  teal/violet), WCAG-AA at the primary shades. Tuples are the one place literal
  hex belongs — the token source. Primary stays inspector-7 (light) / -8 (dark).
- theme/ActionIcon.ts: default `color: "gray"` so chrome icons (settings, theme
  toggle, sidebar, pin, sort, replay) render neutral grey instead of inheriting
  the blue primary. Icons that set their own color/variant still override.
- App.css: fix light surfaces (`surface-body` gray-0, `surface-card` white — the
  body token pointed at near-black dark-9); add direction/method badge tokens;
  lift the semantic status/log/task/history colors 1–2 steps in the dark scheme
  (the shared `*-6` shade sat too dark on charcoal). Light `:root` keeps `*-6`.
- Badges: MessageDirectionBadge + new MethodBadge element driven by
  `--inspector-badge-*` tokens (deep fill + light text in dark, tinted in light)
  via Mantine `bg`/`c` style props with `autoContrast={false}`; swaps
  `<Badge color="dark">{method}</Badge>` in ProtocolEntry/NetworkEntry (their
  Badge import stays — both still use it for status/category chips). Stories +
  test added for MethodBadge (≥90 coverage gate).

Domain badge semantics live in the element components, not theme files. No raw
hex outside the tuples; no inline styles. Supersedes the reverted #1677.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw

* web: WCAG AA accessibility pass — contrast, labels, names, roles (#1678)

Drives the axe-core ruleset (the engine behind Storybook's a11y addon) to
zero violations across all stories in light and dark mode.

Contrast (805 → 0 hits):
- Darken Mantine `*-light-color` + `dimmed` in light mode (and lift dark
  `dimmed`) via `!important` App.css overrides — Mantine injects these at
  runtime at matching specificity, so specificity alone loses the tie.
- `filledBadgeColor` pins amber (orange/yellow) filled badges to shade 5 so
  `autoContrast` (JS-computed from the theme shade, not CSS vars) picks black.
- `accessibleTextColor` routes colored log/filter text through the scheme-aware
  `-light-color` token so it clears AA on light and dark surfaces.
- `status` Badge variant: amber statuses use black-on-amber (orange text can't
  reach AA on light at any Mantine shade).
- Resource URIs use `-light-color` (blue-4 was 4.38:1 in dark).

Names / roles:
- `aria-dialog-name`: custom-header modals use `Modal.Title` (wires the
  dialog's `aria-labelledby`) instead of a bare `Title` + a root `aria-label`
  (which also tripped `aria-prohibited-attr`).
- `button-name`: a `Modal` theme default labels every built-in close button.
- `label`: `aria-label` on the previously-unlabelled Textarea/Select inputs.
- `scrollable-region-focusable`: keyboard-focusable content Code/ScrollArea
  viewports; long OAuth values wrap instead of scrolling.
- `heading-order`: correct heading levels (keeping visual size via `size`) so
  outlines don't skip a level under the h2 modal/section titles.

Scrollbars: `type="scroll"` ScrollArea theme default — scrollbars auto-hide and
show only while scrolling (a deliberate product choice over the always-visible
a11y default).

`region` is disabled in the Storybook a11y config: isolated component/screen
stories render outside the app's `AppShell` landmarks, so it's a page-level
concern the components can't own.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* web: let the resource template form fill the main-area width (#1678)

Drop the maw=40% cap on the template-form pane so the URI preview and
variable inputs use the full width beside the sidebar, matching the
resource preview pane.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* web: left-align + reverse the input-panel action rows (#1678)

Move the footer action buttons to the left (justify flex-start) and put
the primary action first on the Tool / Prompt / Resource-preview /
Resource-template panels — Subscribe, Refresh, View Source on the
preview panel; Execute before Cancel on the tool panel. Shortens the
pointer travel from the sidebar controls / the form fields above to the
actions, which matters most when the monitoring sidebar is hidden and
the form is wide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* web: match the dark-mode body background to the header tab bar (#1678)

Set the dark `--inspector-surface-body` to `dark-8` (the Mantine
SegmentedControl fill used by the header tab bar) instead of `dark-9`,
mirroring light mode where the body and the tab bar share `gray-1`.
Cards (`gray-9`) still sit a step above the body, and the contrast audit
stays at zero AA violations on the slightly lighter background.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* web: enforce the a11y gate + fix modal-overlay heading-order (review #1679)

Address the code review:
- Flip the Storybook a11y addon from test: 'todo' to 'error' so axe
  violations fail the play-function tests in CI — the zero-violation
  state is now gated, not point-in-time.
- Enforcing it surfaced a real heading-order skip: the Tools screen's
  h4 'Tools'/'Results' sit under an h2 Modal.Title when a sampling/
  elicitation request modal opens over them. Lift both to h3 (size h4
  preserves the look) so h2→h3 is valid in any DOM order.
- Comment the intentional color+c pairing on the Remove/Disconnect
  danger buttons.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* web: brighter warning alerts + pin the elicitation form's actions (#1678)

- Add a 'warning' Alert variant: a bright light-yellow fill (yellow-1)
  with crisp near-black text and a bright yellow-5 left accent, instead
  of the muddy brown a tinted yellow alert produced once
  yellow-light-color was darkened to yellow-9 for contrast. Dark mode
  keeps the light-on-dark-tint treatment. Applied to all five yellow
  warning/notice alerts.
- Elicitation form: scroll only the form fields (capped ScrollArea),
  keeping the quoted message, warning, and Cancel/Decline/Submit buttons
  in view. The modal has no other close affordance, so the actions must
  never scroll out of reach.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* web: robustly pin the elicitation form's actions (#1678)

The previous approach capped only the fields' ScrollArea with a fixed
rem reserve for the pinned chrome, which underestimated it on some
viewports — the whole modal then overflowed and the buttons clipped
half-off-screen. Instead cap the whole panel below the modal height and
lay it out as a flex column: the message and the warning + buttons are
pinned (flex: 0 0 auto) and only the fields (flex: 1) shrink and scroll.
Verified the buttons stay within the panel and the modal fits the
viewport down to very short heights.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* web: fix elicitation form scrolling — fields scroll, actions pinned (#1678)

The prior fix capped the panel with a bare max-height, but a flex:1
ScrollArea's viewport won't bound against a max-height (not fixed-height)
parent, so a tall form's fields overflowed unscrollably (clipped) rather
than scrolling. Adopt ToolDetailPanel's pattern instead: the modal gives
its body a bounded flex column, the panel fills it, and the fields use a
flex: "0 1 auto" ScrollArea — which sizes to content when the form is
short (modal stays compact, nothing scrolls) and shrinks + scrolls once
the panel is capped at the modal height, keeping the message, warning,
and Cancel/Decline/Submit pinned in view. Add a ManyFields story (with a
bounded-height decorator mirroring the modal) to cover the scroll case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* web: fix elicitation scrolling in the real modal (cap fields, not flex)

The flex-column approach (panel flex:1 in a flex-column modal body, fields
flex:"0 1 auto") worked in a fixed-height Storybook decorator but NOT in the
real modal: the modal body is bounded by max-height, not an explicit height,
so the fields' ScrollArea viewport still couldn't bound — a tall form's
fields overflowed and pushed the warning + Cancel/Decline/Submit off-screen.

Drop the flex plumbing (and the modal-body styles) and cap the fields
directly with ScrollArea.Autosize mah=calc(90dvh - 22rem): it sizes to
content when the form is short (modal compact, nothing scrolls) and scrolls
once the fields hit the cap. The 22rem reserve = the measured non-field
chrome (modal header/padding + message + warning + buttons + gaps), so the
whole modal stays within 90dvh and the actions are always in view. Verified
in the real PendingClientRequestModal (new ElicitationFormTall story) for
both short and tall forms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* web: pin the sampling modal's actions too (#1678)

Apply the elicitation-form treatment to SamplingRequestPanel: wrap the
request/response content in a ScrollArea.Autosize capped at
calc(90dvh - 10rem) so it scrolls while the Reject / Send Response buttons
stay pinned in view (the modal has no other close affordance). The 10rem
reserve is the pinned chrome (modal header/padding + the actions + gaps) —
smaller than the elicitation form's 22rem since there's no warning banner.
Add a SamplingTall story (8 messages + preferences) to cover the scroll
case in the real PendingClientRequestModal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 18:20:55 -04:00
Cliff Hall
121f875a0e
feat(web): History — Pin & Replay, plus two history-status fixes (#1438) (#1441)
* fix(web): don't show a permanent PENDING badge on history notifications (#1439)

`extractStatus` derived the history status badge purely from whether a
`response` was attached, so a fire-and-forget notification (no id, no
response ever) — and any unmatched standalone response — was labelled
"Pending" forever. The pending → OK/Error lifecycle only applies to
request entries (messageLogState attaches the response by JSON-RPC id),
so scope the status to `direction === "request"` and render no
request-style badge for notifications/responses. The method badge already
labels them.

Part of the History-screen work on #1438.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(core): track outgoing responses so server→client requests resolve in history (#1440)

MessageTrackingTransport.send only tracked outgoing requests, so the
client's response to a server→client request (roots/list, sampling,
elicitation) was never recorded. The inbound request entry never got its
response folded in, leaving it stuck "Pending" with no response body.

Make send symmetric with onmessage: track outgoing responses (by id +
result/error) as well as outgoing requests. The response now correlates
to its request entry, which resolves to OK/Error with the body. Outgoing
notifications remain untracked (separate gap).

Part of the History-screen work on #1438.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): wire up History Pin and Replay (#1438)

Both HistoryEntry actions were rendered but stubbed to todoNoop in App.

- Pin: App now owns a session-scoped Set<string> of pinned entry ids,
  toggled by onTogglePinHistory and passed down as pinnedHistoryIds.
  HistoryListPanel already sorts pinned entries to the top. The set resets
  with the rest of the per-screen state on disconnect/server-switch.
- Replay: onReplayHistory re-issues the entry's original request by method
  (tools/call → callTool, prompts/get → getPrompt, resources/read →
  readResource). The call flows through InspectorClient → tracked
  transport → message log, so the fresh request+response surface as a new
  History entry (history-local) without touching the Tools/Prompts/
  Resources panels. Unsupported methods / a removed tool surface a toast.

Tests: pin toggle propagation, replay dispatch for each supported method,
unsupported-method and missing-tool toasts.

Closes #1438.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): make history Replay support list/discovery requests

Replaying a tools/list entry reported "Replay isn't supported". Extend
replayHistoryRequest beyond the call-style requests to the read/discovery
methods, re-issuing each via its InspectorClient method (preserving the
pagination cursor):

- tools/list → listTools, prompts/list → listPrompts,
  resources/list → listResources,
  resources/templates/list → listResourceTemplates, ping → ping.

Tests: tools/list replay (cursor preserved) and an unsupported-method
toast (logging/setLevel).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): replay tasks/list; reorder + conditionally show History buttons

- Add tasks/list to the replayable set (re-issued via listRequestorTasks),
  completing the client-issued */list methods.
- Reverse the HistoryEntry actions to Pin then Replay, and hide Replay for
  entries whose method can't be replayed.
- Introduce a single source of truth — REPLAYABLE_HISTORY_METHODS /
  isReplayableHistoryMethod in historyUtils — used by HistoryEntry to
  show/hide the button and by App's replayHistoryRequest to gate dispatch,
  so the two can't drift.

Tests: tasks/list replay, Replay hidden for a non-replayable method, and
Pin-before-Replay ordering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): add client↔server direction badge to History and Network (#1437)

A dual-state badge at the top-left of each entry shows which way it
traveled: "client → server" (green) for inspector-originated, "client ←
server" (yellow) for server-originated.

- New shared element MessageDirectionBadge (Elements).
- History: MessageEntry gains an `origin` ("client" | "server") set at
  tracking time — MessageTrackingTransport tags `send` as client and
  `onmessage` as server, threaded through the track callbacks. HistoryEntry
  maps origin → badge. (direction stays the request/response/notification
  message-type used for response correlation; origin is the flow.)
- Network: fetches are always inspector-originated, so NetworkEntry renders
  the badge as outgoing.

Tests + stories for the badge; HistoryEntry direction tests; transport
test asserts the origin argument.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* style(web): use purple (not yellow) for the client ← server direction badge

Yellow + green read as caution/ok status; purple vs green reads as
direction. Incoming (server → client) is now violet; outgoing stays green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): make History Pinned/History sections collapsible toggles

The "Pinned Requests (N)" and "History (N)" section headers are now
clickable toggle buttons that expand/collapse their entries — styled like
the LogControls level toggles (UnstyledButton listItem variant with the
active background) and wrapping the entries in a Collapse. Both default
open and collapse independently.

Tests assert the per-section aria-expanded toggling and independence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): replace Expand/Collapse text button with an icon toggle

History, Network, and Task cards now use a shared ExpandToggle ActionIcon
instead of the "Expand"/"Collapse" text button: LuArrowDownToLine when
collapsed (expand), LuArrowUpFromLine when expanded (collapse). The
aria-label stays "Expand"/"Collapse" so the control reads the same.

- New shared element ExpandToggle (+ test + stories).
- HistoryEntry: actions split into a left group (Pin/Replay) with the
  toggle pushed right via space-between.
- NetworkEntry / TaskCard: drop their now-unused SubtleButton const.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): pin icon toggle on HistoryEntry, moved to the right

Replace the Pin/Unpin text button with a shared PinToggle ActionIcon
(TiPinOutline when unpinned, TiPin when pinned; aria-label stays
Pin/Unpin). Move it to the right action group, just left of the
expand/collapse toggle — Replay stays on the left.

- New shared element PinToggle (+ test + stories).
- HistoryEntry action row: Replay left; Pin + expand toggle right.
- Drop the now-unused formatPinLabel helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): per-section Clear/Export on the Pinned and History headers

Each section header now shows Clear and Export links on the right, but
only when both sections are present (a single section is already covered
by the panel-level Clear/Export). They act on just that section's entries:

- Clear: removes that section's entries by pin membership (clearing the
  Pinned section also drops the now-stale pinned id set).
- Export: downloads just that section's entries
  (inspector-history-pinned/-unpinned-...json).

Threaded onClearSection/onExportSection App → InspectorView → HistoryScreen
→ HistoryListPanel; App computes each section from messages + pinned ids.
Extracted a CollapsibleSection subcomponent and a SectionActions links
group. Extended ExportKind with history-pinned/history-unpinned.

Tests cover both-sections visibility gating and per-section dispatch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): Filter by Message Direction on the History controls

Add a "Filter by Message Direction" section to HistoryControls, modeled on
LogControls' "Filter by Level": a Select/Deselect All control plus two
listItem toggles — "client → server" and "client ← server" — that show or
hide entries by origin.

- HistoryUiState gains visibleDirections (Record<MessageOrigin, boolean>),
  defaulting to both visible; persists with the other per-screen state.
- HistoryScreen wires the toggles (single + all) through onUiChange and
  passes the filter to HistoryListPanel, where matchesFilters drops entries
  whose direction is off (entries without a recorded origin are never
  filtered out).

Tests cover the controls (render, single toggle, select/deselect all), the
HistoryScreen onUiChange wiring, and the list-level direction filtering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* style(web): section Clear/Export as subtle buttons, not link anchors

Match the Select/Deselect All control in HistoryControls: the per-section
Clear/Export on the History/Pinned headers now use a subtle Button
(variant subtle, size xs) instead of an Anchor link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* style(web): color the direction filter labels to match the badge

The "client → server" / "client ← server" toggles in HistoryControls now
color their labels green / violet, matching the MessageDirectionBadge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): track outgoing client notifications in history too

Review follow-up (#1441): MessageTrackingTransport.send now also tracks
the client's own notifications (initialized, progress,
roots/list_changed) as origin "client", making send fully symmetric with
onmessage. Previously only id-bearing outgoing messages (requests,
responses) were tracked, so client notifications never reached History
while incoming server notifications did.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): Filter by Message Direction on the Network screen

Add the same "Filter by Message Direction" section to NetworkControls as
on HistoryControls. Extracted the shared section into a MessageDirectionFilter
group component (used by both HistoryControls and NetworkControls) so they
stay identical — a Select/Deselect All control plus green/violet
client → server / client ← server toggles.

- NetworkUiState gains visibleDirections (default both visible), persisted
  with the rest of the per-screen state; NetworkScreen wires the toggles and
  NetworkStreamPanel filters on them.
- Network fetches are always inspector-originated, so all entries are
  client → server: the client toggle gates them all and the server toggle
  matches nothing (documented in NetworkStreamPanel). The section mirrors
  History for parity.

Tests cover the shared filter, both controls' direction sections, the
Network list filtering, and the NetworkScreen wiring; ambiguous "Deselect
All" selectors (now two sections) disambiguated by order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* revert(web): drop message direction from the Network tab

Network traffic is always client → server — even SSE is a client-initiated
stream tagged outgoing — so a direction badge/filter there is meaningless.
Remove the MessageDirectionBadge from NetworkEntry and the Filter by Message
Direction section (and its NetworkUiState.visibleDirections wiring) from the
Network screen.

History keeps its direction badge + filter; the shared MessageDirectionBadge
and MessageDirectionFilter components stay (still used by History).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(web): a lone History section isn't a collapsible accordion

When only one of Pinned / History is present, its header is now a plain
title and its entries always show — instead of a `listItem` accordion
toggle that could hide them based on a stale collapsed state from when
both sections were on screen. The accordion (and per-section Clear/Export)
only applies when both sections are present.

Example fixed: collapse History while pins exist, then unpin everything —
History becomes the sole section and shows its entries under a plain
header rather than staying collapsed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* style(web): put section Clear/Export on the header pleat itself

The per-section Clear/Export sat to the right of the collapsible header
bar, on the panel background. Make the whole header row the "pleat":
a new Group `sectionHeader` theme variant (rounded + hover, active bg
passed per instance) wraps a flex-1 toggle button (the title) on the left
and the Clear/Export buttons on the right — so the actions live on the
pleat. The toggle is a separate button since the action buttons can't nest
inside it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* style(web): entry expand toggle uses the same icons as ListToggle

Swap ExpandToggle (History / Network / Task cards) from the LuArrow icons
to RiExpandVerticalLine / RiCollapseVerticalLine — the same expand/collapse
icons the list-level ListToggle uses. Same ActionIcon size, so it matches
the other small buttons on the cards; aria-labels unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(web): panel Clear keeps pinned entries (history-only)

Review follow-up (#1441): the panel Clear button is gated on the unpinned
count (implying history-only), but onClearHistory wiped everything
(pinned included) and left pinnedHistoryIds stale. Make Clear history-only
— clearMessages((m) => !pinnedHistoryIds.has(m.id)) — so pinning protects
an entry, matching the gating and the per-section model; the pin set stays
valid since its entries survive.

Also drop the stale "and Network" mention from the MessageDirectionFilter
docstring (Network direction filter was reverted).

Test: panel Clear's predicate keeps pinned ids and removes unpinned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:21:52 -04:00
Cliff Hall
75f74d64c1
test(coverage): bootstrap vitest unit infra and add tests at 90%+ per file (#1245) (#1269)
* test(coverage): bootstrap vitest unit infra and add tests at 90%+ per file (#1245)

Establishes the unit-test runtime AGENTS.md mandates: a vitest "unit"
project alongside the existing storybook project, RTL + jest-dom +
happy-dom, a Mantine-aware render helper, and a 90%-line per-file
coverage gate enforced by CI. Adds 66 test files covering all 62
components under elements/groups/screens/views, plus core/mcp and
clients/web/src/utils. Total: 577 tests, 98.7% line coverage globally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(coverage): address PR #1269 review feedback

- AGENTS.md: correct coverage gate description (lines ≥ 90, statements ≥ 85,
  functions ≥ 80, branches ≥ 50) with rationale for the relaxed branch
  threshold
- InspectorView.test.tsx: document what the Math.random() mock values
  (0.1 / 0.99) control vs. STUB_SUCCESS_RATE in InspectorView.tsx
- vite.config.ts / tsconfig.test.json: drop unused `globals: true` and the
  `vitest/globals` type now that every test file imports describe/it/expect/vi
  explicitly; manual `afterEach(cleanup)` in setup.ts remains required because
  testing-library auto-cleanup relies on globalThis.afterEach

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:15:32 -04:00
cliffhall
3d6280eb0e Address PR #1217 code review feedback
- Fix Collapse animation: remove outer {isExpanded && ...} wrapper so
  Mantine's Collapse component drives the mount/unmount animation
- Show statusMessage for all tasks, not just active ones, so failed/
  cancelled tasks display their error reason without expanding
- Remove redundant "Status" detail row — the TaskStatusBadge in the
  header already communicates status
- Fix ttl guard: change !== null to != null to correctly handle the
  SDK's optional (undefined) field

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 15:49:14 -04:00
cliffhall
6261a7e270 Use descriptive progress message in task stories
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 13:37:30 -04:00
cliffhall
475da3027e Show progress bar OR statusMessage, not both
When progress notification data is available for an active task, show
the ProgressDisplay (which includes its own message). Fall back to
the Task's statusMessage only when no progress data is present.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 13:07:18 -04:00
cliffhall
b487279d36 Add progress bar back to TaskCard via optional progress prop
Progress notifications (notifications/progress) are sent separately
from the Task object per the MCP spec. Add an optional progress prop
typed as Pick<ProgressNotification['params'], 'progress' | 'total' |
'message'> so the parent can pass progress data when available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:53:44 -04:00
cliffhall
6abb39a5fe Phase 2: Refactor Tasks cluster to MCP SDK types
Replace ad hoc TaskCardProps data shape with SDK Task type across the
Tasks group components and TasksScreen.

- TaskCard: accept `task: Task` instead of flat props (taskId, status,
  method, target, progress, etc.); derive all display fields from the
  SDK Task object (taskId, status, ttl, createdAt, lastUpdatedAt,
  statusMessage); remove fields not present on SDK Task (method, target,
  progress as number, error, completedAt, elapsed)
- TaskControls: type statusFilter as `TaskStatus | undefined`; add
  runtime validation guard on Select onChange
- TaskListPanel: accept `Task[]` instead of `TaskCardProps[]`; pass
  `onCancel(taskId)` through to each TaskCard; use `task.taskId` as key
- TasksScreen: accept `Task[]` + `onCancel(taskId)` callback; type
  internal statusFilter state as `TaskStatus | undefined`
- Delete re-export of `TaskStatus` from TaskCard (import directly from
  SDK where needed)
- Update all stories with real SDK `Task` fixtures
- Update ConnectedView TasksActive story

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:28:24 -04:00
cliffhall
3e2e44ccfc Address PR #1211 code review feedback
1. ServerCard: import ConnectionStatus and ServerType from core instead
   of inline union types
2. ResourcePreviewPanel: move ContentBlock import to top of file
3. ContentViewer: remove dead annotation mimeType check in isJsonText
4. ProgressDisplay: use Pick to exclude progressToken from params type;
   remove fake tokens from callers and stories
5. AnnotationBadge: restore human-readable priority labels (high/medium/low)
   and remove unused --inspector-annotation-* CSS vars from App.css
6. MessageBubble: add fallback rendering for resource, resource_link,
   tool_use, and tool_result content block types
7. TaskControls: import TaskStatus directly from SDK instead of via TaskCard

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 11:48:21 -04:00
cliffhall
2d4fca4fa6 Phase 1: Refactor all 16 element component interfaces to use MCP SDK types
Replace ad-hoc local type definitions in element components with real
MCP SDK types and Inspector wrapper types from core/mcp/types.ts:

Schema-typed primitives:
- LogLevelBadge: LogLevel → LoggingLevel (SDK)
- TransportBadge: "stdio"|"http" → ServerType (core), add sse/streamable-http
- TaskStatusBadge: local TaskStatus → SDK TaskStatus (working/input_required/...)
- ServerStatusIndicator: inline union → ConnectionStatus (core), "failed"→"error"

MCP wrapper consumers:
- AnnotationBadge: label+variant → facet+value with SDK Role type
- CapabilityItem: name:string → capability:keyof ServerCapabilities|ClientCapabilities
- LogEntry: flat props → entry:{receivedAt:Date, params:LoggingMessageNotification["params"]}
- MessageBubble: flat props → message:SamplingMessage|PromptMessage
- ProgressDisplay: flat props → params:ProgressNotification["params"]

Composite-input elements:
- ContentViewer: type+content+mimeType → block:ContentBlock (SDK discriminated union)
- InlineError: message+details → error:{message,data?}
- ConnectionToggle: checked+loading+onChange → status:ConnectionStatus+onConnect/onDisconnect

Also updates all group/screen/view consumers to keep the build green,
and renames CSS variable --inspector-status-failed → --inspector-status-error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 11:57:53 -04:00
cliffhall
2540617271 Show progress bar on compact view for active tasks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 09:59:09 -04:00
cliffhall
a1c894f72f Extract TaskStatusBadge element with dark/light text color handling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:10:00 -04:00
cliffhall
c494d2feb4 Redesign Tasks screen: expandable TaskCard, sidebar controls, and list panel
- Redesign TaskCard to match v1 Task Details panel layout:
  header with task ID + status badge + cancel button,
  always-visible summary row (Status, Last Updated, Created At, TTL)
  with Expand/Collapse button, expandable section with status message,
  progress bar, error, and Full Task Object JSON viewer
- Add TaskControls group: search input + status filter dropdown +
  Refresh/Clear History buttons (mirrors HistoryControls pattern)
- Add TaskListPanel group: scrollable panel with ListToggle for
  expand/collapse all, splits tasks into Active/Completed sections
  with counts, filters by search text and status
- Refactor TasksScreen to sidebar + panel layout matching HistoryScreen:
  manages search/filter state internally, flat tasks array
- Simplify TaskCard callbacks to just onCancel
- Extract named .withProps() subcomponents (HeaderLeftGroup,
  HeaderRightGroup, SectionTitle, SummaryRow, DetailRow) for
  AGENTS.md compliance
- Update all stories (TaskCard, TaskControls, TaskListPanel,
  TasksScreen, ConnectedView)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:03:54 -04:00
cliffhall
dd46acba87 Refactor renamed the component groups to something less obtuse:
Elements -> Groups -> Screens -> Views
2026-03-28 12:58:47 -04:00