* 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>
118 lines
3.7 KiB
TypeScript
118 lines
3.7 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import { Badge, Group, Stack, Text } from "@mantine/core";
|
|
import { CopyButton } from "../CopyButton/CopyButton";
|
|
|
|
export interface ResourceLinkInfoProps {
|
|
/** The linked resource's URI (always shown, with a copy button). */
|
|
uri: string;
|
|
/** Optional human-friendly name shown above the URI. */
|
|
name?: string;
|
|
/** Optional MIME type shown as a badge. */
|
|
mimeType?: string;
|
|
/**
|
|
* Optional trailing element placed at the end of the URI row (opposite the
|
|
* copy button) — e.g. an expand/collapse control supplied by an interactive
|
|
* wrapper. Mirrors ProtocolEntry, whose toggle sits on the row below the
|
|
* badges.
|
|
*/
|
|
action?: ReactNode;
|
|
}
|
|
|
|
const HeaderStack = Stack.withProps({
|
|
gap: 4,
|
|
});
|
|
|
|
// Name (left) + MIME badge (right). `justify` is set per-instance: spread when
|
|
// a name is present, otherwise the badge hugs the right.
|
|
const HeaderRow = Group.withProps({
|
|
wrap: "nowrap",
|
|
gap: "xs",
|
|
align: "center",
|
|
});
|
|
|
|
// Copy control + URI (left) and the optional expand/collapse control (right),
|
|
// on the line below the header — mirroring ProtocolEntry's controls row.
|
|
const UriRow = Group.withProps({
|
|
justify: "space-between",
|
|
wrap: "nowrap",
|
|
gap: "xs",
|
|
align: "center",
|
|
});
|
|
|
|
// Copy button + URI cluster; flexes so the URI fills and the action stays right.
|
|
const UriCluster = Group.withProps({
|
|
wrap: "nowrap",
|
|
gap: "xs",
|
|
align: "center",
|
|
flex: 1,
|
|
miw: 0,
|
|
});
|
|
|
|
// Match how ProtocolEntry/NetworkEntry render a URL: `sm` / `fw: 500`, in the
|
|
// default sans-serif face and text color (not a blue monospace "link"). The
|
|
// `monoBreak` variant only adds `word-break: break-all` so a long URI wraps
|
|
// within the card instead of overflowing.
|
|
const UriText = Text.withProps({
|
|
size: "sm",
|
|
fw: 500,
|
|
variant: "monoBreak",
|
|
flex: 1,
|
|
miw: 0,
|
|
});
|
|
|
|
const MimeBadge = Badge.withProps({
|
|
// Match the point size of the ProtocolEntry method/status badges; the
|
|
// lowercase MIME text reads smaller than their uppercase labels at `sm`.
|
|
size: "md",
|
|
radius: "sm",
|
|
// MIME types are conventionally lowercase; keep them as-is rather than
|
|
// letting Badge's default uppercase transform mangle them.
|
|
tt: "none",
|
|
autoContrast: false,
|
|
// Light mode: the tinted blue-light chip (unchanged). Dark mode: a solid
|
|
// dark-blue fill with white text — matching the solid ProtocolEntry badges
|
|
// rather than a washed-out translucent tint.
|
|
bg: "light-dark(var(--mantine-color-blue-light), var(--mantine-color-blue-9))",
|
|
c: "light-dark(var(--mantine-color-blue-light-color), var(--mantine-color-white))",
|
|
});
|
|
|
|
const NameText = Text.withProps({
|
|
size: "sm",
|
|
fw: 600,
|
|
flex: 1,
|
|
miw: 0,
|
|
});
|
|
|
|
/**
|
|
* Pure-display metadata for a `resource_link`: an optional name and MIME-type
|
|
* badge on the header row, then the URI on the line below with a copy button.
|
|
* The URI is styled like ProtocolEntry/NetworkEntry URLs (`sm` / `fw: 500`,
|
|
* default sans-serif face and color), not a blue monospace link. The optional
|
|
* `action` slot lets an interactive wrapper (e.g. {@link ResourceLink}) place
|
|
* an expand/collapse control at the end of the URI row.
|
|
*/
|
|
export function ResourceLinkInfo({
|
|
uri,
|
|
name,
|
|
mimeType,
|
|
action,
|
|
}: ResourceLinkInfoProps) {
|
|
const hasHeader = Boolean(name || mimeType);
|
|
return (
|
|
<HeaderStack>
|
|
{hasHeader && (
|
|
<HeaderRow justify={name ? "space-between" : "flex-end"}>
|
|
{name && <NameText>{name}</NameText>}
|
|
{mimeType && <MimeBadge>{mimeType}</MimeBadge>}
|
|
</HeaderRow>
|
|
)}
|
|
<UriRow>
|
|
<UriCluster>
|
|
<CopyButton value={uri} />
|
|
<UriText>{uri}</UriText>
|
|
</UriCluster>
|
|
{action}
|
|
</UriRow>
|
|
</HeaderStack>
|
|
);
|
|
}
|