Commit 92b9da3
fix(ui): load all agents so the picker and deep-links work past the first page (#347)
### What
- Page through the **full** agent list in `useAgents` instead of
fetching only the default first page.
- Add `useAgentByName` to validate a deep-linked `agent_name` directly,
so a valid agent opens even if it's outside the loaded list.
- Only clear `agent_name` when it's present *and* invalid; accept an
agent found in the list **or** by name.
- Cap the picker's chip grid height and let it scroll, so a long agent
list doesn't push the title/prompt input off-screen.
- Distinguish a real 404 (agent doesn't exist) from transient errors in
the by-name lookup, and validate only against freshly-fetched data, so a
blip or a mid-refetch never bounces a valid deep link.
### Why
The picker fetched agents with an unpaginated `list()` call, so on
accounts with more than one page of agents only the first ~50 showed.
Those agents were invisible in the picker and because the same list
backed deep-link validation, deep-linking to one bounced back to the
home grid. Loading the full list then surfaced a layout issue where a
tall picker overflowed a vertically-centered container with no way to
scroll.
### Testing
- Unit tests for the pagination loop (multi-page, short-page, empty,
exact-boundary, safety-bound, mid-loop error) and the by-name lookup.
- `npm run typecheck` + `npm run lint` clean.
- Manually verified in a dev environment against an account with 100+
agents: all agents appear, the picker scrolls with the title/input
fixed, and deep-linking a valid agent opens its chat.
Before:
https://github.com/user-attachments/assets/46f78616-f525-499f-8047-e7fbffd54910
After:
https://github.com/user-attachments/assets/9a3b27ed-ba88-4d93-ae1f-0b2c7372f973
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes the agent picker and deep-link routing by paginating
through the full agent list (instead of stopping after the default first
page), adding a `useAgentByName` hook for direct deep-link validation,
and capping the picker's chip grid height to prevent layout overflow on
large accounts.
- **`useAgents`** now loops through all pages up to a `MAX_AGENT_PAGES`
safety bound (100 × 100 = 10 000 agents max), warns loudly if truncated,
and exports its constants so tests can import them rather than
duplicating hardcoded values.
- **`useAgentByName`** validates a deep-linked agent name directly
against the backend: 404 resolves to `null`, any other error is
re-thrown so React Query surfaces it as `isError`, and the effect in
`agentex-ui-root.tsx` respects `couldNotDetermine` to avoid clearing a
valid deep link on a transient failure.
- **`AgentsList`** gains `max-h-[60vh] overflow-y-auto` so a long agent
list scrolls within the centered container rather than pushing the title
and input off-screen.
<details><summary><h3>Confidence Score: 5/5</h3></summary>
Safe to merge — all changed paths are well-tested, the catch narrowing
and couldNotDetermine guard are correctly implemented, and the
pagination loop has a clear termination condition.
The pagination loop terminates reliably on a short page or at the safety
bound, both hooks narrow their error handling correctly (404 → null,
everything else re-thrown), and the validation effect is gated on both
isFetching signals so it never acts on stale data. Unit tests cover all
meaningful boundary cases. The previous review thread's concerns have
been fully addressed.
No files require special attention.
</details>
<details><summary><h3>Important Files Changed</h3></summary>
| Filename | Overview |
|----------|----------|
| agentex-ui/hooks/use-agents.ts | Replaces single unpaginated list()
call with an async loop that accumulates pages until a short page
signals the end; exports PAGE_SIZE and MAX_AGENT_PAGES for test imports;
adds safety-bound warning when truncation occurs. |
| agentex-ui/hooks/use-agent-by-name.ts | New hook that validates a
deep-linked agent name directly against the backend; narrows 404 to null
(success) and re-throws all other errors so transient failures surface
via isError rather than silently clearing the URL. |
| agentex-ui/components/agentex-ui-root.tsx | Integrates useAgentByName
alongside useAgents; gates validation on both isFetching signals;
couldNotDetermine guard prevents clearing a valid deep link on transient
API errors. |
| agentex-ui/components/agents-list/agents-list.tsx | Single-line change
adds max-h-[60vh] and overflow-y-auto to the chip grid container so a
long agent list scrolls instead of overflowing the vertically-centered
layout. |
| agentex-ui/hooks/use-agents.test.tsx | New unit tests covering
multi-page, short-page, empty-list, exact-multiple, safety-bound, and
mid-loop error scenarios; imports PAGE_SIZE and MAX_AGENT_PAGES directly
from source after previous review feedback. |
| agentex-ui/hooks/use-agent-by-name.test.tsx | New unit tests covering
disabled-when-no-name, successful lookup, 404→null, and non-404 error
surfacing, using NotFoundError and APIError from the agentex SDK. |
</details>
<details><summary><h3>Flowchart</h3></summary>
<a href="#gh-light-mode-only">
```mermaid
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Page loads with agent_name in URL] --> B{isAgentsFetching\nOR isAgentByNameFetching?}
B -- Yes --> C[Return early — wait for both queries to settle]
B -- No --> D{agentName present\nin URL?}
D -- No --> E{localAgentName\nin localStorage?}
E -- Yes --> F[Restore: updateParams agent_name = localAgentName]
E -- No --> G[No-op]
D -- Yes --> H{Agent found\nin paginated list?}
H -- Yes --> I[agentInList = agent]
H -- No --> J{useAgentByName\nresult?}
J -- Agent returned --> K[agentByName = Agent]
J -- 404 → null --> L[agentByName = null]
J -- Non-404 error --> M[isAgentByNameError = true\ncouldNotDetermine = true]
I --> N{status === Ready?}
K --> N
L --> O[isAgentValid = false\ncouldNotDetermine = false]
M --> P[Leave URL alone\npreserve deep link]
N -- Yes --> Q[isAgentValid = true\nLeave URL alone — agent opens]
N -- No --> O
O --> R[Clear agent_name from URL\nsetLocalAgentName undefined]
```
</a>
<a href="#gh-dark-mode-only">
```mermaid
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[Page loads with agent_name in URL] --> B{isAgentsFetching\nOR isAgentByNameFetching?}
B -- Yes --> C[Return early — wait for both queries to settle]
B -- No --> D{agentName present\nin URL?}
D -- No --> E{localAgentName\nin localStorage?}
E -- Yes --> F[Restore: updateParams agent_name = localAgentName]
E -- No --> G[No-op]
D -- Yes --> H{Agent found\nin paginated list?}
H -- Yes --> I[agentInList = agent]
H -- No --> J{useAgentByName\nresult?}
J -- Agent returned --> K[agentByName = Agent]
J -- 404 → null --> L[agentByName = null]
J -- Non-404 error --> M[isAgentByNameError = true\ncouldNotDetermine = true]
I --> N{status === Ready?}
K --> N
L --> O[isAgentValid = false\ncouldNotDetermine = false]
M --> P[Leave URL alone\npreserve deep link]
N -- Yes --> Q[isAgentValid = true\nLeave URL alone — agent opens]
N -- No --> O
O --> R[Clear agent_name from URL\nsetLocalAgentName undefined]
```
</a>
</details>
<sub>Reviews (3): Last reviewed commit: ["Merge origin/main into
vineetvora/agx1-3..."](433e6a2)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=42404585)</sub>
<!-- /greptile_comment -->
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent 2ffde24 commit 92b9da3
6 files changed
Lines changed: 368 additions & 9 deletions
File tree
- agentex-ui
- components
- agents-list
- hooks
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
22 | | - | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
23 | 32 | | |
24 | 33 | | |
25 | 34 | | |
26 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
27 | 41 | | |
28 | | - | |
| 42 | + | |
29 | 43 | | |
30 | | - | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
31 | 48 | | |
32 | 49 | | |
33 | | - | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
34 | 56 | | |
35 | 57 | | |
36 | 58 | | |
| |||
39 | 61 | | |
40 | 62 | | |
41 | 63 | | |
42 | | - | |
| 64 | + | |
43 | 65 | | |
44 | 66 | | |
45 | 67 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
0 commit comments