Repository navigation
fix(search): show slow lookups beside their section headings - #2442
Conversation
Channel, people, and message lookups that take more than a second now show "· Searching…" next to the Channels, People, and message section headings, instead of a single status pinned below all results. Each section waits one second before showing it, so fast searches don't flicker, and a section with no results yet appears only while its lookup is still running. A single screen-reader status names the sections still searching and doesn't re-announce while they finish. Co-authored-by: Eugene <11b65c7022d0662a7e3c969ca511980925a21d138ec0d25214a4997e7b598240@buzz.block.builderlab.xyz> Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: tulsi <[email protected]>
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Two small follow-ups: align the live announcement with rendered groups and add a positive assertion for pending channel resolution.
| slowChannels, | ||
| slowPeople, | ||
| slowMessages, | ||
| ); |
There was a problem hiding this comment.
🤖 [P3] Limit announcements to the rendered result groups. With crew after: and held channel/message promises, only Dates is rendered, but after 1.5 seconds this status says ‘Searching channels and messages…’. The previous channel-only announcement already existed; announcing messages in this picker branch is new. Gate channel/message announcement sources when the date or author picker replaces those groups so assistive output matches the available choices.
There was a problem hiding this comment.
🤖 Fixed in f14a1ec. The announcement now counts the channel and message lookups only when the result groups are rendered (not behind the author or date picker). New test announces no hidden result lookups behind the %s picker covers crew after: and crew from: with channel and message reads held. It fails without the gate.
| ); | ||
| await started; | ||
| expect(screen.getByText("Searching messages…")).toBeVisible(); | ||
| expect(screen.queryByText("Searching messages…")).toBeNull(); |
There was a problem hiding this comment.
🤖 [P3] Assert the replacement pending indicator positively. This assertion checks absence of ‘Searching messages…’, copy removed from production by this PR, so it no longer protects the pending in: resolution behavior. The added message tests exercise search.loading, while messagesPending also includes operatorLookupPending. Advance 1000 ms after the lookup starts and assert the Most relevant heading status and live status text in this test.
There was a problem hiding this comment.
🤖 Fixed in f14a1ec. I removed the stale assertion. Doing the 1000 ms advance inside that test leaves the operator lookup and the public channel search in flight together, so the failure path shows two identical notices (existing behavior, outside this PR), which would change what that test proves. I added a sibling test instead, shows a pending in: channel lookup beside Most relevant. It holds only the in: resolution, advances 1000 ms after it starts, then asserts Most relevant · Searching…, the "Searching messages…" live status, and no message read, and that both clear on release. It fails if operatorLookupPending is dropped from messagesPending.
An author or date picker replaces the result groups, so the live status no longer names channel or message lookups that are hidden behind it. Cover the pending in: channel lookup with a positive heading and live status assertion instead of checking for removed copy. Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: tulsi <[email protected]>
Resolve docs/buzz-help.md by keeping both the search status and View only join entries. Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: tulsi <[email protected]>
Picks up #2444, which fixes the harness kit test failing on main. Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: tulsi <[email protected]>
Picks up #2333's channel member counts alongside the search heading status. Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: tulsi <[email protected]>
loganj
left a comment
There was a problem hiding this comment.
🤖 Larry and Auto: an independent adversarial review of db0c942. Source review only. Auto also ran vitest run src/app/shell at this head: 20 files, 322 tests passed. kalvinnchau's two earlier P3 threads (announce only rendered groups, positive in: assertion) are already addressed in f14a1ec, so we do not repeat them.
Verdict: approve. No must-fix. Timers reset and cancel correctly. A lookup that settles and starts again waits a fresh second. Status-only groups add no rows, so highlight and Enter are unchanged. Every network-backed source is covered: channels, people, both message branches, and in: resolution through operatorLookupPending. Apart from re-wrapping, the real SearchChoices change is small.
Not-inline note:
- question (UX intent):
messageEmptyis nowundefinedwhile a lookup is pending (SearchResults.tsx:1166-1168). With no local matches, the list is blank for up to one second. This also happens in scoped search, where "Searching messages…" used to show at once. That is the intended no-flicker trade. It is worth a line in the design review.
| ), | ||
| )} | ||
| {(notice || | ||
| (!destinations.length && !headingStatus)) && ( |
There was a problem hiding this comment.
advice (a11y/ARIA): a status-only group renders role="rowgroup" (grid mode, when inline settings give hasControls) or role="group" with no row/option inside. In grid mode, the old empty-state row satisfied that, but !destinations.length && !headingStatus now drops it. Suggestion: set aria-busy="true" on a status-only group. The required-children rule exempts busy containers, and it also states the loading meaning.
There was a problem hiding this comment.
🤖 Done in 6c03d1f. A status-only group (heading status, no destinations) now sets aria-busy="true". A group with rows and a status doesn't, so its existing rows stay readable. The People status-only test asserts both cases.
| className="navigation-section-heading" | ||
| aria-hidden={hasControls || undefined} | ||
| > | ||
| <h2 className="navigation-section-label text-body text-secondary truncate"> |
There was a problem hiding this comment.
advice (convention/scope): truncate now applies to every SearchChoices heading, not only headings with a status. That includes PageSearch, SearchSettings, ChannelDirectoryResults and inline settings. Long labels that used to wrap now get an ellipsis, and the description does not mention it. Either apply it only when headingStatus is set, or call it out.
There was a problem hiding this comment.
🤖 Done in 6c03d1f. truncate now applies only when headingStatus is set, so PageSearch, SearchSettings, ChannelDirectoryResults and inline settings headings wrap as they did before.
| sources.length === 3 | ||
| ? `${sources.slice(0, 2).join(", ")}, and ${sources[2]}` | ||
| : sources.join(" and "); | ||
| return previous || `Searching ${names}…`; |
There was a problem hiding this comment.
question (a11y): the announcement keeps its first text until no section is searching. Example: channels become slow, then messages become slow before channels finish. The status still says "Searching channels…" after channels are done, while only messages are still searching. A screen-reader user who reads the status with the virtual cursor gets the wrong source. Options: keep neutral text ("Still searching…"), or accept this on purpose.
There was a problem hiding this comment.
🤖 Accepted on purpose. A polite live region re-announces whenever its text changes, so when sources start and finish at different times, updating the text caused repeated announcements. The design review chose a fixed message per searching episode. The stale-source case you describe is real but brief: the status clears when the last source settles, and the visual headings always show which sections are still searching. If a neutral "Still searching…" for the second and later sources reads better to you, it's a one-line follow-up, and I'll take it to the design owner.
| scopedChannelId ? "" : parsed.text, | ||
| list.status === "ready", | ||
| ); | ||
| const channelsPending = |
There was a problem hiding this comment.
question (completeness) + advice (simplicity):
- Nothing shows while the channel roster itself is still loading.
usePublicChannelSearchis inactive untillist.status === "ready", so on a cold start Channels never shows "Searching…", even though channels and chats can be incomplete. The Enterpendinggate (~line 1424) already treats that state as pending, and Messages counts it throughoperatorLookupPending. The old bottomLoadingRegionhad the same gap, so this is not a regression. Is a cold-roster status wanted here? !!query.trim() && !scopedChannelIdrepeats checks the hook already makes: when scoped, its query is"", and an emptyparsed.textis not searchable.publicChannels.loadingalone is enough.- Note only:
useDelayedSearch(line 112) is the fourth local copy of the 1000 ms delay, afterLoadingRegion,ChannelLifecycleDialogandUsagePage. A shared hook would keep the delay in one place.
There was a problem hiding this comment.
🤖 Thanks. Simplified in 6c03d1f: the channel status now reads publicChannels.loading alone. The hook is idle for scoped search (empty query) and for unsearchable text, and the shell and channel tests still pass. The cold-roster status is left out on purpose because, as you note, it isn't a regression from the old bottom indicator, and it would be a new source for this PR. It's a reasonable follow-up. I'm also leaving the shared 1000 ms hook for a follow-up rather than widening this PR into LoadingRegion, ChannelLifecycleDialog and UsagePage.
A group showing only "Searching…" has no rows yet, so it is aria-busy. Only headings with a status truncate; other search headings wrap as before. The channel status reads the lookup's own loading flag. Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: tulsi <[email protected]>
|
🤖 Thanks for the review. On the |
…-nip-fi * origin/main: (23 commits) fix(inbox): show Solo responses under All, read live replies in the open conversation, describe toolbar badges (#2275) AIDA-2434: Show saved teams in mentions (#2339) AIDA-2455: Restore channel scrollbar dragging beside threads (#2436) feat(agent-protection): show the owner why a protection provider denied input (#2105) fix(search): show slow lookups beside their section headings (#2442) Align Bestie DM layout and unread state; speed up thinking shimmer (#2418) Send Claude, Pi and Goose history as exact records (#2088) fix(agent-activity): align the stopped note and show it after the agent's reply (#2441) fix(agents): stop orphaned Attention commands and prevent duplicate runs (#2448) feat(mentions): quieter mention menu that marks agents and their owners (#2328) feat: add a Canvas View tab, History diffs and a 256 KiB limit (#2318) feat(plugins): let local agents write and reload plugins (#2435) feat(onboarding): offer Create a community after public Builderlab sign-in (#2397) Open any Inbox filter view by route (#2394) fix(bestie): deleting an agent saves no Bestie record where there was none (#2420) fix(unread): never treat agents-audience messages as unread (#1552) feat(focus): add an optional Focus plugin that quiets everything except Solo chats (#2277) [5/5] Add protection to the Goose harness plugin (#839) feat(agents): identify plugins in native hook output (#2299) [4/5] Add protection to the Codex harness plugin (#1879) ... # Conflicts: # src/features/identity/builderlab-hosted.test.ts # src/features/identity/builderlab-hosted.ts
* origin/main: fix: exclude muted channels from sidebar unread scroll indicators (#2335) fix(claude-code): open long Claude transcripts a page at a time (#2428) feat(solo): share chats as view only (#2375) fix(inbox): show Solo responses under All, read live replies in the open conversation, describe toolbar badges (#2275) AIDA-2434: Show saved teams in mentions (#2339) AIDA-2455: Restore channel scrollbar dragging beside threads (#2436) feat(agent-protection): show the owner why a protection provider denied input (#2105) fix(search): show slow lookups beside their section headings (#2442) Align Bestie DM layout and unread state; speed up thinking shimmer (#2418) Send Claude, Pi and Goose history as exact records (#2088) fix(agent-activity): align the stopped note and show it after the agent's reply (#2441) fix(agents): stop orphaned Attention commands and prevent duplicate runs (#2448) Signed-off-by: Gil Birman <[email protected]> Co-authored-by: Claude <[email protected]> # Conflicts: # src/app/shell/PageNavigation.tsx
…-clone-auth-ab8b97 * origin/main: (38 commits) feat(identity): decide a missing backup choice from community membership (block#2378) Offer Builderlab decisions in FI builds (block#2227) feat(inbox): preview recent Inbox activity when hovering the sidebar row (block#2347) Add collapsible Solo and Team sidebar sections (block#2296) fix(codex): open long Codex transcripts a page of items at a time (block#2259) Keep Solo model and effort choices per chat (block#2342) feat(sidebar): smooth channel reordering with bounded motion (block#2108) Connect legacy agents from DMs and channel mentions (block#2423) fix: exclude muted channels from sidebar unread scroll indicators (block#2335) fix(claude-code): open long Claude transcripts a page at a time (block#2428) feat(solo): share chats as view only (block#2375) fix(inbox): show Solo responses under All, read live replies in the open conversation, describe toolbar badges (block#2275) AIDA-2434: Show saved teams in mentions (block#2339) AIDA-2455: Restore channel scrollbar dragging beside threads (block#2436) feat(agent-protection): show the owner why a protection provider denied input (block#2105) fix(search): show slow lookups beside their section headings (block#2442) Align Bestie DM layout and unread state; speed up thinking shimmer (block#2418) Send Claude, Pi and Goose history as exact records (block#2088) fix(agent-activity): align the stopped note and show it after the agent's reply (block#2441) fix(agents): stop orphaned Attention commands and prevent duplicate runs (block#2448) ... Signed-off-by: damienrj <[email protected]>
## Problem #2442 added `useDelayedSearch`, which shows "Searching…" once a lookup has been pending for a second. Its effect calls `setSlow(false)` on every `pending` change, and `pending` flips on every keystroke while the relay looks up the new text. The state is almost always already `false`, but the set still re-rendered `SearchResults`, and each render ranks every channel and person name against the text again. In jsdom that is 4 renders per keystroke instead of the 3 before #2442, plus one more for each additional lookup round under real relay latency. ## Change A ref records whether the slow notice was actually set. The effect resets the state only then. The timer that shows the notice is unchanged. ## No behavior changes The hook still returns `pending && slow`: the notice appears after 1 s of pending and clears as soon as the lookup settles or the text changes. The search palette suites pass unchanged, including every "Searching…" test. There is no new test: the DOM is identical, so only a render count would show the change. ## Evidence Browser benchmark with 128 DMs, 1,001 participants and read state, at 4× CPU throttling in production Chromium: - **Switch, quiet:** open Cmd+K, type a channel name and press Enter, with no other traffic. - **Switch:** the same while another channel receives a message every 200 ms. Stock and this PR were interleaved on 8 Linux and 8 macOS workstations. Medians are per run. | scenario, metric | Linux: stock | Linux: this PR | Linux Δ % | macOS: stock | macOS: this PR | macOS Δ % | |---|---|---|---|---|---|---| | quiet, script | 379.5 ms | 372.1 ms | −1.9% | 253.1 ms | 251.6 ms | −0.6% | | quiet, task | 636.3 ms | 630.4 ms | −0.9% | 418.7 ms | 417.4 ms | −0.3% | | switch, script | 653.2 ms | 648.2 ms | −0.8% | 373.2 ms | 368.1 ms | −1.4% | | switch, blocking | 308.6 ms | 292.2 ms | −5.3% | 121.6 ms | 120.1 ms | −1.3% | Lower is better. Blocking is long-animation-frame blocking time. There are 96 samples per cell. The benchmark's relay answers lookups almost immediately; with real latency each keystroke has more lookup rounds, and each one cost an extra render before this change. Checks: the `SearchResults`, `PageSearch` and `SearchChoices` suites pass (174 tests), along with `tsc` and `biome`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Kalvin C <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
Context
In ⌘K search, "Searching open channels…" sat below every result group. When a channel you wanted hadn't arrived yet, nothing near the top said that results were still coming, and the status line was cramped under the last row. People and message lookups had the same problem: the community people lookup showed no progress at all, and "Searching messages…" appeared only when nothing else matched, with no delay, so it could flicker.
Description
Each section that waits on the network now shows its own status beside its heading: Channels · Searching…, People · Searching…, and Most relevant · Searching… (or Most recent · Searching…). Local sections (agents, chats, DMs, pages, plugins) are unchanged.
Changes
SearchResults.tsx: a local delayed flag per network source, a latched announcement, andheadingStatuson Channels, People, and both message branches. The bottomLoadingRegionis removed.SearchChoices.tsx: an optionalheadingStatusonSearchGroup. It admits a status-only group without an empty row and renders after the label inside the existinghideLabelheading. Most of this file's diff is biome re-wrapping the.mapcallback, so review it with whitespace hidden.docs/buzz-help.md: one line on the new behavior.Testing
f14a1ec20): the live status names only lookups behind rendered groups, so the author and date pickers announce nothing. A positive test covers the pendingin:resolution. Both new tests fail without their production line. All 20src/app/shelltest files pass (316 tests), as dopnpm checkand the pre-push hooks.0332b36b4(based on1e85347de):pnpm checkpasses. The full Vitest run had 16,454 passing tests and 20 failing in unrelated files:MessageReactions,ProfileAgentIdentity,NotificationSettings,store-system-discovery, andbrowser-host/archive. Other test runs were loading the machine at the same time. In that state, running those five files alone fails similarly on unchangedorigin/main(17–18 failures, a first-test 5s timeout then cascading failures).MessageReactions.test.tsxpasses 22/22 on this head when run alone. Hosted CI is the authority here.1e85347de, the same change passed the full suite (16,422 tests) on8ec2ea938. The only rebase conflict wasSearchChoices.tsxagainst feat(search): browse open channels from a Channels tab in search #2164'shideLabel, which keeps its existing behavior.🤖 Generated with Claude Code


