feat(web): expandable resource_link with read-on-demand (#1449)#1456
Conversation
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>
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>
|
@claude review |
|
Claude finished @cliffhall's task in 2m 6s —— View job Claude Code ReviewNice work — this is a clean, well-tested port. The dumb-component + callbacks-as-props pattern is followed correctly, the prop is threaded properly through A few things worth considering before merge — none are blockers. 1. A failed read is cached forever — no retry path
// toggle(): re-read on re-expand if the prior attempt errored
if (result !== null) return; // only the successful result is cached
setError(null);
setLoading(true);2. Possible double-fetch if the user toggles mid-loadIn 3. Circular import between
|
…ement 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>
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>
|
Thanks for the thorough review! Addressed in d25c938: #1 — Failed read cached forever (fixed). #2 — Double-fetch on rapid toggle (fixed). Added an #3 — Circular import (already resolved). This was fixed by the component reorg: #4 — Extracted helper / rich media previews (intentional, follow-up noted). Dumping the full #5 — Color tokens (noted). Agreed these are Mantine named colors (no literal-hex violation) and
|
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@claude review |
|
Claude finished @cliffhall's task in 1m 17s —— View job Claude Code Review (round 2)I re-reviewed against
🔴 Blocker: committed build artifacts8 compiled/build files were accidentally added in These don't exist on ✅ Prior review items — all resolved#1 Retryable failed read — #2 Double-fetch guard — #3 Circular import — resolved by the reorg. Minor (non-blocking)
VerdictSource code, tests, and the element/group refactor are all in good shape and ready. Just strip the build artifacts (and ideally gitignore them) so the diff reflects the actual change, and this is good to merge. |
…ts/*/build A stray 'git add -A' swept 8 compiled build files into d25c938. 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>
|
Good catch on the blocker — fixed in 799637c. 🔴 Build artifacts (fixed). A stray The minor notes (Code padding inside the scroll area, render-time @claude review |
|
Claude finished @cliffhall's task in 34s —— View job Claude Code Review (round 3)Re-reviewed the build-artifact fix in
✅ Blocker resolvedBuild artifacts stripped.
dist
+clients/*/build
test-servers/buildSo a stray Diff is now honest. The PR is back to 728 insertions / 17 deletions across 15 files — all of it the real Verdict🟢 Ready to merge. All prior items are resolved:
The remaining minor notes ( |
Closes #1449
Summary
Replaces V2's placeholder
resource_linkrendering with a dedicated, Mantine-nativeResourceLinkelement that brings V1's interactive, expandable, click-to-read experience to V2.Behavior
role,aria-expanded,aria-label, and Enter/Space handling.The matching V1 layout (uri / name / description / mime badge / green "Resource:" label / scrollable JSON):
Wiring
Follows the V2 "dumb component + callbacks as props" pattern. The read handler is threaded
App → InspectorView → ToolsScreen → ToolResultPanel → ContentViewer → ResourceLink.Appsupplies a handler returning theReadResourceResultfrominspectorClient.readResource(uri).Refactor
Extracts the shared
resourceContentsToBlockhelper intoutils/resourceContents.ts(previously private toResourcePreviewPanel).Testing
ResourceLink(collapsed/expanded/error/cache/non-interactive),resourceContents, and theContentViewerresource_link wiring.playfunctions forResourceLinkandContentViewer.npm run validate,npm run test:integration(522), andnpm run test:storybook(345) all pass.🤖 Generated with Claude Code