feat(ui): show agent count numbers on filter tabs#1918
feat(ui): show agent count numbers on filter tabs#1918bluzername wants to merge 2 commits intopaperclipai:masterfrom
Conversation
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 SummaryThis 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.
Confidence Score: 4/5Safe 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
Prompt To Fix All With AIThis 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 |
ui/src/pages/Agents.tsx
Outdated
| 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, | ||
| }; |
There was a problem hiding this 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):
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:
| 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.
ui/src/pages/Agents.tsx
Outdated
| 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, | ||
| }; |
There was a problem hiding this comment.
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.
Thinking Path
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
matchesFilterfunction (same logic that drives filtered lists):Then show them in the tab labels as parenthesized numbers: "Active (5)", "Error (2)", etc.
Counts are wrapped in
useMemowith[agents, showTerminated]deps, consistent with other memoized data in this component (liveRunByAgent,agentMap).How to test
Review feedback addressed
matchesFilterinstead of manual status checks, so they respectshowTerminatedstateuseMemoto avoid 4x array iteration on every render1 file changed, 9 insertions(+), 7 deletions(-) (net +2 lines).