mirror of
https://github.com/block/buzz.git
synced 2026-11-06 17:31:19 +00:00
fbfbf3d64f028da95368f8cd5c80ca0f77e482d0
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c39ee507b2 |
fix(mobile): find verified people who have no profile (#9086)
## What changes
Mobile now finds a person who has a relay-verified profile (NIP-FI, kind
30382) but never published a kind:0 profile. This happens, for example,
when someone skips profile setup. The relay's `POST /profiles/search`
already finds such people by their verified name. Each one comes back as
a kind:30382 head with no kind:0. Mobile kept only kind:0 results, so
these people did not appear.
- `RelaySessionNotifier.searchProfiles` keeps kind:30382 heads from the
endpoint.
- `VerifiedProfilesNotifier.searched` turns a search page into people:
- If the relay authority is not known yet, it first waits for NIP-11
discovery. Otherwise, a search that finished first would drop these
people for that query.
- It checks each head's signature with the directory's off-isolate proof
checker.
- Proven heads go into the verified directory, so the rows show verified
names right away.
- `searchedPeople` (in `verified_profile.dart`) builds one row per
person, in the relay's order:
- If the person has a kind:0, the newest kind:0 is used, as before.
- If not, it uses the proven head that the authority signed, provided it
parses as an active verified profile.
- These cases add no row:
- a head signed by another key;
- a head that names the relay key but has a bad signature;
- a retraction;
- a malformed subject.
- **Behavior change for all mobile mentions:** every verified person now
matches in mention ranking by their verified full name and username, as
well as by display name, including people who have a kind:0. The relay's
search matches these same three names. Without this, a person whose
display name is their username (`glilienfeld`, full name "Gabi
Lilienfeld") came back from the relay for "gabi", and then the local
filter dropped them.
- Callers:
- Mention search, which also serves Add members and New DM.
- The user rows in global search, through `directoryUsersFromSearch`.
The desktop app has the same fix for Manage members:
https://github.com/block/buzz-app/pull/2554
## Screenshots
These come from the real Add members sheet on an Android emulator. Only
the relay session, verified-profile discovery and the member actions are
faked. The names are made up. Jordan Park has a kind:0. jrivera has only
a verified profile, with the full name "Jordan Rivera". The search is
"jordan". The row shows the verified display name, which here is the
same as the username.
| Search finds jrivera | After tapping + on jrivera |
| --- | --- |
| <img width="300" alt="jrivera is found next to Jordan Park"
src="https://github.com/user-attachments/assets/d93b77b1-1bc7-4d09-afc1-f359616a192a"
/> | <img width="300" alt="jrivera is selected to add"
src="https://github.com/user-attachments/assets/ce6c7a71-b098-434e-8b0e-ed0de8d1a5a2"
/> |
## Tests
- `add_members_sheet_names_test.dart`: a journey in which Gabi has
`display_name` = `glilienfeld` and `full_name` = "Gabi Lilienfeld", and
NIP-11 discovery is slow.
- While discovery is pending, typing "gabi" shows nothing yet.
- When discovery finishes, Gabi appears with no further keystroke.
- A head from another key does not appear, and neither does a head that
names the relay key but has a bad signature.
- **Add member** adds Gabi's key.
- I removed each fix in turn: the full-name alias, the signature proof,
and the wait for discovery. The journey fails each time, for the
expected reason.
- `verified_profile_test.dart`: a table of `searchedPeople` cases.
- A kind:0 wins over a head, and the newest kind:0 counts.
- These find nobody: another signer, a retraction, an uppercase subject,
a non-profile kind, and any head when the authority is unknown.
- `relay_session_test.dart` and `channel_management_provider_test.dart`:
rows for the kept heads and the directory row.
- `flutter analyze` is clean, and the full `flutter test` passes on
6efef79cc: 4,462 tests.
**Note:** mobile on `main` does not compile at
|
||
|
|
9aa70124ba |
perf(mobile): validate each verified profile once, not on every publish (#9101)
Opening or resuming a big channel spent about 210 ms of UI-thread CPU validating verified profiles, measured in Dart CPU samples on an iPhone 12 mini (Profile build) across two separate channel-resume captures (213 ms and 211 ms in `VerifiedProfilesNotifier._snapshot`, nearly all of it in `parseVerifiedProfile`). Every publish rebuilt the profile map by validating every known head again, and a channel with many members publishes once per subscription batch as each lands, so the work grew with members times batches. Each subject's parse is now kept while its head and the authority stand. Only `checked` changes between publishes (rebatching unchecks and rechecks subjects), so it is applied to the kept parse without validating again. ### Validation - Added `a later batch reuses profiles already parsed`: after a second subject rebatches the first, each head is validated exactly once. It fails when the kept parse is ignored. - `flutter analyze` is clean and the full mobile suite passes. - Same phone and scenario after this change: `parseVerifiedProfile` no longer appears in the resume CPU samples, and main-isolate message handling over the 15 s after resume dropped from 604 ms to 491 ms (one run each). Frame build and layout time on resume is unchanged; that is separate work. - Automated app run on the same phone with this change: Home, search, the large channel and its resume after the reconnect grace period. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> |
||
|
|
cf8fbbba55 |
fix(mobile): keep verified names on screen in channels with over 1,024 members (#9111)
In a channel with more members than the verified-name interest limit (1,024), returning to the app made member names flip: a verified name changed to a profile name with a collision suffix (for example "Ankit · ykn9") or to an npub, then back once a refetch landed. Seen on an iPhone 12 mini in a 2,004-member channel; instrumented logging showed the verified-name map dropping from 956 entries to 0 on opening that channel. Tracking a large roster evicts the oldest-tracked subjects, and eviction dropped their records too. On resume, member and profile preloads track the evicted people again, which evicts the ones on screen, so their verified names disappear until the next fetch. Evicted records now stay as unchecked cache (up to 4,096, oldest untracked dropped first) and are rechecked when tracked again. Only tracked subjects accept new records, so the cache never grows from relay traffic. ### Validation - Added `a subject evicted by a large roster keeps its last known name`: it fails when eviction drops the record. - `flutter analyze` is clean. The full mobile suite passes except 10 push and notification-toggle tests that fail identically on unchanged main on this machine. - Same phone, 2,004-member channel, after this change: the verified-name map held steady (978 entries, growing to 1,828) through opening the channel and two resumes after 12 s and 20 s in the background, and the "ankit, lhoward" and "sfoo, Dani" rows never changed in the screen recording. Before, both flipped (to "Ankit · ykn9" and an npub) and back after each resume. - Automated app run on the same phone with this change: Home, search, the large channel, and its resume after the reconnect grace period. This touches the same `_snapshot` line as [#9101](https://github.com/block/buzz/pull/9101), so whichever lands second needs a one-line rebase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> |
||
|
|
0bdad8ea96 |
mobile: simplify verified profiles and prioritize full names (#9051)
Simplifies mobile identity presentation: removes verification checkmarks from messages, threads, mentions and profiles, plus issuer/time/disclaimer copy. Profiles show the community-verified full name as the title and the display name below, with fallback when no full name exists. Validation: mobile formatting/analysis, full Flutter suite (4,401 passed, 45 skipped), all 4 unconfigured-push checks, and pre-push hooks passed. Independent review found no blockers. Device and human visual checks pending. <details> <summary>Before / after snapshots</summary> Real Flutter profile widget with synthetic data, light/dark themes; widget previews, not device screenshots. Images are PR attachments only. | Theme | Before | After | | --- | --- | --- | | Light |  |  | | Dark |  |  | </details> <details> <summary>Inline messages — before / after</summary> Actual Flutter channel and thread widgets with synthetic verified-user data; local widget previews, not device screenshots. | Screen | Before | After | | --- | --- | --- | | Channel · light |  |  | | Channel · dark |  |  | | Thread · light |  |  | | Thread · dark |  |  | </details> Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> |
||
|
|
40fe4b5d9a |
perf(mobile): evict verified-profile interest in one pass (#9007)
## Summary Follow-up to [#8931](https://github.com/block/buzz/pull/8931), from Wes's [review note](https://github.com/block/buzz/pull/8931#discussion_r4456891410). [#8931](https://github.com/block/buzz/pull/8931) replaced the per-eviction scan in `VerifiedProfilesNotifier.track` with `keys.first`. That was much cheaper than main, but not constant time: removing from a Dart map leaves deleted slots, and each new `keys` iterator walks past them. One `track` call that overflows the 1,024-subject cap by E subjects still costs about E²/2 slot checks. Wes measured ~3 ms at 4,300 subjects, 62 ms at 8,600 and 205 ms at 17,200. Eviction now takes the overflow from a single iteration (`keys.take(overflow).toList()`), so it's linear. Which subjects are kept doesn't change: still the 1,024 most recently inserted. The [#8931](https://github.com/block/buzz/pull/8931) eviction test counted keys yielded by the map, which `keys.first` keeps at one per eviction, so it couldn't fail on the quadratic path. It now counts iterations started over the interest map and expects exactly one per `track` call. Main's scan and the merged `keys.first` loop both start one per eviction. ### Testing - Falsification: with the merged `keys.first` loop restored, the new test fails (`Expected: <1>, Actual: <3276>`). With this change it passes. - `flutter test test/shared/verified_profiles/`: 31 passed. The existing "keeps the 1,024 most recently tracked subjects" journey still passes, so retention is unchanged. - `just mobile-check`: clean. - `just mobile-test`: 4,227 Flutter tests passed, 40 skipped, plus 4 push and 27 crypto tests. - Automated app run: installed this build on an iOS 27 simulator (iPhone 17 Pro) signed in to a Builderlab community, opened the inbox, a channel timeline with images and the image viewer, and the channel details member list, which all track verified-profile subjects. No errors. That community is small (30 members), so the overflow path itself is covered by the unit tests, not the app run. - Agent review: an independent agent review found no blockers. - Human test: waived under the mobile testing exception; the automated app run above replaces it. Not covered: Android, and no new device profile (Wes's AOT numbers above are the cost model; this removes the E² term). buzz-review-completed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0e5a69962e |
perf(mobile): evict verified-profile interest in constant time (#8931)
## Summary `VerifiedProfilesNotifier.track` trims its interest map to 1,024 subjects, but it finds the oldest subject by scanning every entry, once per eviction. Opening #announcements (4,302 members) evicts about 3,300 subjects, which is roughly 3.4 million comparisons. On a 12 mini Profile build that was 316 ms of main thread, inside the channel's 134 to 167 ms opening frames. It runs whether or not the relay serves verified profiles, so it also affects the prod Block community. A subject's interest is set once, on insertion (`??=`), so the map's insertion order is interest order. The oldest subject is therefore `keys.first`, and eviction is constant time. Which subjects are kept does not change. ### Related issue None found. ### Testing - New `verified_profile_directory_test.dart` case: tracking 4,300 subjects keeps exactly the 1,024 most recently tracked ones, and tracking a subject again does not refresh its age. This test checks that behavior is preserved, and it passes on main too. The speedup comes from removing the per-eviction scan, which the device CPU profile attributed. - `just mobile-check` passes. `flutter test test/shared/verified_profiles/` passes (30 tests). The pre-push hook ran the mobile unit tests. - Device (12 mini, Profile build, opening #announcements, first 5 s; runs not matched): `VerifiedProfilesNotifier.track` inclusive CPU 316 ms before, 14 ms after. Opening frames of 134 to 141 ms remain, mostly the channel name scope (follow-up). Not covered: a human test (waived under the mobile testing exception), Android. Separately, the 1,024 cap still churns subscriptions for rosters larger than that. This PR doesn't change that design. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
985de41f14 |
feat(mobile): show NIP-FI verified names (#8858)
Stacked on #8700. Review only the last commit until #8700 merges. NIP-FI relays can publish a relay-signed verified name for each member who signs in with an organization assertion (kind 30382, specified in #8745 and implemented in the relay in #8774). Desktop shows these names in block/buzz-app#2005. Mobile showed only self-declared kind 0 names, so a verified Block name looked no different from one anyone could set. This adds verified names to mobile, following the spec and the desktop client's validation rules. **What people see** - A verified member's name is the issuer-verified display name wherever mobile resolves names through identity names (message headers, threads, DM titles, member lists). - Message headers and the profile sheet show a verification mark. The profile sheet also shows the full name when present, "Verified by {issuer}", "Last verified {time ago}", and marks the record as cached when it hasn't been reconciled with the relay on this connection. Verification is described as historical, never as current access. - Nothing changes on relays that don't advertise `federated_identity.public_profile`, and marks never come from kind 0 or NIP-05. **How it works** - NIP-11 discovery decides whether marks may show: a successful response without `public_profile` hides them (cached records are kept), and a failed or malformed response keeps the last state. Discovery refreshes on connect and every 5 minutes while names are tracked. - Subjects are tracked as the profile cache resolves them, and fetched in batches of up to 128 with `{kinds:[30382], authors:[self], #d:[...]}`. Live updates and retractions apply on the same subscription. A full batch that omits subjects is not treated as proof they have no record. - Each event's id and signature are verified (in an isolate) before use, and it must be authored by the community's NIP-11 `self` key with exactly the specified tags (plus the optional `full_name` the relay emits). Names follow the spec's NFC and character rules; `unorm_dart` (MIT, pure Dart) is added for the NFC check because Dart has no built-in normalizer. - The cache is per community and is cleared when the relay's `self` key changes. **Not yet covered** - Some surfaces still read the kind 0 name directly: the members sheet initial, and search and inbox rows. The mark isn't shown in search rows, inbox rows, or member tiles. (The mention picker now matches, ranks and inserts by the verified name and shows the mark, and it also offers cached verified people outside the channel whose kind 0 name differs.) - NIP-FI says clients MUST show `issuer_label` beside every verified name. Here it appears only in the mark's tooltip and accessibility label and in the profile sheet, and verified names reaching DM titles and member lists show no mark or issuer. Follow-up: show the issuer inline wherever a verified name is shown. - Live updates are coalesced to the newest per subject before signature proof, ordered by unverified `created_at`/`id`. Only a misbehaving relay could exploit this (the subscription is `authors:[self]`), and it could already withhold events. Follow-up: prove first, then coalesce among valid events. - The verified `username` isn't shown beside the name in message headers (the NIP-05 handle still is). - Collision qualification relies on the existing name-disambiguation policy rather than the spec's exact self-declared-name rule. ### Validation Added tests: - Validation: discovery enabled, disabled and malformed; wrong author; mismatched or duplicate `d`/`p`; extra or duplicate tags; invalid usernames; non-NFC and whitespace-padded names; `verified_at` after `created_at`; bad signatures; retractions. - Directory: discovery gating, batching, truncation, live retraction, reconnect reconciliation, and disable hiding cached records. - Directory failures: a pre-EOSE flood, failed proof retried on refresh, oversized events (by total size, id or signature) dropped before proof, live events coalesced, a retry before or after the first EOSE never proving absence, a retry during proof not promoting records to checked, and a newer signed malformed projection clearing the older one. - Mention picker: a verified name finds, ranks and inserts the verified person; the self-declared name still finds them; a verified person outside the channel is found by verified name even when an impostor copies that name into kind 0. - Identity names: a verified name outranks the kind 0 name. - UI: the mark in message headers and the profile sheet details, no mark for a cached record, and a single accessible label. Full `mobile-check` and `mobile-test` pass. **Live relay (checked 2026-11-01):** not yet run end to end. - Staging now advertises the feature: the bb-block staging relay's NIP-11 (`buzz.block.builderlabstaging.xyz`) has `federated_identity.public_profile` with `kind: 30382` and `multiple_issuers: false`, bound to `self` `f7f4df94…dcf1`. So #8774 is deployed and enabled there. The other staging relays (`communities.public.builderlabstaging.xyz` and its `team.` host) advertise no `federated_identity`, and the client shows nothing on them. - I couldn't confirm that real 30382 events are served or render them. The relay requires NIP-42 auth plus a client-attached NIP-FI assertion, which needs an interactive Builderlab/Okta sign-in on a physical device. SSO doesn't work in the iOS simulator. The remaining step is a device run signed in to the staging Block community, with screenshots of a verified name, its mark and its details. **Shipping behind discovery gating:** this is safe. The client reads and shows verified names only when a successful NIP-11 discovery advertises `public_profile` bound to `self`. It accepts only kind 30382 events signed by that key that pass id and signature verification and strict shape checks. On every relay without the feature, which is every relay but bb-block staging today, it shows nothing new and changes no names. A failed or malformed discovery never turns the feature on, and it marks cached records as unchecked, which hides their mark. The subscription and reconciliation paths are covered by the tests above, and Carl's review reran retry probes through the real `RelaySessionNotifier`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |