* web: Group tool-result resource_links in a scrollable "Resource Links" box with Protocol colors
Closes#1685
- ToolResultPanel: coalesce consecutive `resource_link` blocks into a
single bordered, internally-scrollable "Resource Links" box (mirroring
the "Messages" box in the Protocol monitoring sidebar). Non-link blocks
and non-adjacent runs are preserved in order via `segmentContent`.
- ResourceLink: render each link card on the recessed `inset` surface
(`Card variant="inset"`), matching ProtocolEntry's colors in light and
dark modes; nested read-result Code blocks read as raised on the grey
card via the inset cascade variable.
- Drop redundant metadata: the resource `description` line
(ResourceLinkInfo, and thus ContentViewer/ResourceLink) and the green
"Resource:" label above the expanded read result.
- Update tests + stories for the removed props/label and the new grouping
box; add multi-link and split-run coverage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE
* web: keep the resource_link description; only drop the "Resource:" label
Reverts the description removal from the previous commit per review — the
`description` line is retained on ResourceLinkInfo (and its ContentViewer /
ResourceLink callers). Only the redundant green "Resource:" label above the
expanded read result stays removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE
* web: drop the resource_link description from the link cards
The `description` is generated by the server and can be misleading — e.g. a
blob resource served with a `text/plain` mimeType gets described as a
"plaintext resource" even though the payload is a blob. Rather than surface
that nonsense text, drop the description line from ResourceLinkInfo (and its
ContentViewer / ResourceLink callers) entirely.
Reverts the description-retention from the prior commit; the grouped
"Resource Links" box and the "Resource:" label removal are unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE
* web: refine resource-link cards — URI-below-name + copy, ProtocolEntry toggle, full-height box
Follow-up refinements to the Resource Links display:
- ResourceLinkInfo: move the URI below the name and add a CopyButton beside
it so the URI can be copied. The name + MIME badge (and the optional
expand control) sit on the header row; the URI + copy sit on the row below.
- ResourceLink: replace the bespoke chevron with the same tooltip'd
ExpandToggle used by ProtocolEntry, and animate the read-result panel with
Mantine's Collapse (same as ProtocolEntry). The toggle is a sibling of the
URI copy button (no card-wide wrapping button) so the two real buttons
never nest (axe `nested-interactive`).
- ToolResultPanel: when a result contains a Resource Links box, fill the
card's height so the box grows into the available space and scrolls
internally (pinned "Resource Links" heading), instead of capping at a fixed
content height. Plain text/image results keep the scroll-within-card body.
- ToolsScreen: give the result card `flex: 1` so it fills the pane, providing
the definite height the Resource Links box grows into.
- Tests/stories updated for the new layout, the Expand/Collapse toggle labels,
the mounted-through-collapse read result, and the full-height box (with a
height-bounded decorator on the ResourceLinks story).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE
* web: align resource-link card with ProtocolEntry — toggle row, badge, URI style
Bring ResourceLinkInfo closer to the ProtocolEntry message card:
- Move the expand/collapse toggle to the second (URI) row, opposite the copy
button — mirroring ProtocolEntry, whose toggle sits on the row below the
badges. The MIME badge stays on the header row's right, beside the name.
- MIME badge: bump to `size="md"` so the lowercase MIME text matches the point
size of ProtocolEntry's uppercase method/status badges, and make it a solid
dark-blue fill with white text in dark mode (keeping the blue-light tint in
light mode) instead of a washed-out translucent chip.
- Render the URI like ProtocolEntry/NetworkEntry render URLs — `sm` / `fw: 500`
in the default sans-serif face and text color — rather than a blue monospace
"link". `monoBreak` is kept only for long-URI wrapping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE
* web: scope result-card fill to resource-link results (review follow-up)
Code-review finding: ToolsScreen gave the result card `flex={1}`
unconditionally, so plain text/image/error results also filled the full pane
height — leaving a tall empty card and a height jump versus the input-form
state. Fill is only needed so a "Resource Links" box can grow and scroll.
Gate the fill on a new `resultHasResourceLinks(result)` helper (extracted to
`toolResultUtils.ts` so the component module keeps exporting only components,
per react-refresh/only-export-components). Non-link results keep their
content-sized card; link results still fill and scroll internally.
Tests: unit tests for the helper (link / text-only / empty / error) and a
ToolsScreen test that a resource-link result renders the Resource Links box
(covering the `flex={1}` branch).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE
* web: prettier-format the resultHasResourceLinks error-case test fixture
The error-case fixture in ToolResultPanel.test.tsx wasn't re-formatted after
adding the required `name` field, failing CI's `format:check`. No logic change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE
* web: cap a non-link block above the Resource Links box at 50% height
Addresses the mixed-content case from the review: a long text block preceding
the links would take its full natural height and push the Resource Links box
almost entirely off-screen (only the first card peeking at the bottom).
In the links-containing result, wrap each non-link block in a ScrollArea.Autosize
capped at `mah: 50%` (flex "0 1 auto"), so it scrolls within at most half the
card and the links box keeps the rest. A short block still takes only its
content height; a result with no links renders non-link blocks uncapped in the
outer scroll body as before.
Adds a `ResourceLinksWithLongText` story (full-height decorator) exercising the
cap. Verified visually: the text scrolls within ~50% and the links box fills
the remainder.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE
* web: address review — expand-toggle a11y, stale JSDoc, stronger fill test
Review follow-ups on PR #1686:
- ExpandToggle: add an optional `ariaLabel` (defaults to the tooltip verb, so
ProtocolEntry/NetworkEntry/Task are unchanged) and expose `aria-expanded`.
ResourceLink passes a per-resource label ("Expand/Collapse resource <uri>")
so multiple link toggles are distinguishable to assistive tech and announce
their state — restoring the a11y the card-wide button previously provided,
while keeping the visible tooltip a plain "Expand"/"Collapse".
- ResourceLinkInfo: fix the stale JSDoc that still described the URI as
"monospace, link-styled" — it now renders sans-serif sm/fw:500 default color.
- ToolsScreen test: assert the result card actually gets `flex: 1` for a
links result and NOT for a plain text result, so the test verifies the fill
branch it names rather than only that the heading renders.
Tests: ExpandToggle gains ariaLabel + aria-expanded cases; ResourceLink /
ToolResultPanel / stories updated for the per-resource toggle name.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE
* web: restore cli/tui/launcher package-locks accidentally committed
These lock files were touched by a local install in the worktree (dropping the
`version` field and adding optional @emnapi platform deps); they are unrelated
to this PR. Restore them to their v2/main state.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* 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>
* feat(web): expand ContentViewer to render PDF, CSV, HTML, XML, JSON, CSS (closes#1329)
Turn ContentViewer into a per-MIME dispatch surface for resource content.
It now accepts raw TextResourceContents | BlobResourceContents (in addition
to the existing ContentBlock path), so ResourcePreviewPanel.toContentBlock
collapses to a passthrough and blob-typed resources render natively instead
of falling back to opaque "[Binary content …]" text.
New renderers (clients/web/src/components/elements/):
- CodeHighlight: lazy react-syntax-highlighter prism-light element. Runtime,
tomorrow theme, and each grammar dynamic-import on first use (one chunk per
grammar). Module-level registeredLanguages / failedLoads / loadingPromises
caches; plain Code block until a grammar resolves.
- PdfFrame: iframe over a Blob URL (#view=FitH), revoked on unmount.
- CsvTable: papaparse -> Mantine Table (first 100 rows), plain-Code fallback.
- HtmlFrame: sandbox="" iframe over a Blob URL with an injected CSP meta
(no script-src, falls through to default-src 'none').
- contentViewerUtils: getMimeKind, base64 decode helpers, formatXml,
wrapHtmlWithCsp, SAFE_HREF markdown-anchor allowlist (unsafe href -> span).
ResourcePreviewPanel: extend the URI-suffix MIME inference table
(.csv/.json/.xml/.html/.htm/.css/.pdf alongside .md).
Deps: papaparse + react-syntax-highlighter (+ @types). tsconfig paths stubs
redirect the deep RSH imports for typecheck (the @types ambient declarations
are shadowed by the real .js under bundler resolution) — same pattern the
repo uses for react/pino.
Tests cover PDF blob-URL creation + cleanup, CSV table render + fallback,
HTML sandbox attributes + CSP injection, JSON/XML/CSS highlighted output,
the lazy-load/alias/failed-load/shared-runtime paths, and the URI-suffix
MIME-inference paths. validate, test:coverage (per-file gate), test:storybook,
and integration all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): harden ContentViewer blob/CSV rendering per review (#1551)
Address review feedback on the ContentViewer MIME-dispatch PR:
- useObjectUrl: fix a dev-only StrictMode bug where the committed object
URL was revoked but never recreated (setup→cleanup→setup with no
re-render), blanking PDF/HTML previews. Revocation is now deferred to a
microtask and guarded by a "live URL" ref so a StrictMode re-setup
cancels it, while a real unmount / blob change still releases the URL.
Avoids set-state-in-effect; mirrors AppRenderer's deferred-disposal.
- atob: guard base64 decoding with tryDecodeBase64ToUtf8 /
tryDecodeBase64ToBytes so a malformed blob from an external server
degrades to the binary-content notice instead of throwing during render
and taking down the whole preview panel.
- CsvTable: surface the 100-row display cap with a
"Showing first N of M rows" caption (no silent truncation).
- Extract BinaryNotice into its own module so PdfFrame can reuse it
without an import cycle.
Tests added for the StrictMode path, malformed-base64 fallbacks (PDF +
textual blob), the safe-decode helpers, and the truncation caption.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): keep server cards a fixed height (#1528)
Two causes made server cards ragged-height:
1. The command/URL box wrapped long content onto multiple lines, growing
the card. Add a `nowrap` Code variant (single line, ellipsis on
overflow) and a `wrap` prop on ContentViewer (default true); ServerCard
passes `wrap={false}`. The full value stays available via the copy
button.
2. At ~1000-1500px the grid packed cards narrow enough that a connected
card's action row (Clone/Edit/Remove + Connection Info/Settings) wrapped
and stacked. Switch the SimpleGrid to container queries with thresholds
that keep every card >= ~500px wide at each column count, so it drops to
fewer/wider columns instead of wrapping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): address review — correct threshold comment, single-line note, title tooltip
- Correct the column-threshold comment in ServerListScreen: the values
(1040/1560) use N gaps, not N−1; restate the formula accurately so the
numbers aren't "fixed" to match a wrong formula later.
- Note in the ContentViewer `wrap` JSDoc that `false` is for single-line
values only (nowrap collapses embedded newlines).
- Add a native `title` tooltip to the non-wrapping Code box so a clipped
command/URL is readable on hover, not only via copy. Covered by tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): expandable resource_link with read-on-demand (#1449)
Replace the placeholder `resource_link` rendering in ContentViewer with a
dedicated, Mantine-native `ResourceLink` element that mirrors V1's behavior:
- Shows uri (monospace, link-styled), optional name/description, and a
mimeType badge.
- Expand/collapse affordance (chevron + loading spinner) that is keyboard
accessible (native button: role, aria-expanded, aria-label, Enter/Space).
- On first expand, reads the linked resource on demand via an injected
callback and renders the full read result inline as formatted JSON under a
green "Resource:" label, capped to a max height with scroll for large
results. The fetched result is cached across collapse/expand.
The read handler is threaded as a "dumb component + callback as prop" through
ToolResultPanel → ToolsScreen → InspectorView, with App supplying a handler
that returns the `ReadResourceResult` from `inspectorClient.readResource`.
Also extract the shared `resourceContentsToBlock` helper into
`utils/resourceContents.ts` (previously private to ResourcePreviewPanel).
Tests: unit coverage for ResourceLink (collapsed/expanded/error/cache) and
the ContentViewer wiring, plus Storybook stories with play functions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): scroll large resource_link read results within the card
A plain ScrollArea with only `mah` does not scroll when its parent (the
content-sized card) has no definite height, so a large read result overflowed
the card instead of scrolling. Switch to ScrollArea.Autosize, which grows with
content up to the max height and then scrolls. Adds a LargeResult story
covering the bounded-scroll behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(web): make ResourceLink a group; extract ResourceLinkInfo element
ResourceLink is stateful (expand/loading/cached result) and fetches data via
a callback — group-like behavior, and the only `elements/` component using
useState. Move it to `groups/` and split out the pure-display metadata as a
new `elements/ResourceLinkInfo` element (uri/name/description/mime + an
optional action slot).
This keeps the layer dependencies clean (no element→group import, which
moving ResourceLink alone would have introduced):
- ContentViewer (element) renders ResourceLinkInfo (element) statically for
`resource_link` blocks and no longer takes an onReadResource prop.
- ToolResultPanel (group) renders the interactive ResourceLink (group) for
`resource_link` blocks, ContentViewer for the rest.
- ResourceLink (group) composes ResourceLinkInfo + ContentViewer.
Also reverts the now-single-consumer resourceContents.ts extraction, restoring
ResourcePreviewPanel's local toContentBlock.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): make ResourceLink read retryable and guard double-fetch
Address PR review: a failed read is no longer cached as terminal — only
a successful result is cached, so collapsing and re-expanding retries the
read after a transient failure. Add a loading guard so toggling while a
read is in flight does not fire a redundant second read.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(web): prettier formatting for ResourceLink test
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: remove accidentally-committed build artifacts; gitignore clients/*/build
A stray 'git add -A' swept 8 compiled build files into d25c9382. Remove
them from tracking and add clients/*/build to .gitignore so client build
output is never committed again.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Expose live resource subscriptions via a hook (#1325)
Add ResourceSubscriptionsState that mirrors InspectorClient's subscribed
URIs as InspectorResourceSubscription[], resolving each URI against the
managed resources list (so the subscription tile shows server-supplied
name/title) and stamping lastUpdated on notifications/resources/updated.
Pair it with useResourceSubscriptions and wire App.tsx so the Resources
screen reflects subscribe/unsubscribe actions in real time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address review notes on resourceSubscriptionsState
- Soften the onResourceUpdated comment: the client's dispatch is already
guarded by subscribedResources.has(uri), so the re-check is true
defense-in-depth rather than guarding a known hazard.
- Use this.getSubscriptions() in the statusChange handler so every emit
goes through the defensive-copy path.
- Document the deliberate fallback to a synthetic Resource when a
previously-listed resource is removed from the managed list while
the user is still subscribed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Reflect subscribed state in preview + compact subscribed tile labels
- Wire `isSubscribed` on the ReadResourceState passed to ResourcesScreen
by deriving it from the live subscriptions list in App.tsx. The
ResourcePreviewPanel's SubscribeButton already flips its label to
"Unsubscribe" when subscribed; without this derivation isSubscribed
was always false and the button looked stuck on "Subscribe".
- In ResourceSubscribedItem, display only the last URI path segment
(truncated with ellipsis if it still overflows) and surface the full
URI via a hover tooltip. Keeps the Subscriptions pleat readable when
resources have long path-style URIs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Auto-read resource on sidebar click
Clicking a resource in the URIs accordion now triggers onReadResource
in addition to setting the selection, so the preview panel jumps
straight to the pending → ok render path. Removes the unreachable
"Click to read this resource" placeholder, since the loader / preview /
error states always cover the rendered output for a selected resource.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Render markdown resources via react-markdown + fix preview overflow
- Add react-markdown / remark-gfm dependencies.
- ContentViewer accepts an optional mimeType prop; when it matches
text/markdown (or text/x-markdown), text blocks render via
react-markdown wrapped in a `.markdown-content` container. Non-markdown
text now uses the existing Code "wrapping" variant so long lines stay
inside the panel.
- ResourcePreviewPanel infers `text/markdown` from a `.md` / `.markdown`
URI suffix when the server didn't supply a mimeType, and threads the
effective mime through to ContentViewer per content item.
- ResourcesScreen flex children get miw=0 so a long unwrappable line in
the resource body can no longer push the preview past the viewport's
right edge.
- App.css gains a `.markdown-content` ruleset constraining nested pre,
table, code, and img elements to the container's width.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Contain resource preview: sticky header/footer, scrollable body
Restructure ResourcePreviewPanel into a fixed-height column with the
resource title + URI pinned to the top and the timestamp / annotations /
subscribe-refresh actions pinned to the bottom. The content viewer area
in the middle now owns its own ScrollArea, so a long markdown body
scrolls within the panel instead of pushing the subscribe button below
the viewport.
In ResourcesScreen, the selectedResource branch (and the template
branch's right pane) now hosts the panel inside a PreviewPane Flex
column with the screen's max-height, and renderReadState wraps in a
FillDetailCard sized to fill that column. The legacy outer
ScrollArea.Autosize is removed for these panes since scrolling is
internal now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Auto-hide template after read; size preview to content with cap
- After the user clicks Read Resource on the template form,
handleReadResource now clears selectedTemplateUri so the screen
swaps from the template form to the resource preview. Previously
both panels stayed mounted side-by-side.
- The preview no longer hard-fills the viewport. The Card uses a new
"preview" theme variant (overflow: hidden) and is content-sized,
capped at SCROLL_MAX_HEIGHT. ResourcePreviewPanel's flex column
marks the header / meta / footer rows as `flex: 0 0 auto` and the
ContentScroll as `flex: 0 1 auto` with `mih: 0`, so:
- short content → card hugs it, footer sits right under the body
- long content → card caps at viewport, ContentScroll shrinks
and scrolls internally, footer stays pinned at the cap
- ScrollArea / Group imports in ResourcesScreen pruned to match the
simpler single-pane layout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Cap template form panel at 40% width
A bare text input + Read Resource button stretched across the whole
main content area looks awkward on wide displays. Apply maw=40% to
the PreviewPane in the template branch so the form keeps a reasonable
form-field width regardless of viewport.
The preview branch is unaffected — resource bodies still get the full
main area for content.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Live completion/complete suggestions on resource template inputs
Wires the server's `completions` capability through to the resource
template form so each variable's input becomes an Autocomplete that
fires `completion/complete` (debounced, 300ms) on every keystroke and
surfaces the returned values as a dropdown — mirroring v1's behavior.
- core/mcp/inspectorClientProtocol.ts: surface getCompletions on the
protocol so non-runtime callers (state managers, hooks, tests) can
depend on it. FakeInspectorClient gains a vi.fn-backed stub.
- ResourceTemplatePanel: accepts onCompleteArgument + completionsSupported.
When both are present it renders Mantine Autocomplete instead of
TextInput, debounces keystrokes via per-arg timers, aborts in-flight
requests on the next keystroke, and disables client-side filtering
(the server already filtered for the typed prefix).
- ResourcesScreen / InspectorView: thread the props through; the
screen-level callback re-injects the active template's URI as the
`ref: "ref/resource"` so the panel-level callback stays ref-free.
- App.tsx: wires onCompleteArgument to inspectorClient.getCompletions
and derives completionsSupported from `capabilities?.completions`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 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>
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>