Skip to content

feat(ui): show agent count numbers on filter tabs#1918

Open
bluzername wants to merge 2 commits intopaperclipai:masterfrom
bluzername:fix/agent-tab-count-badges
Open

feat(ui): show agent count numbers on filter tabs#1918
bluzername wants to merge 2 commits intopaperclipai:masterfrom
bluzername:fix/agent-tab-count-badges

Conversation

@bluzername
Copy link
Copy Markdown

@bluzername bluzername commented Mar 27, 2026

Thinking Path

  • Paperclip orchestrates ai-agents for zero-human companies
  • But humans want to watch the agents and oversee their work
  • The Agents page has filter tabs (All, Active, Paused, Error) so human can quickly see distribution of agent states
  • This PR adds count numbers to those tabs so user can see at glance how many agents in each state
  • But first version had a bug: the counts was computed with manual status checks separate from the matchesFilter function
  • The matchesFilter function includes terminated agents in every tab when "Show terminated" toggle is on
  • The manual counts did not account for this, so when toggle was enabled, tab numbers showed less than actual list length
  • This fix makes counts go through same matchesFilter logic, so numbers always match what user sees
  • Also wrapped counts in useMemo for consistency with other memoized derived data in this component

Problem

On the Agents page, the filter tabs at the top show "All", "Active", "Paused", "Error" but with no numbers. If I have 20 agents and want to know how many are in error state, I have to click the "Error" tab and count manually (or look at the list length). Same for active and paused.

This is a very basic UX pattern that every list page should have - showing the count next to each filter option so user can scan the distribution at a glance.

What I changed

Computed counts for each status category using the existing matchesFilter function (same logic that drives filtered lists):

  • All: non-terminated agents (plus terminated when showTerminated is on)
  • Active: agents with status active, running, or idle (plus terminated when toggle on)
  • Paused: agents with status paused (plus terminated when toggle on)
  • Error: agents with status error (plus terminated when toggle on)

Then show them in the tab labels as parenthesized numbers: "Active (5)", "Error (2)", etc.

Counts are wrapped in useMemo with [agents, showTerminated] deps, consistent with other memoized data in this component (liveRunByAgent, agentMap).

How to test

  1. Go to Agents page
  2. Tab labels should now show counts like "All (12)", "Active (8)", "Paused (2)", "Error (2)"
  3. The numbers should match the actual list contents when you click each tab
  4. Enable "Show terminated" toggle - counts should increase to include terminated agents
  5. Disable toggle again - counts should go back to previous numbers

Review feedback addressed

  • P1: Counts now use matchesFilter instead of manual status checks, so they respect showTerminated state
  • P2: Counts wrapped in useMemo to avoid 4x array iteration on every render

1 file changed, 9 insertions(+), 7 deletions(-) (net +2 lines).

The Agents page filter tabs was showing just "All", "Active",
"Paused", "Error" without any indication of how many agents
are in each category. User had to click each tab to find out.

Now tabs show counts in parentheses like "Active (5)" or
"Error (2)". This let user see at a glance how their agents
are distributed across statuses without clicking anything.

The counts use the same filter logic already in the component
(matchesFilter function) so they stay consistent with what
the filtered list actually show.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 27, 2026

Greptile Summary

This PR adds agent-count badges to the filter tabs on the Agents page (e.g. "Active (5)", "Error (2)"), which is a useful quality-of-life improvement. The approach is straightforward but has one correctness gap and a process gap noted below.

  • Count/filter mismatch with "Show terminated": The new counts object filters agents independently of the showTerminated state, while matchesFilter (used for the actual lists) correctly includes terminated agents in every tab when the toggle is on. As a result, all four count badges will under-count compared to the actual rendered list whenever a user has "Show terminated" enabled. The fix is to compute counts through matchesFilter rather than hand-rolling the status checks.
  • Memoization: Minor — the four .filter() passes could be wrapped in useMemo([agents, showTerminated]) for consistency with the rest of the component.
  • PR description missing thinking path and screenshots: Per CONTRIBUTING.md, PRs with UI changes should include a "thinking path" and before/after screenshots. The description explains what changed well, but does not include the top-down thinking path or any screenshots showing the new tab labels.

Confidence Score: 4/5

Safe to merge after fixing the showTerminated count mismatch; the bug only affects users who use the 'Show terminated' toggle, but it does produce misleading count numbers in that path.

One P1 logic bug: counts bypass matchesFilter and will disagree with the actual list length when the 'Show terminated' toggle is on. Needs a one-line fix before merge.

ui/src/pages/Agents.tsx — the new counts block at lines 134–140

Important Files Changed

Filename Overview
ui/src/pages/Agents.tsx Adds per-tab agent counts to tab labels; counts diverge from actual list lengths when the "Show terminated" toggle is active because they bypass the existing matchesFilter logic.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/pages/Agents.tsx
Line: 135-140

Comment:
**Counts ignore `showTerminated` state**

The new `counts` object computes values independently of the `showTerminated` state flag, but the existing `matchesFilter` function — which drives the actual filtered lists — does respect it.

Looking at `matchesFilter` (line 38–45):
```ts
function matchesFilter(status: string, tab: FilterTab, showTerminated: boolean): boolean {
  if (status === "terminated") return showTerminated;  // terminated agents appear in ALL tabs when this is true
  ...
}
```

When the user enables "Show terminated", terminated agents appear **in every tab** (all, active, paused, error). But none of the `counts` values account for this, so every count badge will show a number lower than the actual list length whenever terminated agents are present and the toggle is on.

The fix is to drive counts through the same `matchesFilter` logic:

```suggestion
  const counts = {
    all: allAgents.filter((a) => matchesFilter(a.status, "all", showTerminated)).length,
    active: allAgents.filter((a) => matchesFilter(a.status, "active", showTerminated)).length,
    paused: allAgents.filter((a) => matchesFilter(a.status, "paused", showTerminated)).length,
    error: allAgents.filter((a) => matchesFilter(a.status, "error", showTerminated)).length,
  };
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: ui/src/pages/Agents.tsx
Line: 134-140

Comment:
**Consider memoizing `counts`**

The `counts` computation iterates over the full `agents` array four times on every render. The rest of the component already uses `useMemo` (for `liveRunByAgent` and `agentMap`) for similar derived data. Wrapping `counts` in `useMemo` with `[agents, showTerminated]` deps would be consistent with that pattern and avoids repeated passes on every re-render (e.g. status badge polling triggers a render every 15 s).

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(ui): show agent counts on filter ta..." | Re-trigger Greptile

Comment on lines +135 to +140
const counts = {
all: allAgents.filter((a) => a.status !== "terminated").length,
active: allAgents.filter((a) => a.status === "active" || a.status === "running" || a.status === "idle").length,
paused: allAgents.filter((a) => a.status === "paused").length,
error: allAgents.filter((a) => a.status === "error").length,
};
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.

P1 Counts ignore showTerminated state

The new counts object computes values independently of the showTerminated state flag, but the existing matchesFilter function — which drives the actual filtered lists — does respect it.

Looking at matchesFilter (line 38–45):

function matchesFilter(status: string, tab: FilterTab, showTerminated: boolean): boolean {
  if (status === "terminated") return showTerminated;  // terminated agents appear in ALL tabs when this is true
  ...
}

When the user enables "Show terminated", terminated agents appear in every tab (all, active, paused, error). But none of the counts values account for this, so every count badge will show a number lower than the actual list length whenever terminated agents are present and the toggle is on.

The fix is to drive counts through the same matchesFilter logic:

Suggested change
const counts = {
all: allAgents.filter((a) => a.status !== "terminated").length,
active: allAgents.filter((a) => a.status === "active" || a.status === "running" || a.status === "idle").length,
paused: allAgents.filter((a) => a.status === "paused").length,
error: allAgents.filter((a) => a.status === "error").length,
};
const counts = {
all: allAgents.filter((a) => matchesFilter(a.status, "all", showTerminated)).length,
active: allAgents.filter((a) => matchesFilter(a.status, "active", showTerminated)).length,
paused: allAgents.filter((a) => matchesFilter(a.status, "paused", showTerminated)).length,
error: allAgents.filter((a) => matchesFilter(a.status, "error", showTerminated)).length,
};
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/pages/Agents.tsx
Line: 135-140

Comment:
**Counts ignore `showTerminated` state**

The new `counts` object computes values independently of the `showTerminated` state flag, but the existing `matchesFilter` function — which drives the actual filtered lists — does respect it.

Looking at `matchesFilter` (line 38–45):
```ts
function matchesFilter(status: string, tab: FilterTab, showTerminated: boolean): boolean {
  if (status === "terminated") return showTerminated;  // terminated agents appear in ALL tabs when this is true
  ...
}
```

When the user enables "Show terminated", terminated agents appear **in every tab** (all, active, paused, error). But none of the `counts` values account for this, so every count badge will show a number lower than the actual list length whenever terminated agents are present and the toggle is on.

The fix is to drive counts through the same `matchesFilter` logic:

```suggestion
  const counts = {
    all: allAgents.filter((a) => matchesFilter(a.status, "all", showTerminated)).length,
    active: allAgents.filter((a) => matchesFilter(a.status, "active", showTerminated)).length,
    paused: allAgents.filter((a) => matchesFilter(a.status, "paused", showTerminated)).length,
    error: allAgents.filter((a) => matchesFilter(a.status, "error", showTerminated)).length,
  };
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +134 to +140
const allAgents = agents ?? [];
const counts = {
all: allAgents.filter((a) => a.status !== "terminated").length,
active: allAgents.filter((a) => a.status === "active" || a.status === "running" || a.status === "idle").length,
paused: allAgents.filter((a) => a.status === "paused").length,
error: allAgents.filter((a) => a.status === "error").length,
};
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.

P2 Consider memoizing counts

The counts computation iterates over the full agents array four times on every render. The rest of the component already uses useMemo (for liveRunByAgent and agentMap) for similar derived data. Wrapping counts in useMemo with [agents, showTerminated] deps would be consistent with that pattern and avoids repeated passes on every re-render (e.g. status badge polling triggers a render every 15 s).

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/pages/Agents.tsx
Line: 134-140

Comment:
**Consider memoizing `counts`**

The `counts` computation iterates over the full `agents` array four times on every render. The rest of the component already uses `useMemo` (for `liveRunByAgent` and `agentMap`) for similar derived data. Wrapping `counts` in `useMemo` with `[agents, showTerminated]` deps would be consistent with that pattern and avoids repeated passes on every re-render (e.g. status badge polling triggers a render every 15 s).

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Count badges was computed with manual status checks that did not include
terminated agents. When user enable "Show terminated" toggle, the tab
numbers was showing less than actual list length.

Now counts go through same matchesFilter function that drives the
filtered list, so numbers always match what user sees. Also wrapped
in useMemo for consistency with other derived data in this component.
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.

1 participant