Skip to content

Say who is in a group, out of the list the connection already keeps - #128

Merged
jlucaso1 merged 2 commits into
mainfrom
claude/issue-126-pr-fw57s8
Sep 3, 2026
Merged

Say who is in a group, out of the list the connection already keeps#128
jlucaso1 merged 2 commits into
mainfrom
claude/issue-126-pr-fw57s8

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Closes #126.

What was wrong

A group's header drew its name and nothing under it. WhatsApp puts the members there; here the line was blank, and deliberately so — the comment in chat_header.rs said why:

No member count. participants is not a roster — it is filled only when a live message supplies that sender's name, so a fifty-person group with one recently observed sender reported "1 members".

That is the whole of the bug. The map a front end holds per chat answers "who has spoken here lately", and the header had nothing else to read.

Where the answer was

On the connection, and it was already being maintained: the library keeps a participant list per group because the send path needs one, patches it as add/remove notifications arrive (handlers/notification/groups.rs), and invalidates it when the server calls its snapshot stale. So this adds no polling and no new source of truth — it asks the side that has one.

  • ClientRequest::GroupMembers, answered with DaemonMessage::GroupMembers { id, roster }. Addressed to the client that asked rather than published, like a page of history: it is one line in one window's header.
  • Protocol 27. A v26 daemon does not know the request and refuses it as malformed, and the daemon is the half that deliberately outlives an upgrade — the same case v15, v21, v23, v24 and v25 were bumped for. Changelog entry in transport.rs.
  • Groups::query_info, not get_metadata. The first is the cached, send-oriented view: a group opened twice costs nothing the second time, and a miss sends the participant phash, so an unchanged group is answered not-modified rather than downloaded. The second has no cache in front of it at all, and what it adds — subject, description, admin roles — is drawn nowhere yet.
  • Not gated on the network (Action::needs_network), because the common answer is a read of a list already held: gating it would empty the line for the length of a blip and put it back only when the conversation was opened again. A query that does reach the wire fails on its own and says asking again may work.

What the line says

Names come from the same NameBook a bubble's sender comes from, so nobody is "Ana" over their message and a number in the line above it. Then:

  • "You" first, not last where WhatsApp puts it — the line is cut short by design, and the one name the reader always recognises must not be what falls off the end.
  • Six names, then a count. You, Ana, Bruno, Carla, Davi, Elena and 42 others.
  • A member nobody has named is counted, never spelled out. A LID-addressed group answers with an address and no contact behind it, so listing them would read "Unknown contact" six times in a row. An all-stranger group therefore reads 50 members — which is the answer participants could never give, and is the fact the names were standing in for.
  • An empty roster draws nothing. That is an answer that says nothing, not a group with nobody in it.

Typing still outranks it: who is typing is a fact about now, and a roster changes when somebody joins.

Asked on every open

Rather than once per session, because that is what keeps the line current after a join or a leave — and it normally costs nothing, per the cache above. A request in flight blocks a second one, and whatever is already known stays on screen until the new answer lands, so re-opening a conversation never blanks the line. MembersLost releases the guard on a refusal so the next open asks again, exactly as a lost page does.

Rosters are cleared with the rest of the account state on logout: group membership is the departing account's view of who is where.

Verification

cargo fmt --all -- --check and cargo clippy --workspace --all-targets --all-features -- -D warnings clean; cargo test --workspace --all-features green (41 test binaries, no failures).

New tests:

  • chat_header: the line for a small group (You, Ana, Bruno), the cut-off with the count, the all-stranger group (50 members, and 1 member / 1 other for the singular), an empty roster drawing nothing, and the existing subtitle cases carried over.
  • ipc: a_group_roster_survives_both_directions_of_the_wire pins the answer's exact bytes, absences included — a member nobody has named carries no name key and everybody but the reader carries no is_self — plus the request's bytes in the existing literal-JSON table.

Not run here: the wasm build (nightly plus -Z build-std). The changed GUI code is platform-agnostic and names nothing per target.

Not in scope

The rest of what get_metadata carries — description, admin badges, a members screen you can open. Those want a screen rather than a line, and each needs the uncached query this deliberately avoids.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EQsp672XkWkr5ypjZwsvtj


Generated by Claude Code


Summary by cubic

Closes #126. Group chat headers now show who is in the group instead of leaving the subtitle blank when no one is typing; typing indicators still take priority.

  • The daemon returns a connection-maintained GroupRoster through ClientRequest::GroupMembers, using cached Groups::query_info data without adding polling.
  • The header puts “You” first, shows up to six names from the existing NameBook, and counts unnamed or remaining members; empty rosters still produce no subtitle.
  • Requests run on every chat open, deduplicate while in flight, preserve the previous roster during refresh, and allow a later open to retry after failures.
  • Rosters clear on logout.

Migration

  • Bumps the IPC protocol to 27; v26 daemon and client pairs must be upgraded together.

Written for commit 69d5254. Summary will update on new commits.

Review in cubic

A group's header drew its name and nothing under it. The one thing this
side had was `Chat::participants`, which is not a roster: it fills as
senders are *seen*, so counting it told a fifty-person group it had one
member, and saying nothing was the honest answer left.

The membership list lives on the connection. The library keeps one per
group because the send path needs it, patches it as add/remove
notifications arrive, and invalidates it when the server calls its
snapshot stale — so the front end now asks the daemon for it
(`ClientRequest::GroupMembers`, answered with a `GroupRoster`, protocol
27), and the session reads it through `Groups::query_info`: cached, free
on a repeat, and a miss sends the participant hash so an unchanged group
is answered `not-modified` rather than downloaded.

The line names this account as "You" first, then whoever the address book
or a push name has named, and counts the rest — a member nobody has ever
named is counted rather than spelled out, since a LID-addressed group
would otherwise read "Unknown contact" six times over. An all-stranger
group therefore reads "50 members", which is the answer `participants`
could never give.

Asked on every open rather than once, because that is what keeps it
current after somebody joins; what is already known stays on screen until
the new answer lands.

Closes #126.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EQsp672XkWkr5ypjZwsvtj
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 35 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: 1a42741d-b197-4ee7-8a0a-af437e3085c0

📥 Commits

Reviewing files that changed from the base of the PR and between 39e8c9d and 69d5254.

📒 Files selected for processing (17)
  • crates/core/src/group.rs
  • crates/core/src/lib.rs
  • crates/daemon/src/server/requests.rs
  • crates/daemon/src/session_bridge/act.rs
  • crates/daemon/src/session_bridge/action.rs
  • crates/gui/src/app/mod.rs
  • crates/gui/src/components/chat_header.rs
  • crates/gui/src/session/frames.rs
  • crates/gui/src/session/mod.rs
  • crates/gui/src/views/chat.rs
  • crates/ipc/src/lib.rs
  • crates/ipc/src/protocol.rs
  • crates/ipc/src/transport.rs
  • crates/session/src/whatsapp/groups.rs
  • crates/session/src/whatsapp/history.rs
  • crates/session/src/whatsapp/mod.rs
  • docs/gotchas.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Both branches added an entry to `docs/gotchas.md` at the same place and
nothing else overlapped. Kept both, with the tray's unread icon beside
the tray reasoning it continues and the roster entry after it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EQsp672XkWkr5ypjZwsvtj
@jlucaso1
jlucaso1 merged commit d19d688 into main Sep 3, 2026
11 checks passed
@jlucaso1
jlucaso1 deleted the claude/issue-126-pr-fw57s8 branch September 3, 2026 17:26
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.

Group members not visible

2 participants