feat(resources): render annotations on ResourceListItem (#1249)

* feat(resources): render annotations on ResourceListItem (#1238)

Half-finished Phase 5 spec target: ResourceListItem already used
title ?? name but ignored resource.annotations entirely. Now renders
audience and priority via the existing AnnotationBadge element, matching
how ResourcePreviewPanel surfaces the same fields.

Layout: a wrapping Group keeps the title + badges on a single line when
the row is wide, and lets badges drop below when narrow. When neither
annotation is present, the row renders the bare Text (no Group, no
leaked spacing).

Adds stories for audience-only, priority-only, both, and a
ResourceTemplate with annotations.

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

* Address PR #1249 review feedback

- Inline the audience/priority narrowing checks instead of routing
  through aliased booleans, matching ResourcePreviewPanel's pattern
  and avoiding reliance on TS aliased-condition narrowing for the
  inner badges.
- Use a distinct resource name (settings.json) for WithAudience so
  the Storybook canvas reads more clearly across the four annotation
  stories.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-04-27 17:23:32 -04:00 • committed by GitHub
commit f1d6fbd704
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 79 additions and 2 deletions

View file

@ -53,3 +53,48 @@ export const Template: Story = {
selected: false,
},
};
export const WithAudience: Story = {
args: {
resource: {
name: "settings.json",
uri: "file:///settings.json",
annotations: { audience: ["user"] },
},
selected: false,
},
};
export const WithPriority: Story = {
args: {
resource: {
name: "schema.sql",
uri: "file:///schema.sql",
annotations: { priority: 0.8 },
},
selected: false,
},
};
export const WithAudienceAndPriority: Story = {
args: {
resource: {
name: "config.json",
title: "Configuration File",
uri: "file:///config.json",
annotations: { audience: ["user", "assistant"], priority: 0.5 },
},
selected: false,
},
};
export const TemplateWithAnnotations: Story = {
args: {
resource: {
name: "User Profile",
uriTemplate: "file:///users/{userId}/profile",
annotations: { audience: ["assistant"], priority: 0.2 },
},
selected: false,
},
};

View file

@ -1,8 +1,9 @@
import { Text, UnstyledButton } from "@mantine/core";
import { Group, Text, UnstyledButton } from "@mantine/core";
import type {
Resource,
ResourceTemplate,
} from "@modelcontextprotocol/sdk/types.js";
import { AnnotationBadge } from "../../elements/AnnotationBadge/AnnotationBadge";
export interface ResourceListItemProps {
resource: Resource | ResourceTemplate;
@ -10,11 +11,28 @@ export interface ResourceListItemProps {
onClick: () => void;
}
const RowGroup = Group.withProps({
gap: "xs",
wrap: "wrap",
justify: "space-between",
});
const BadgeGroup = Group.withProps({
gap: "xs",
wrap: "wrap",
});
export function ResourceListItem({
resource,
selected,
onClick,
}: ResourceListItemProps) {
const audience = resource.annotations?.audience;
const priority = resource.annotations?.priority;
const showBadges =
(audience !== undefined && audience.length > 0) || priority !== undefined;
const label = <Text fw={500}>{resource.title ?? resource.name}</Text>;
return (
<UnstyledButton
w="100%"
@ -23,7 +41,21 @@ export function ResourceListItem({
bg={selected ? "var(--mantine-primary-color-light)" : undefined}
onClick={onClick}
>
<Text fw={500}>{resource.title ?? resource.name}</Text>
{showBadges ? (
<RowGroup>
{label}
<BadgeGroup>
{audience !== undefined && audience.length > 0 && (
<AnnotationBadge facet="audience" value={audience} />
)}
{priority !== undefined && (
<AnnotationBadge facet="priority" value={priority} />
)}
</BadgeGroup>
</RowGroup>
) : (
label
)}
</UnstyledButton>
);
}