Skip to content

Fix Members dates, name unnamed members, paste keys in channel add - #142

Merged
QuicksilverSlick merged 1 commit into
mainfrom
fix/members-date-unnamed-paste
Nov 9, 2026
Merged

QuicksilverSlick merged 1 commit into
mainfrom
fix/members-date-unnamed-paste

Conversation

@QuicksilverSlick

Copy link
Copy Markdown
Owner

Factory-Task: desktop/members-dates-unnamed-paste
Built-by: claude-code/claude-opus-5-5
Undo: revert this PR's merge. It changes only the desktop front end and the test mock. Nothing is stored, so there is nothing to clean up.

Three problems Russell found on 5 Nov 2026.

1. Every member showed "Added Nov 5, 2026"

Cause: relayMembersFromEvent stamped each member with the created_at of the kind:13534 membership snapshot. That's one replaceable event, re-signed on every roster change, so the date moved for everyone whenever anyone joined.

Fix: dates now come from the relay's kind:8000 "member added" announcements, one per add. Clients can't publish that kind, because required_scope_for_kind rejects it. If someone was removed and re-added, the newest announcement wins. A member with no announcement (the bootstrap owner, an allowlist backfill) shows no date instead of a wrong one.

  • createdAt is gone from RelayMember, so nothing can read the wrong date by accident.
  • The dates have their own query. Live snapshots overwrite the relayMembers cache with setQueryData, which would have wiped dates merged into it.
  • useLiveDirectoryUpdates refreshes the dates inside its existing 2 s window. That's at most one extra REQ per window, and only while the Members panel is open. invalidateRelayQueries covers reconnects.

Limit: it reads one page, the relay's maximum of 1,000 adds. A full page logs a console warning, and the oldest members then show no date. There's a ponytail: comment with the upgrade path. Today's roster is 6 members with 7 announced keys.

No relay change: the community is on Block's hosted relay, which the fork can't deploy to. relay_members.created_at holds the true date, but no API exposes it and the snapshot doesn't carry it.

2. "Unnamed member"

A member with no profile now shows a short npub, such as npub1a2d…yp60, so two of them can be told apart without hovering. The "Actions for…" labels are distinct too, and the remove dialog already showed the full key. The owner keeps "Community owner", since there's only one.

No local label: the app has no local-name store or surface. NIP-02 petname exists in the social.ts types, but nothing displays it. A label would need its own store and UI on every surface that shows a name, and the short npub already solves telling people apart. Add one if Russell wants names for people who never set a profile.

3. Pasting a key in "Add people and agents"

It didn't work: the sidebar only ran user search, which matches profile text and never keys. So someone with no profile couldn't be added from there. Now a pasted npub or hex key (any case, surrounding whitespace is fine) searches as its hex form:

Pasted key Result
Community member, not in this channel Add row. Enter also adds them.
Has a profile Add row with their name
Current channel member Found, with nothing to add
Agent the viewer may not use No row. The existing agent allowlist still applies.
No profile and not on the community's roster No row. It says why and who can fix it.
Lookup failed No row. It says the lookup failed.
Malformed "No matching people or agents."
Relay with no member list (open relay) Add row, since there's nothing to check against

A difference from ChannelMemberInviteCard: the relay checks channel policy only, so it would seat any 64-hex string: a typo, an event id, or someone who can't get into the community. That leaves a ghost member. The sidebar checks the roster first, and the invite card still doesn't. I left the card alone to keep this PR small.

Enter after a paste: Enter now uses the existing getKeyboardSearchSelection, so it can't add the previous search's top row before the new results render.

How it was checked

Against Block's live relay (dev build, launch-check member, read-only through a temporary log line that isn't in this diff):

  • The snapshot's created_at was 2026-11-05T15:52:34Z, the date Russell saw on every row.
  • 5 of 6 members got real dates, from 1 Sep to 5 Nov. The launch-check member reads 28 Oct, which matches when it was added.
  • The owner has no announcement, so that row shows no date.

Tests: Unit 7,205 + 193 jsdom pass. tsc, biome, file-size and pubkey-truncation checks are clean. Playwright on the 15 specs that touch the sidebar or roster: 396 of 400 pass locally. The other 4 are unrelated to this change: community-rail:822 and onboarding:2909 fail 3 of 3 here and on clean main too; community-rail:1474 passed 3 of 3 on rerun; channels:5580 is a page-boot timeout under machine load that passed 10 of 10 on both builds.

Each behavior was undone to confirm its test fails: date source, short npub, paste row, npub matching current members, profile lookup, roster gate, empty-roster guard, live refresh. The malformed-key test passes either way, as a negative guard should.

Agent review: an independent review found six issues, and all are fixed here: the refresh depended on desktop notifications, the key was missing from reconnect recovery, ghost members, one REQ per person on a multi-select add, Enter on stale results, and a failed lookup offering an agent as a person.

Not checked in the real app: the rendered Members list and sidebar against the live relay. The app-control grant was declined, and the launch-check member isn't an owner or admin, so it can't open Members normally. Both were checked rendered in Playwright against the mock. No real member was added, since that writes to the live community. Not covered by a test: the failed-lookup message, which only appears after about 7 s of retries. The first real look is Russell's own Settings > Members, and a paste in a channel's add box.

Needs a launch check when its train asks, since it touches desktop/.

🤖 Generated with Claude Code

Settings > Members showed every member as added today. Each row took the
created_at of the kind:13534 snapshot, one replaceable event that moves
on every roster change. Read dates from the relay's kind:8000
announcements instead, in their own query since live snapshots overwrite
the roster cache. A member never announced shows no date.

Show a short npub instead of "Unnamed member", so two can be told apart.

Let the channel add box take a pasted npub or hex key, so someone with no
profile can be added. The relay checks channel policy only, so a key with
no profile that isn't on the community roster gets an explanation rather
than an Add row: it would seat a typo or event id as a ghost member.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@QuicksilverSlick QuicksilverSlick added the lane:ready Builder is done: green, agent-reviewed, exercised. The steward puts it in the next train. label Nov 9, 2026
@QuicksilverSlick
QuicksilverSlick merged commit a09311c into main Nov 9, 2026
56 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane:ready Builder is done: green, agent-reviewed, exercised. The steward puts it in the next train.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant