Repository navigation
perf(identity-names): re-render a names consumer only when a label it resolved changes - #2100
Conversation
… resolved changes useidentitynames re-rendered every consumer on any profile, agent-library or provider change. each hook now records the labels it resolved, keyed by the full resolve arguments, re-resolves them on a names change and re-renders only on a difference. past 500 recorded labels it falls back to re-rendering on every change. the mention chooser and composer chips read names scopes directly, so they follow the names revision themselves while open or holding recipients. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kalvin Chau <kalvin@block.xyz>
each consumer's snapshot re-resolves its recorded labels only when the names revision moved, and the record restarts on the first render at a new version, so a change between two labels of one render is no longer hidden. callers that read names.lookup or names.scope share usenamesrevision. adds tests for a mid-render change and strictmode. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kalvin Chau <kalvin@block.xyz>
composer chip labels come from names.lookup in the memo that already follows the names revision, so fresh candidate arrays no longer grow a watched-label record on every names emit. a resolve callback kept from an older render adds to the current record instead of restarting it. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kalvin Chau <kalvin@block.xyz>
notes, solo share, todos and search built candidate arrays on every render. watched labels are keyed by array identity, so each render grew the record toward its cap. memoize the built arrays and share one empty member list. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kalvin Chau <kalvin@block.xyz>
the people block rebuilt its candidates and display facts every render, so each keystroke grew the watched-label record. it now resolves those labels through names.scope, normalized once per render, and follows the names revision while people results are shown. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kalvin Chau <kalvin@block.xyz>
key the watched-label record by pubkey and fallback. a label shown again under a new candidate or display-fact list, as after a member joins, re-renders on the next names change and starts a new record, instead of keeping and re-resolving every earlier list. drops the array identity ids. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kalvin Chau <kalvin@block.xyz>
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket, Mantis, and Groot reviews.
Reviewed head a4e09fddea27f49727cefdc4941528991f2818e0 against base c253fc051e1aa7acb4c6c7f2f64f8b779e2a41cd. No required changes found. Source assessment: minimalness, elegance, and correctness each 9/10.
The per-consumer record preserves the existing subscription lifetime and conservative overflow behavior. The direct scope/lookup consumers retain explicit revision subscriptions, and the new React tests cover relevant name changes, scope changes, stale callbacks, and render/subscription ordering. The test-layer choice is appropriate; browser cases added/removed: 0/0.
Optional efficiency note: in src/bundled/channels/useSoloShare.ts:203-207, the memo depends on destinationMembers, which is recreated at lines 98-105 whenever the destination roster is ready. Memoizing that derivation would let this path keep its candidate array stable across unrelated renders. This preserves prior behavior and is not a correctness finding.
Source-only review: no tests, builds, app runs, or PR code execution performed. The hosted check snapshot showed CI required successful and Windows native validation skipped. Benchmark gains and reported local test counts were not independently reproduced; native/live behavior, focus, and runtime concurrency remain unverified. Public PR text was inspected; it contains no attached media.
the deduplicated destination roster was rebuilt every render, so the display-key memo and the names record saw a new candidate list each time. memoize it on the channel's member list. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kalvin Chau <kalvin@block.xyz>
|
🤖 Thanks. Applied the optional note in 7f61de6: |
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord’s automated follow-up source review via Wes’s account, integrating Rocket, Mantis, and Groot.
Reviewed head 7f61de62584bdb9231f142de770e8350c777c737 against base c253fc051e1aa7acb4c6c7f2f64f8b779e2a41cd, focusing on the one-file change since reviewed head a4e09fddea27f49727cefdc4941528991f2818e0. No new findings. The previous optional useSoloShare memoization note is addressed: destinationMembers now retains its identity while the ready member array is unchanged, and the existing destination/readiness/cache guards remain intact. Sharing still obtains its authoritative audience independently.
Minimalness, elegance, and correctness: 9/10 each on source review. Tests and browser-case counts are unchanged. No tests, builds, app runs, or PR code were executed; performance and live behavior were not independently verified. A single hosted-check snapshot still had JavaScript, Rust, and browser jobs running; Windows validation was skipped. Public PR text was inspected and has no attached media.
…isibility * origin/main: feat(identity): first-run setup store, step table and pages (composition follows in the next PR) (#1819) perf(identity-names): re-render a names consumer only when a label it resolved changes (#2100) fix(search): hold an early ⌘K Enter until results land (#1910) ci: 14 WebKit and 4 Vitest shards; retry preview publish and tool fetch (#1974) perf(channels): re-render working-agent rows only for their agents' profiles (#2101) test(messages): measure coordination text relative to its row (#2096) test: serve artwork in reaction preview geometry fixture (#2068) feat(desktop): send app version as user-agent on native relay requests (#2111) test: stabilize reaction overlay fixture media (#2106) [1/5] Keep protected launches on the startup Agents root (#2044) fix(mcp): tag community emoji in agent messages and edits (#2102) feat(identity): show when the organization sign-in ends in settings (#2097) feat(channels): simplify access choices in the members dialog (#2030) fix(profiles): show nip-fi verified name read-only in profile settings (#2099)
Problem
useIdentityNamessubscribed every names consumer to one global names revision. That revision bumps on every profile-directory, agent-library or provider emit. In a large channel, each arriving profile (joins, authors loading) re-rendered every message row, header, sidebar label and composer that shows a name, even when no visible label changed. In the big-channel scale cell this was about 25k of 45k renders per 20 s of idle.Change
identity-names/react.ts: each hook instance records the labels it rendered.useSyncExternalStoresnapshot. When the names revision has moved, it re-resolves the recorded labels and bumps its version only if one differs. The snapshot never clears the record. The first render at a new version starts a new one.names.lookupornames.scopeinstead of the callback. They now follow the names revision through a shareduseNamesRevision(names, active), and only when it matters.RichComposerInputtakes its chip labels fromlookupas well, so it no longer usesuseIdentityNames:use-mention-choices.ts, while the chooser is open.RichComposerInput.tsx, while the draft has recipients.NotesPage,useSoloShare,TodosPanelandSearchResultspassed candidate arrays rebuilt on every render. They now memoize them or share one empty list, so their watched labels stay bounded. The People results inSearchResultsbuild a new candidate scope on every render. They now resolve through onenames.scopeper render and followuseNamesRevisionwhile shown.consumers.test.tsx: the sidebar probe read the channel list without subscribing and relied on the names revision to re-render. It now subscribes to the list.react.test.tsx: an unrelated profile does not re-render, while a namesake or rename does. It also covers:team-mentions.test.tsx: an open chooser relabels on a names-only change.Evidence
Browser scale harness (
tests/browser): 100 channels, one with 3000 members, a join every 2 s, background traffic, 20 s idle. Medians of 6 runs per OS.This change alone, against main:
With the working-agent row change (separate PR) on top, final version:
A normal 100-channel, 10-tab cell is unchanged within noise. The label comparison now runs on each names emit. Its cost is included in the idle numbers above, where the cell loads 3000 member profiles and a member joins every 2 s.
The current revision renders the same as the measured one in the big-channel cell, where a member joins every 2 s: 9,103 idle renders and 9,305 switch renders, both unchanged (local Chromium, one run).
Tests:
tsc --noEmitand biome are clean.🤖 Generated with Claude Code