Skip to content

feat: Agents list - #109

Open
gcgoncalves wants to merge 2 commits into
mainfrom
a2a-list
Open

feat: Agents list#109
gcgoncalves wants to merge 2 commits into
mainfrom
a2a-list

Conversation

@gcgoncalves

Copy link
Copy Markdown
Contributor

Relates to IBM/mcp-context-forge#5843

Screenshot 2026-09-07 at 16 16 03 Screenshot 2026-09-07 at 16 15 30

Raw for now to keep PRs small, to be incremented slowly.

@marekdano marekdano left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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, +N badge for remainder
  • include_inactive=true query param sent to /a2a
  • null items 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.

Signed-off-by: Gabriel Costa <gabrielcg@proton.me>
@gcgoncalves

Copy link
Copy Markdown
Contributor Author

@marekdano addressed!

@marekdano marekdano left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 by agent.id, which A2AAgentRead.id types as string | null | undefined. Same latent pattern as Resources.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>
@gcgoncalves

Copy link
Copy Markdown
Contributor Author

@marekdano

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 marekdano left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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."

@a-effort a-effort Sep 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants