From 5b654ab74fb697d57693162fba1bbe38e121a8e1 Mon Sep 17 00:00:00 2001 From: Wes Date: Fri, 21 Aug 2026 09:45:33 -0600 Subject: [PATCH] Complete filtered user search pages Fetch another user-search page when selected or archived results leave the visible result viewport underfilled. Cover fully filtered and non-scrollable first pages, and capture duplicate-name UI evidence. Co-authored-by: Wes Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Signed-off-by: Wes --- .../src/features/agents/ui/RespondToField.tsx | 100 +++++++++++------- .../tests/e2e/agent-access-warning.spec.ts | 53 ++++++++++ desktop/tests/e2e/mentions.spec.ts | 6 ++ 3 files changed, 121 insertions(+), 38 deletions(-) diff --git a/desktop/src/features/agents/ui/RespondToField.tsx b/desktop/src/features/agents/ui/RespondToField.tsx index bb39be0cd7..9d07f8ca49 100644 --- a/desktop/src/features/agents/ui/RespondToField.tsx +++ b/desktop/src/features/agents/ui/RespondToField.tsx @@ -142,6 +142,24 @@ export function CreateAgentRespondToField({ ), [allowlistSet, isArchivedDiscovery, userSearchResults], ); + const searchViewportRef = React.useRef(null); + React.useEffect(() => { + const viewport = searchViewportRef.current; + if ( + !viewport || + !userSearchQuery.hasNextPage || + userSearchQuery.isFetchingNextPage || + viewport.scrollHeight > viewport.clientHeight + ) { + return; + } + + void userSearchQuery.fetchNextPage(); + }, [ + userSearchQuery.fetchNextPage, + userSearchQuery.hasNextPage, + userSearchQuery.isFetchingNextPage, + ]); const handleSearchScroll = useUserSearchFetchMoreOnScroll(userSearchQuery); const pasteParsed = React.useMemo( @@ -261,6 +279,7 @@ export function CreateAgentRespondToField({ onAddRawPubkey={handleAddRawPubkey} onAddSearchResult={handleAddSearchResult} onSearchScroll={handleSearchScroll} + searchViewportRef={searchViewportRef} onPasteTextChange={setPasteText} onQueryChange={setQuery} onRemove={handleRemove} @@ -296,6 +315,7 @@ function AllowlistPicker({ onAddRawPubkey, onAddSearchResult, onSearchScroll, + searchViewportRef, onPasteTextChange, onQueryChange, onRemove, @@ -318,6 +338,7 @@ function AllowlistPicker({ onAddRawPubkey: (pubkey: string) => void; onAddSearchResult: (user: UserSearchResult) => void; onSearchScroll: (event: React.UIEvent) => void; + searchViewportRef: React.RefObject; onPasteTextChange: (value: string) => void; onQueryChange: (value: string) => void; onRemove: (pubkey: string) => void; @@ -414,66 +435,69 @@ function AllowlistPicker({

Searching…

- ) : searchResults.length > 0 ? ( + ) : (
- {searchResults.map((result) => ( + {searchResults.length > 0 ? ( + searchResults.map((result) => ( + + )) + ) : queryIsHexPubkey ? ( - ))} + ) : ( +

+ No matching users. +

+ )}
- ) : queryIsHexPubkey ? ( - - ) : ( -

- No matching users. -

)} ) : null} diff --git a/desktop/tests/e2e/agent-access-warning.spec.ts b/desktop/tests/e2e/agent-access-warning.spec.ts index 7bfec80167..65c093cb3c 100644 --- a/desktop/tests/e2e/agent-access-warning.spec.ts +++ b/desktop/tests/e2e/agent-access-warning.spec.ts @@ -165,6 +165,9 @@ test("selected-people search keeps same-name people independently selectable", a await expect(firstWill).toContainText("11111111…1111"); await expect(secondWill).toContainText("Will"); await expect(secondWill).toContainText("22222222…2222"); + await page + .getByTestId("agent-respond-to-allowlist") + .screenshot({ path: `${SHOTS}/duplicate-will-selected-people.png` }); await secondWill.click(); await expect( @@ -173,6 +176,56 @@ test("selected-people search keeps same-name people independently selectable", a await expect(firstWill).toHaveCount(0); }); +for (const selectedFirstPageCount of [50, 49]) { + test(`selected-people search fetches past a ${ + selectedFirstPageCount === 50 ? "fully filtered" : "non-scrollable" + } first page`, async ({ page }) => { + const firstPageProfiles = Array.from({ length: 50 }, (_, index) => ({ + pubkey: (index + 1).toString(16).padStart(64, "0"), + displayName: `Will ${String(index).padStart(2, "0")}`, + })); + const targetPubkey = "f".repeat(64); + const agent = TEST_IDENTITIES.charlie; + await installMockBridge(page, { + managedAgents: [ + { + pubkey: agent.pubkey, + name: "Hack Day Helper", + status: "running", + channelNames: ["general"], + respondTo: "allowlist", + respondToAllowlist: firstPageProfiles + .slice(0, selectedFirstPageCount) + .map((profile) => profile.pubkey), + }, + ], + searchProfiles: [ + ...firstPageProfiles, + { pubkey: targetPubkey, displayName: "Will Target" }, + ], + }); + await page.goto("/"); + await openAgentAccessDialog(page, agent.pubkey); + + await page.getByTestId("agent-respond-to-search").fill("Will"); + + await expect + .poll( + () => + page.evaluate(() => + (window.__BUZZ_E2E_COMMAND_LOG__ ?? []) + .filter((entry) => entry.command === "search_users") + .map((entry) => entry.payload), + ), + { message: "search should request the second result page" }, + ) + .toContainEqual(expect.objectContaining({ cursor: "2" })); + await expect( + page.getByTestId(`agent-respond-to-result-${targetPubkey}`), + ).toContainText("Will Target"); + }); +} + test("full agent editor tightens the exact sidebar agent instance", async ({ page, }) => { diff --git a/desktop/tests/e2e/mentions.spec.ts b/desktop/tests/e2e/mentions.spec.ts index 94ec4eb3d2..b7cece6ac8 100644 --- a/desktop/tests/e2e/mentions.spec.ts +++ b/desktop/tests/e2e/mentions.spec.ts @@ -8,6 +8,7 @@ import { const MOCK_VIEWER_PUBKEY = "deadbeef".repeat(8); const GENERAL_CHANNEL_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50"; +const SHOTS = "test-results/mentions"; test.beforeEach(async ({ page }) => { await installMockBridge(page); @@ -715,6 +716,11 @@ for (const selectedWill of ["first", "second"] as const) { await expect(firstWill).toBeVisible(); await expect(secondWill).toBeVisible(); await expect(dropdown.getByTestId("mention-collision-npub")).toHaveCount(2); + if (selectedWill === "first") { + await dropdown.screenshot({ + path: `${SHOTS}/duplicate-will-autocomplete.png`, + }); + } await dropdown.getByTestId(`mention-suggestion-${selectedPubkey}`).click(); await page.keyboard.type(`hello ${selectedWill}`);