feat: Agents list - #109
Conversation
marekdano
left a comment
There was a problem hiding this comment.
🔴 Must Fix
[T1] No Agents.test.tsx — all page-level states are untested
Every comparable page has a full *.test.tsx sibling with MSW-backed integration tests.
The following scenarios have zero coverage:
- Loading spinner renders with correct ARIA attributes
- Error banner renders on API failure
- Empty state renders when API returns
[] - Cards render with agent name, status dot, description, tags
- Active vs inactive status dot
aria-label - Tags overflow: first 8 shown,
+Nbadge for remainder include_inactive=truequery param sent to/a2anullitems in the API response are filtered out
The PR note "raw for now" is acceptable for features but not for loading/error/empty states — those are stable and straightforward to cover now alongside the implementation.
[T2] No global MSW handler for /api/a2a
src/test/mocks/handlers.ts has baseline handlers for /api/tools, /api/gateways, /api/logs/activity etc., but nothing for /api/a2a. Any test that renders <Agents /> without a local server.use(...) override will hit an unhandled request. Depending on MSW's unhandled request mode this silently puts the component into an error state, making the existing App.test.tsx smoke test fragile.
Add a baseline handler to handlers.ts:
http.get("*/api/a2a", () => HttpResponse.json([])),[S1] Agent type exposes credential fields within AgentCard scope
A2AAgentRead includes authToken, authPassword, authHeaderValue, authQueryParamValueMasked, authValue, and authUsername. The entire object is passed as the agent prop into AgentCard. Nothing is rendered today, but one accidental {agent.authToken} in a future diff silently leaks secrets to the UI with no type error to catch it.
Destructure only the fields actually used in the card, or narrow the prop type to exclude credential fields. At minimum add a warning comment to src/types/agent.ts flagging these fields as never-render.
🟠 Should Fix
[S2] Raw error.message rendered to the user
<p className="text-red-800 dark:text-red-200">{error.message}</p>QueryError.message flows directly from err.message in useQuery.ts, which comes from the API client. A backend 500 containing something like "psycopg2.OperationalError: could not connect to host 'db-prod-1'" would be shown verbatim to the user. Show a static fallback for 5xx or unknown errors and only surface error.message for 4xx client errors where the backend message is intentionally user-facing.
[A2] AgentIcon SVG is missing aria-hidden
<AgentIcon className="h-3.5 w-3.5 text-black" />The hand-rolled SVG in AgentIcon.tsx has no aria-hidden="true". Lucide icons used everywhere else (e.g. <Wrench> in Tools.tsx) get aria-hidden automatically from the library. Screen readers may attempt to announce this decorative icon. Fix by forwarding props in the component and marking it hidden at the call site:
// AgentIcon.tsx
export const AgentIcon = ({ className, ...props }: React.SVGProps<SVGSVGElement>) => (
<svg ... className={className} {...props}>// Agents.tsx
<AgentIcon className="h-3.5 w-3.5 text-black" aria-hidden="true" />[Q3] Grid renders even when error is truthy and stale data is present
{!isLoading && !error && agents.length === 0 && (/* empty state */)}
{!isLoading && agents.length > 0 && (/* grid */)}The empty-state guard checks !error but the grid guard does not. If a refetch button is ever added and a retry fails while stale data is still in state, both the error banner and the grid would render simultaneously. Add !error to the grid condition:
{!isLoading && !error && agents.length > 0 && (/* grid */)}[Q1] Heading style diverges from every other page
The PR uses:
<Typography variant="heading3" as="h1" className="mb-6">Every other page (Tools, Servers, Prompts, Resources) uses:
<h1 className="mb-6 text-base font-semibold text-foreground">heading3 in typography.tsx resolves to font-heading text-base font-semibold text-foreground — visually nearly identical but inconsistent. Either align with the existing pattern here, or raise a separate PR to migrate all page headings to Typography.
🟡 Nice to Fix
[A3] Error title is an <h3> causing a skipped heading level
<h3 className="mb-1 font-semibold">Error loading agents</h3>The page has an <h1> then jumps straight to <h3> with no <h2> in between. Screen reader users navigating by heading will encounter a structural gap. Use <p> or <Typography variant="heading3" as="p"> for the error title — it is a label, not a structural heading.
[T3] Anchored regex in App.test.tsx needs a clarifying comment
// Before:
["/app/agents", /agents/i],
// After:
["/app/agents", /^agents$/i],The regex was tightened because the loaded page now renders additional text containing the word "agents". Without a comment the next developer will not know why the anchor was added and may loosen it again.
[Q2] xl:grid-cols-2 is a redundant Tailwind class
className="grid grid-cols-1 gap-6 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-3"xl:grid-cols-2 has no effect because lg:grid-cols-2 already sets 2 columns and Tailwind breakpoints cascade upward. Remove it. The same nit exists in Tools.tsx.
[Q4] MAX_VISIBLE_TAGS placement is inconsistent with Tools.tsx
Tools.tsx defines MAX_VISIBLE_TOOLS = 8 inside the component function where it is used. The new code puts MAX_VISIBLE_TAGS = 8 at module scope. Move it into the AgentCard function body to match the established pattern.
22f57d8 to
cff0e3a
Compare
Signed-off-by: Gabriel Costa <gabrielcg@proton.me>
cff0e3a to
bafbb25
Compare
|
@marekdano addressed! |
marekdano
left a comment
There was a problem hiding this comment.
Follow-up review — a2a-list @ latest commit
Nice work addressing the Must Fix and Nice-to-Fix items — tests, MSW handler, credential-field narrowing, aria-hidden, the !error grid guard, and the heading-level fix all look solid.
Two items from the original Should Fix list remain open:
[S2] Raw error.message still rendered verbatim (src/pages/Agents.tsx:117)
<p className="text-red-800 dark:text-red-200">{error.message}</p>Not fixed — a 5xx or unexpected backend error string would still surface verbatim to the user. That said, I checked and this exact pattern exists in Tools.tsx, Prompts.tsx, Resources.tsx, and Servers.tsx too, so it's pre-existing tech debt across the app rather than something new here. Not blocking on this PR — worth a follow-up issue to fix app-wide (e.g. gate on error.status in the 400–499 range before showing error.message, static fallback otherwise) rather than patching just this page.
[Q1] Heading style still diverges from other pages (src/pages/Agents.tsx:92)
<Typography variant="heading3" as="h1" className="mb-6">Every other page (Tools, Servers, Prompts, Resources) uses the plain <h1 className="mb-6 text-base font-semibold text-foreground"> pattern instead of Typography. Visually identical, just inconsistent — still a nit, not blocking.
Two small new nits from a fresh pass
src/pages/Agents.tsx:64— tag chips are keyed by label text (key={tag}) with no dedupe; a repeated tag label on an agent would produce a React key collision.src/pages/Agents.tsx:136— cards are keyed byagent.id, whichA2AAgentRead.idtypes asstring | null | undefined. Same latent pattern asResources.tsx, low risk in practice.
Given both open Should-Fix items are pre-existing patterns elsewhere in the codebase and not regressions, I'd approve rather than request changes again - happy to file a follow-up issue for the app-wide error-message handling if useful.
Two nits from the follow-up review pass:
- Tag chips were keyed by label text alone (key={tag}), colliding if
an agent carries a repeated tag label. Key on label+index instead.
- Cards were keyed by agent.id, which A2AAgentRead types as
string | null | undefined. Fall back to index so a null/missing id
can't collide with another card.
Leaving open per review: the Typography heading (Q1) is intentional —
Typography rolls out to the rest of the pages in a follow-up PR, so
Agents.tsx already matches the target state rather than the old
plain-<h1> pattern. Raw error.message rendering (S2) is pre-existing
across Tools/Prompts/Resources/Servers too; fixing it only here would
just add a new inconsistency, so it's left for an app-wide follow-up.
Signed-off-by: Gabriel Costa <gabrielcg@proton.me>
|
About the typography component: will be applied to the rest of the pages on a follow-up PR. The rest of the items are now addressed. |
marekdano
left a comment
There was a problem hiding this comment.
Confirmed — both nits from the last pass are fixed cleanly (tag key now includes index, card key falls back to index for a null/missing agent.id), and nothing else changed in this commit. Approving.
| "agents.card.status.active": "Active", | ||
| "agents.card.status.inactive": "Inactive", | ||
| "agents.empty.title": "No agents yet", | ||
| "agents.empty.description": "Agents will appear here once they're registered with the gateway." |
There was a problem hiding this comment.
Please change "gateway" to "server" (moving away from using that terminology other than the product being an AI Gateway). Request applies to all copy (i18N)
Relates to IBM/mcp-context-forge#5843
Raw for now to keep PRs small, to be incremented slowly.