Skip to content

fix(ui): load all agents so the picker and deep-links work past the first page#347

Merged
vineetvora-scale merged 4 commits into
mainfrom
vineetvora/agx1-361-fix-agentex-ui-agent-picker-pagination-only-first-50-agents
Jul 8, 2026
Merged

fix(ui): load all agents so the picker and deep-links work past the first page#347
vineetvora-scale merged 4 commits into
mainfrom
vineetvora/agx1-361-fix-agentex-ui-agent-picker-pagination-only-first-50-agents

Conversation

@vineetvora-scale

@vineetvora-scale vineetvora-scale commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

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:

Screen.Recording.2026-07-07.at.11.44.28.AM.mov

After:

Screen.Recording.2026-07-07.at.11.41.26.AM.mov

Greptile Summary

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.

Confidence Score: 5/5

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.

Important Files Changed

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.

Flowchart

%%{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]
Loading
%%{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]
Loading

Reviews (3): Last reviewed commit: "Merge origin/main into vineetvora/agx1-3..." | Re-trigger Greptile

vineetvora-scale and others added 2 commits July 2, 2026 14:59
…ks by name

The agent picker fetched agents with an unpaginated list() call, so on
accounts with more than one page of agents only the first page was shown.
Those agents were invisible in the picker and, because the same list backed
deep-link validation, deep-linking to one cleared the agent_name param and
bounced back to the home grid.

- useAgents now pages through the full list (limit 100, loop until a short
  page) with a safety bound + warning to avoid silent truncation.
- Add useAgentByName to validate a deep-linked agent directly via
  retrieveByName, so a valid agent opens even if it's outside the loaded list.
- agentex-ui-root only clears agent_name when it's present and invalid, and
  accepts an agent found either in the list or by name.
- Add unit tests for the pagination loop and the by-name lookup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With the full agent list now loading, accounts with many agents produced a
picker taller than the viewport. Because the home view is vertically centered
with no overflow handling, the grid spilled off-screen top and bottom, hiding
the title and the prompt input with no way to scroll. Cap the chip grid height
and let it scroll internally so the title and input stay visible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vineetvora-scale
vineetvora-scale requested a review from a team as a code owner July 7, 2026 15:37
@vineetvora-scale vineetvora-scale changed the title fix(ui): load all agents in the picker and keep it usable with long lists fix(ui): load all agents so the picker and deep-links work past the first page Jul 7, 2026
Comment thread agentex-ui/hooks/use-agent-by-name.ts
Comment thread agentex-ui/hooks/use-agents.test.tsx Outdated
// on agent_name changes, not on every `agents`/`agentByName` identity change.
useEffect(() => {
if (isLoading) return;
if (isLoading || isAgentByNameLoading) return;

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.

this validates using isLoading, which is false when React Query has stale cached data and is refetching. A cached null by-name result or stale non-Ready list entry can clear agent_name before the fresh validation finishes. Can we use the enabled query’s isFetching / success state instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, I switched both queries to isFetching so it won't validate against stale cached data mid-refetch

…ation on isFetching

- useAgentByName resolves to null only on a 404 (APIError, status 404) and re-throws
  everything else, so a transient network/5xx/auth failure surfaces as a query error
  instead of masquerading as "not found".
- agentex-ui-root: gate the deep-link validation on isFetching (not isLoading, which is
  false once a query has cached data) so it never validates against stale data mid-refetch;
  and only clear agent_name when the agent is genuinely invalid — skip the clear when the
  by-name lookup errored and the agent isn't in the loaded list (unknown != invalid).
- Export AGENTS_PAGE_SIZE / MAX_AGENT_PAGES and import them in the test instead of
  duplicating the values.
- Tests updated: 404 -> null, non-404 -> surfaced error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@declan-scale declan-scale left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM thanks, maybe want to make a low priority linear ticket for the improvement (or just implement real quick, I believe the threads fetching already has the infinite query pattern)

@@ -20,7 +38,31 @@ export function useAgents(agentexClient: AgentexSDK) {
return useQuery({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[nit] we could use an infinite query with a scroll trigger but until we see long loading times I think this pattern is ok

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, went with the simple page-through for now since the picker wants the full list available anyway. If we start seeing slow loads on large accounts, an infinite query with a scroll trigger is a good follow-up. Good call out

…r-pagination

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vineetvora-scale
vineetvora-scale merged commit 92b9da3 into main Jul 8, 2026
15 checks passed
@vineetvora-scale
vineetvora-scale deleted the vineetvora/agx1-361-fix-agentex-ui-agent-picker-pagination-only-first-50-agents branch July 8, 2026 17:23
erichwoo-scale added a commit that referenced this pull request Jul 8, 2026
…switch

- Account picker: extract a shared `iconTile` (loading + single-account collapsed
  tiles) and a shared `options` element (the SelectContent list previously duplicated
  across the collapsed and expanded switchers); single change handler `onAccountChange`.
- Provider: `setSelectedAccountId` now clears `task_id` on an explicit switch (the open
  task is account-scoped and 404s under a new account). This preserves the reset that
  previously lived in agentex-ui-root's validation effect, which #347 reworked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
erichwoo-scale added a commit that referenced this pull request Jul 8, 2026
…itch

A selected agent is account-scoped, but its name persisted in the URL across a switch —
so a same-named agent in the new account stayed selected (and #347's localStorage restore
could reinstate it). On an account change, clear agent_name and the remembered agent so
the view resets to the grid. task_id is already cleared by the switch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
erichwoo-scale added a commit that referenced this pull request Jul 8, 2026
…itch

A selected agent is account-scoped, but its name persisted in the URL across a switch —
so a same-named agent in the new account stayed selected (and #347's localStorage restore
could reinstate it). On an account change, clear agent_name and the remembered agent so
the view resets to the grid. task_id is already cleared by the switch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

3 participants