镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(search): show member counts on channel results - #2333

Merged
kazemek21 merged 3 commits into
block:mainfrom
koltenrobison:kolten/search-channel-member-counts
Nov 4, 2026
Merged

kazemek21 merged 3 commits into
block:mainfrom
koltenrobison:kolten/search-channel-member-counts

Conversation

@koltenrobison

@koltenrobison koltenrobison commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Channel results in the search palette now show a member count at the end of the detail line, e.g. Public channel · not joined · 3 members or Conversation · 12 members.

Screenshots use the browser test fixture, with channel names and member counts copied from the internal community: buzz-community 99, buzz-help 14, buzz-development 138.

Light Dark
Search palette, light theme Search palette, dark theme

How it works

  • Joined channels use the roster discovery already has (channel.members). No extra read.
  • Unjoined public channels have no roster on the client, because discovery only reads rosters that include the viewer (#p: [viewer]). The new channels.memberCounts(ids) in store.ts sends one bounded 39002 read without that filter and counts distinct well-formed p entries in each channel's newest relay-signed roster. The relay already serves full rosters of public channels to non-members.
  • Those rosters never reach discovery, so a counted roster grants no reading, membership or sidebar row. A test checks this.
  • useChannelMemberCounts waits for the same 300 ms typing pause as public channel search and reads each channel only once while the palette is mounted. If the read fails, the row just has no count.

Testing

  • New tests: SearchResults.test.tsx checks the joined, unjoined and singular/plural labels, the exact read, and that discovery is untouched. store.test.ts checks the newest roster wins, that duplicate, malformed, wrong-author and wrong-channel entries are ignored, and that an oversized answer is rejected.
  • global-search.spec.mjs now asserts the count on the unjoined row. The fixture serves that channel's roster for an unfiltered read.
  • global-search.spec.mjs: 20/20 passed across Chromium and WebKit.
  • vitest run (all files): 2 failures, neither caused by this change. media-preparation.integration.test.mjs (tiled HEIC) fails on main on my machine too. store-system-discovery.test.ts passes when run alone.
  • tsc --noEmit and Biome on changed files are clean.
  • I haven't checked it against a live relay in the native app yet.

Human test steps

  1. just desktop with VPN on, signed in to the internal community.
  2. Press ⌘K and type part of a public channel name you haven't joined, e.g. buzz-dev.
  3. Expected: about 300 ms after you stop typing, the row reads Public channel · not joined · N members. N should match the channel's member list once you open it. Joined channels read Conversation · N members.

🤖 Generated with Claude Code

@koltenrobison
koltenrobison marked this pull request as ready for review November 3, 2026 21:57
@koltenrobison
koltenrobison requested a review from a team as a code owner November 3, 2026 21:57

@kazemek21 kazemek21 left a comment

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.

Kyle reviewed this PR and the Nexus review-tracker notes. Approved with no comments.

Posted by Codex (gpt-6.1-sol) on behalf of kylez.

koltenrobison and others added 3 commits November 4, 2026 11:24
Channel rows in the search palette now end with their member count, e.g.
"Public channel · not joined · 3 members". Joined rows count the roster
discovery already holds. Unjoined public rows read their relay-signed
39002 rosters through a new display-only channels.memberCounts read
that never reaches discovery, so it grants no access.

Co-authored-by: Wumbo <cba8cea567445730d41093b28915fac9a55aba4315a05864995e81bc53bfc724@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kolten Robison <kolten@squareup.com>
Co-authored-by: Wumbo <cba8cea567445730d41093b28915fac9a55aba4315a05864995e81bc53bfc724@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kolten Robison <kolten@squareup.com>
…eader

Main now has channels.readMemberCounts and useMemberCounts for the
Channels tab, so search rows reuse them instead of a second reader.

Co-authored-by: Wumbo <cba8cea567445730d41093b28915fac9a55aba4315a05864995e81bc53bfc724@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kolten Robison <kolten@squareup.com>
@koltenrobison
koltenrobison force-pushed the kolten/search-channel-member-counts branch from 4add9a6 to ade5617 Compare November 4, 2026 18:34
@kazemek21
kazemek21 merged commit 262a213 into block:main Nov 4, 2026
42 checks passed
johnmatthewtennant added a commit that referenced this pull request Nov 4, 2026
Signed-off-by: John Tennant <jtennant@block.xyz>
Co-authored-by: Damon <8a08290d889f1c68921542bfe9631d4cd671be9bea5f1114951f386819e4c10a@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* origin/main:
  feat(agents): identify plugins in native hook output (#2299)
  [4/5] Add protection to the Codex harness plugin (#1879)
  feat(search): show member counts on channel results (#2333)
  Fix the mute icon flashing when selecting a channel (#2287)
  feat(agents): open a working agent's transcript from its activity (#2276)
  test(agents): refuse a real relay workflow mention in the harness kit (#2444)
  fix(agents): catch up every event watch on the kinds it watches (#1852)
  Keep local artifact collections current (#1507)
  feat(members): add several people to a chat in one write (#2010)
bradseiler added a commit that referenced this pull request Nov 4, 2026
Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz>

* origin/main:
  feat(agents): identify plugins in native hook output (#2299)
  [4/5] Add protection to the Codex harness plugin (#1879)
  feat(search): show member counts on channel results (#2333)
  Fix the mute icon flashing when selecting a channel (#2287)
  feat(agents): open a working agent's transcript from its activity (#2276)
  test(agents): refuse a real relay workflow mention in the harness kit (#2444)
  fix(agents): catch up every event watch on the kinds it watches (#1852)
  Keep local artifact collections current (#1507)
  feat(members): add several people to a chat in one write (#2010)

Signed-off-by: Brad Seiler <seiler@squareup.com>
tulsi-builder added a commit that referenced this pull request Nov 4, 2026
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 <noreply@anthropic.com>
Signed-off-by: tulsi <tulsi@block.xyz>
michaelneale added a commit that referenced this pull request Nov 4, 2026
* origin/main:
  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)
  feat(search): show member counts on channel results (#2333)
  Fix the mute icon flashing when selecting a channel (#2287)
  feat(agents): open a working agent's transcript from its activity (#2276)

Signed-off-by: Michael Neale <michael.neale@gmail.com>

# Conflicts:
#	crates/plugin-manager/tests/management.rs
#	src/app/pages.integration.test.mjs
#	src/features/agent-harness/acp/process.ts
wpfleger96 added a commit that referenced this pull request Nov 4, 2026
…-service

* origin/main: (73 commits)
  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)
  feat(search): show member counts on channel results (#2333)
  Fix the mute icon flashing when selecting a channel (#2287)
  feat(agents): open a working agent's transcript from its activity (#2276)
  test(agents): refuse a real relay workflow mention in the harness kit (#2444)
  fix(agents): catch up every event watch on the kinds it watches (#1852)
  Keep local artifact collections current (#1507)
  feat(members): add several people to a chat in one write (#2010)
  feat(solo): let viewers join read-only spaces (#2301)
  [3/5] Add protection to the Claude harness plugin (#1878)
  fix(settings): keep the bottom padding on long sections (#2361)
  fix(mermaid): cap diagram output size (#2231)
  feat(agents): give plugin agent types the Buzz side of a turn (#2364)
  feat(plugins): pin buzz://plugin-install links to a path and commit (#2315)
  feat(plugins): tint currentColor SVG page icons like host icons (#2314)
  ...

# Conflicts:
#	src-tauri/src/host_process.rs
#	src-tauri/src/lib.rs
#	src/bundled/claude-code/claude.ts
#	src/bundled/codex/rpc.ts
#	src/bundled/goose/goose.ts
#	src/bundled/goose/runtime.ts
#	src/bundled/goose/sessions.test.ts
#	src/bundled/goose/sessions.ts
#	src/features/agent-harness/acp/process.ts
gilgtm added a commit that referenced this pull request Nov 4, 2026
* origin/main: (22 commits)
  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)
  feat(search): show member counts on channel results (#2333)
  Fix the mute icon flashing when selecting a channel (#2287)
  feat(agents): open a working agent's transcript from its activity (#2276)
  test(agents): refuse a real relay workflow mention in the harness kit (#2444)
  fix(agents): catch up every event watch on the kinds it watches (#1852)
  Keep local artifact collections current (#1507)
  feat(members): add several people to a chat in one write (#2010)
  feat(solo): let viewers join read-only spaces (#2301)
  [3/5] Add protection to the Claude harness plugin (#1878)
  ...

Signed-off-by: Gil Birman <gil@squareup.com>
Co-authored-by: Claude <noreply@anthropic.com>
wpfleger96 added a commit that referenced this pull request Nov 4, 2026
…r-load-perf

* origin/main:
  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)
  feat(search): show member counts on channel results (#2333)
  Fix the mute icon flashing when selecting a channel (#2287)
  feat(agents): open a working agent's transcript from its activity (#2276)

Signed-off-by: Will Pfleger <wpfleger@block.xyz>

# Conflicts:
#	src/features/agent-harness/acp/agent-sessions.ts
matt2e added a commit that referenced this pull request Nov 5, 2026
…own with a label-only Edit target (#2506)

## What changed

In **Messages → channel → ⋯ Channel actions → View channel details**,
the Description row now renders through `MessageMarkdown`, the same
renderer messages use, instead of plain text.

- URLs, Buzz links and `#channel` references are real links for every
viewer: owners, admins and plain members. `#name` only links when it
exactly matches a channel name, including case.
- Bold, italics and other Markdown render, instead of showing raw `**` /
`_` markers.
- Only the **Description** label and its pencil open *Edit channel
details*. Clicking the description text no longer opens the editor. The
pencil still reveals on row hover, the label is a 24px target, and the
row has no hover fill. "No description" and "Not available" stay plain
text. Members and Channel ID keep their whole-row actions.
- The panel's Escape handler now has ThreadPanel's guard: it skips
events that are already handled or that come from a portalled layer. A
description link's context menu or preview card can't close the panel.

Link safety is the same as in messages: raw HTML is skipped, only Buzz,
profile and http(s) URLs pass `safeLinkUrl`, images render as alt text,
and external links get `target=_blank rel="noopener noreferrer"`.

## Why

Before this change, the description was plain text, so its URLs and
`#channel` references couldn't be followed. For owners and admins it was
worse: the Edit button covered the whole row with an `::after` overlay,
so clicking a URL in the description opened *Edit channel details*.
`docs/channels.md` documented that whole-row behaviour on purpose, and
this PR updates it. Lifting the links above the overlay was rejected,
because DESIGN.md says to "keep an independent row action outside the
row's primary click target". The label-only `SettingsDetail` variant
follows that rule.

## Scope

This PR covers channel details only. **⌘K and the join notice are left
alone on purpose.** Unjoined channels still show only "Public channel ·
not joined" in ⌘K, and the join notice still says only "Join channel to
send messages". Showing the description there edits the same lines as
#2333 (member counts in channel results) and #2301 (joining read-only
spaces), so it will be a separate follow-up PR stacked on those two.
`BuzzLinkPreview` is also out of scope (see Open points).

## Before / after

Both sides ran the same Playwright script against the same fixture,
viewport (1280×800), light theme and timeline. Before is `0b2dfbf988`
(this branch's base). After is `8e1ffa8dcc`; the last commit only
changes tests. The fixture description is `**R2 ops** on-call. Runbook:
https://example.test/r2-runbook · escalate in #Beta · _quiet hours after
22:00 UTC_`.


https://github.com/user-attachments/assets/07c62edb-25f5-4033-a0c8-7c29b332c76f

*Each side gets the same clicks at the same moment. **Before:** clicking
the URL (11.4s) and `#Beta` (15.9s) both open "Edit channel details".
**After:** the URL opens a new window, `#Beta` goes to the Beta channel,
and only the Description label (21.7s) opens the editor. The pointer,
red click dot and action captions come from Playwright's `showActions`
and are not app UI.*

![Side-by-side at t=9.7s with the pointer on "on-call": before shows raw
** and _ markers, a plain-text URL and the whole row greyed as one
button; after shows bold, underlined URL and #Beta links, italics, and
only the pencil next to
"Description"](https://github.com/user-attachments/assets/50cb3fce-26dc-454e-8430-ecb6242e03b8)

*Pointer resting on "on-call". **Before:** raw `**` and `_` markers, the
URL as plain text wrapping at its hyphen, and the whole row greyed
because the row is one button. **After:** bold text, underlined URL and
`#Beta` links, italics, and only the pencil next to "Description", with
no row fill.*

| Before (`0b2dfbf988`) | After (`8e1ffa8dcc`) |
|---|---|
| ![Before: Description row shows raw Markdown markers and a plain-text
URL, and the whole row is greyed on
hover](https://github.com/user-attachments/assets/6a42ae5d-f99d-4516-a65c-ca6e4a43b4bc)
| ![After: Description row shows rendered Markdown with URL and #Beta
links, and only the pencil appears on
hover](https://github.com/user-attachments/assets/d1f8bb09-1ab0-4498-8f6e-acf43a4ae3e5)
|

*Full-window stills behind the side-by-side image, captured losslessly
with `page.screenshot` at the same moment (no pointer overlay).*

## Commits

1. **`fix(channels): link the description in channel details`**
(43db4ea): `ChannelSettingsPanel` renders a non-empty description
with `MessageMarkdown`, using a synthetic row, the session's reference
directory and the page's `openLink` / `canOpenLink`. `ChannelsPage`
passes the session and link handlers. A new label-only `SettingsDetail`
variant drops the whole-row overlay for Description. CSS and
`docs/channels.md` are updated, and Vitest and browser cases are added.
2. **`fix(channels): tighten the linked description row in channel
details`** (8e1ffa8): follow-ups from the first AI review (below).
3. **`test(channels): prove the description row neither grows nor
highlights`** (fce07e4): follow-ups from the second AI review
(below). Test-only.

## AI review follow-ups

**First review of 43db4ea** found no blockers and five polish
suggestions. All five, plus the Escape guard, landed in 8e1ffa8:

- Moved `useReferenceDirectory` and the synthetic row into a small
`ChannelDescription` component. It mounts only for a non-empty
description with a session and link handler. Profile and channel-list
snapshots no longer re-render the whole panel and its editor surface for
DM, solo or empty-description channels.
- Dropped `aria-description` from the label-only Edit button. It made
screen readers read the raw Markdown source right before the rendered
value.
- Gave the label a 24px minimum target, as DESIGN.md asks for inline
targets, with a negative block margin so the row doesn't move. The
pencil reveals on row hover again, including under reduced motion, but
only the label takes the click.
- Set `line-height: inherit` on the description cell, so it lines up
with Members and Channel ID instead of using message line height.
- Restored "no animation for reduced motion" in the Channel ID docs, and
described the row-hover reveal and the label target.
- Guarded the panel's Escape handler like ThreadPanel's.

**Second review of 8e1ffa8** found no merge blockers and confirmed
all six follow-ups. It raised two test-strength suggestions, both
applied in fce07e4:

- The 24px label-target check would still have passed if the negative
`margin-block` stopped applying, because the row would just grow. It now
checks that the Description value sits at the same offset inside its row
as Members' value (40px inside the top border).
- The "no row highlight" check hovered the padding and read the
background straight away. It now hovers the value text, waits for the
row-hover pencil reveal as proof the row is hovered, then checks the
background. It also hovers the label, which catches a highlight rule
that loses its label-only exclusion.

## Tests

- **Vitest** (`ChannelDetailsEditor.test.tsx`): an editor and a member
each get link roles, `onOpenLink` targets, an editor that stays closed
on link clicks, and a label that still opens it.
`ChannelSettingsPanel.test.tsx` covers the Escape guard and the missing
accessible description.
- **Browser case added**: "description links" → "an owner's description
links open their targets, not the editor" in
`tests/browser/channel-settings.spec.mjs`. It's a browser case because
it checks layout: which element gets a click at a given point, the 24px
label target, row offsets and hover paint. jsdom can't model any of
that.
- **Browser case updated**: `tests/browser/channel-header-menu.spec.mjs`
now expects the Description row to be label-only (pencil revealed, no
row fill). Members still expects a whole-row action. No browser cases
were removed.
- **Fail-then-pass**: the commit 1 Vitest and browser cases fail on
`0b2dfbf988`. The commit 2 Escape and accessible-description assertions
fail on 43db4ea. Each commit 3 assertion fails under a deliberate CSS
break in Chromium and WebKit; for example, removing the margin gives 44
vs 39.98.

## Checks run

On fce07e4 (the PR head), locally on a Blox workstation:

- typecheck, lint, design checks and Biome: pass
- Vitest on `src/bundled/channels`: pass (1,000 tests across 60 files)
- `tests/browser/channel-settings.spec.mjs` and
`tests/browser/channel-header-menu.spec.mjs`: pass in Chromium and
WebKit (18 runs)

Not run:

- The full browser suite.
- Any checks against current `main`. This branch is based on
`0b2dfbf988`. `git merge-tree` shows a clean merge with `main`, but
`main` has since changed `docs/channels.md`, `Channels.module.css`,
`ChannelsPage.tsx` and `channel-header-menu.spec.mjs`.

Hosted CI is the first run on a merged tree.

## Open points

1. **The `#channel` preview card can cover the Description label.**
Hovering `#Beta` in the description shows MessageLink's existing channel
preview card. The card can hide the "Description" label and pencil,
which is now the only way to start editing. In the recording the card
had dismissed before the pointer reached the label, so the click worked,
but this interaction is new to the panel.
2. **The panel now parses descriptions as Markdown, but
`BuzzLinkPreview` still shows them raw.** The same channel can look
different in the two places; for example, `# Ops` becomes a heading in
the panel. This trade-off was accepted for this PR, and
`BuzzLinkPreview` was out of scope.
3. **Not yet tried in the native app.** Only Vitest, the browser specs
and headless Chromium recordings have exercised this change. The human
test step below is still pending.

## How to test

1. Open a channel you own. Choose **⋯ Channel actions → Edit channel
details** and set the description to `**Ops** runbook:
https://example.com · see #<another channel's exact name> · _quiet
hours_`.
2. Choose **⋯ Channel actions → View channel details**. Expected: bold
and italics render, and the URL and `#channel` are underlined links.
Hovering the row shows the pencil next to "Description" but doesn't fill
the row.
3. Click the URL. Expected: it opens in the browser, and the editor
doesn't open.
4. Click the `#channel` link. Expected: it goes to that channel.
5. Go back, reopen details, and click the "Description" label. Expected:
*Edit channel details* opens.
6. Repeat steps 2–4 as a plain member. Expected: the same links, and no
pencil.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: mtoohey <mtoohey@squareup.com>
Co-authored-by: Claude Opus 5.5 <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.

2 participants