Skip to content

web: Group tool-result resource_links in a scrollable "Resource Links" box with Protocol colors#1686

Merged
cliffhall merged 10 commits into
v2/mainfrom
v2/resource-links-box
Jul 15, 2026
Merged

web: Group tool-result resource_links in a scrollable "Resource Links" box with Protocol colors#1686
cliffhall merged 10 commits into
v2/mainfrom
v2/resource-links-box

Conversation

@cliffhall

@cliffhall cliffhall commented Jul 14, 2026

Copy link
Copy Markdown
Member

Closes #1685

What

Tool-result resource_link blocks now render inside a labeled, full-height, internally-scrollable "Resource Links" box that closely matches the Protocol message cards (ProtocolEntry) in both light and dark modes.

Changes

Grouping & layout (ToolResultPanel, ToolsScreen)

  • Consecutive resource_link blocks are coalesced (via segmentContent) into one bordered Resource Links box with a pinned heading, mirroring the Messages box (ProtocolListPanel). Non-link blocks and non-adjacent runs are preserved in order.
  • When a result contains links, the result card fills the pane (gated by resultHasResourceLinks) and the box grows into the available height and scrolls internally. Plain text/image/error results keep their content-sized card (no tall empty card, no height jump vs the input form).
  • A non-link block sharing the card with the links box (e.g. a long text preamble) is capped at 50% of the available height and scrolls within, so it can't push the links box out of view; the box keeps the remaining space. A short block still takes only its content height.

Link card (ResourceLink, ResourceLinkInfo)

  • Inset surface — each card uses Card variant="inset" (--inspector-surface-inset), matching ProtocolEntry in light/dark; nested read-result Code blocks read as raised via the inset cascade variable.
  • Layout — name + MIME badge on the header row; URI + CopyButton on the row below, with the expand/collapse control at the end of that row (mirroring ProtocolEntry).
  • Expand/collapse — the same tooltip'd ExpandToggle as ProtocolEntry, with the read-result panel animated by Mantine's Collapse. The toggle and copy button are sibling real buttons (no card-wide wrapping button) so they never nest (axe nested-interactive).
  • MIME badge — sized to match the Protocol method/status badges; solid dark-blue fill + white text in dark mode, blue-light tint in light mode.
  • URI — rendered like ProtocolEntry/NetworkEntry URLs (sm / fw: 500, sans-serif, default color) instead of a blue monospace link.
  • Dropped misleading metadata — the server-generated description (e.g. a blob served as text/plain described as a "plaintext resource") and the redundant green "Resource:" label are removed.

Review

Ran a /code-review pass and addressed the substantive findings:

  • Result card filling height unconditionally (empty space for plain text/error results) → scoped the fill to link results via resultHasResourceLinks.
  • A long non-link block crowding out the links box → capped at 50% height with its own scroll.

Remaining note: the shared ExpandToggle's generic "Expand"/"Collapse" label (no aria-expanded) matches ProtocolEntry by request.

Screenshots

resource-links-dark resource-links-light

Tests & stories

  • Updated ResourceLinkInfo, ResourceLink, ContentViewer, ToolResultPanel, and ToolsScreen tests/stories for the new layout, the Expand/Collapse toggle, the mounted-through-collapse read result, the grouped full-height box, and the 50%-capped text (ResourceLinksWithLongText story).
  • Added coverage: resultHasResourceLinks (link/text/empty/error), the grouped box (single / consecutive / split runs), and the ToolsScreen fill branch.
  • test:coverage (unit + integration) passes the per-file ≥90 gate; Storybook play + axe a11y checks pass in headless chromium. Verified visually in light and dark against ProtocolEntry.

🤖 Generated with Claude Code

…" 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
@cliffhall cliffhall added the v2 Issues and PRs for v2 label Jul 14, 2026
cliffhall and others added 4 commits July 14, 2026 13:42
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
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
…y 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
… 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
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
cliffhall and others added 2 commits July 14, 2026 15:40
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
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
@cliffhall

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cliffhall's task in 2m 22s —— View job


Review — PR #1686 · Resource Links box

  • Read the diff against origin/v2/main
  • Review layout/grouping logic (ToolResultPanel, ToolsScreen)
  • Review ResourceLink / ResourceLinkInfo changes
  • Check tests & stories
  • Post review feedback

Overall this is a clean, well-commented change. The segmentContent coalescing is correct and order-preserving, the hasLinks gate is applied consistently across ToolResultPanel and ToolsScreen, the nested-interactive fix (sibling copy + toggle buttons, no card-wide wrapping button) is right, and coverage for the new util and grouping branches is solid. A few notes below — none blocking.


1. Stale JSDoc: URI is no longer "monospace, link-styled" (minor, doc)

ResourceLinkInfo.tsx:88 still documents the old rendering:

 * badge on the header row, then the URI (monospace, link-styled) on the line

But the code (and the accurate inline comment at line 52) now renders the URI as sm / fw: 500 sans-serif default color — variant="monoBreak" only adds word-break: break-all (confirmed in theme/Text.ts), it is not monospace, and the blue c/ff: "monospace" were removed. The docstring contradicts the implementation.

Fix this →

2. Loss of the URI from the expand control's accessible name (a11y consideration)

The old control had aria-label="Expand resource demo://r/1"; the shared ExpandToggle uses a generic "Expand"/"Collapse" label with no aria-expanded. Inside a Resource Links box with many links, every toggle now has the identical accessible name, so a screen-reader user can't tell them apart — more impactful here than in ProtocolEntry (where each row is a distinct message). The PR notes this is intentional ("matches ProtocolEntry by request"), so flagging only as a consideration — a per-entry ExpandToggle label prop (defaulting to the current text) would restore distinguishability without diverging visually.

3. Dropping description entirely (product consideration)

description is removed from ResourceLinkInfo, ResourceLink, and the ContentViewer call site. The motivation (a misleading server-generated description in the test server) is understood, but resource_link.description is a spec field some servers populate usefully. Consider whether truncating/de-emphasizing it would have been preferable to dropping it — not a blocker, just noting the tradeoff.

4. Per-block 50% cap, not shared (edge case)

NonLinkCap applies mah: "50%" per non-link block. For content like [long text, resource_link, long text], both text blocks can each claim up to 50%, summing to ~100% and squeezing the flex: 1 links box toward its mih: 0. The common single-preamble case is fine; just noting the cap isn't a combined budget across multiple non-link siblings.

5. Weak assertion in the new ToolsScreen fill test (minor, test)

ToolsScreen.test.tsx "fills the result card … when the result has links" asserts only that the Resource Links heading renders — which is true in both the flex={1} and the flex={undefined} branches, so it doesn't actually distinguish the fill branch it documents. The branch is covered for the gate, but the assertion doesn't verify the behavior it names. (resultHasResourceLinks itself is well unit-tested, so this is cosmetic.)


Nice work matching ProtocolEntry — the inset surface, badge sizing, and Collapse animation all line up. Items above are polish; #1 is the only clear correctness (of documentation) fix.
· v2/resource-links-box

cliffhall and others added 2 commits July 15, 2026 10:58
…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
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
@cliffhall

Copy link
Copy Markdown
Member Author

Thanks for the review — addressed in af129e84 (plus 9c4d6796 restoring three package-locks a local install had accidentally touched).

1. Stale JSDoc (fixed). Updated the ResourceLinkInfo docstring — the URI now reads "styled like ProtocolEntry/NetworkEntry URLs (sm / fw: 500, default sans-serif face and color), not a blue monospace link," matching the implementation and the inline comment.

2. Expand-toggle accessible name / aria-expanded (fixed). Took your suggestion: ExpandToggle now accepts an optional ariaLabel (defaulting to the tooltip verb, so ProtocolEntry/NetworkEntry/Task are unchanged) and always exposes aria-expanded. ResourceLink passes a per-resource label (Expand/Collapse resource <uri>), so multiple toggles are distinguishable and announce their state, while the visible tooltip stays the plain verb. Added ExpandToggle tests for both.

3. Dropping description (working as intended). This was an explicit maintainer decision in the thread that produced this PR: the field is frequently mis-populated (e.g. a blob served with text/plain gets described as a "plaintext resource"), so we chose to drop it rather than surface misleading text. Happy to revisit as a follow-up if we want a truncated/de-emphasized treatment for servers that populate it well — noting it's a deliberate product call here, not an oversight.

4. Per-block 50% cap (accepted edge case). Correct that [long text, link, long text] lets each text claim up to 50%. The requested behavior was specifically "the text field above the links ≤ 50%" (the single-preamble case), which this satisfies; a shared budget across interleaved non-link siblings is meaningfully more complex for a case we don't expect in practice. Left as-is intentionally; can revisit if it shows up.

5. Weak ToolsScreen fill-test assertion (fixed). The fill test now asserts the result card actually carries flex: 1, and the plain-text test asserts it does not — so the pair verifies the fill branch rather than only that the heading renders.

validate + test:coverage + Storybook (incl. axe) all green locally.

@cliffhall

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cliffhall's task in 1m 56s —— View job


Re-review — PR #1686 · Resource Links box

  • Read the diff against origin/v2/main
  • Verify the fixes from the prior review (JSDoc, ExpandToggle a11y, fill-test assertions)
  • Review ExpandToggle / ResourceLink a11y changes
  • Review layout/grouping logic
  • Post review feedback

All follow-ups from the earlier review landed cleanly, and I don't see any new issues. Confirming each fix against the current tree:

1. Stale JSDoc (fixed). ResourceLinkInfo.tsx:86-93 now describes the URI as "styled like ProtocolEntry/NetworkEntry URLs (sm / fw: 500, default sans-serif face and color), not a blue monospace link" — matches the implementation and the inline comment at line 51.

2. ExpandToggle accessible name + aria-expanded (fixed, well done). ExpandToggle.tsx now takes an optional ariaLabel (defaulting to the tooltip verb, so ProtocolEntry/NetworkEntry/Task are unchanged) and always emits aria-expanded={expanded}. ResourceLink.tsx:103-109 passes a per-resource label (Expand/Collapse resource <uri>), so sibling toggles in a densely-populated Resource Links box are distinguishable and announce their disclosure state while the visible tooltip stays the plain verb. New ExpandToggle tests cover the default label, the ariaLabel override, and both aria-expanded states — good coverage of the new branch.

3. description dropped (accepted product call). Noted as a deliberate maintainer decision; ContentViewer.tsx and the story drop the prop consistently. No concern.

4. Per-block 50% cap (accepted edge case). Left as-is; the single-preamble case is satisfied and the comment at ToolResultPanel.tsx:101-105 documents the per-block (not combined) semantics. Fine.

5. ToolsScreen fill-test assertion (fixed). The pair now actually distinguishes the branch: the links test asserts the result card carries flex: "1" (ToolsScreen.test.tsx:203-204) and the plain-text test asserts it does not (:178-179). This verifies the behavior it names rather than just that the heading renders.

I also re-checked the surrounding logic for regressions from the a11y change:

  • resultHasResourceLinks is computed independently in ToolsScreen (drives flex) and ToolResultPanel (drives hasLinks); both share the same helper so the fill decision and the layout branch stay consistent — including the error case (isError → helper returns false and the panel takes the error-alert branch).
  • Adding aria-expanded to the shared ExpandToggle is a pure improvement for the existing ProtocolEntry/Network/Task callers (they're always expandable), no regression.

Nothing blocking — this is good to go from my side. Nice, thorough follow-up.
· v2/resource-links-box

@cliffhall cliffhall merged commit 8f70b60 into v2/main Jul 15, 2026
3 checks passed
@cliffhall cliffhall deleted the v2/resource-links-box branch July 15, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

web: Group tool-result resource_links in a scrollable "Resource Links" box, styled like other list entry cards

1 participant