Skip to content

perf(channels): keep openLink stable across tab changes - #1525

Merged
kalvinnchau merged 1 commit into
mainfrom
perf/thread-open
Oct 26, 2026
Merged

kalvinnchau merged 1 commit into
mainfrom
perf/thread-open

Conversation

@kalvinnchau

Copy link
Copy Markdown
Contributor

Opening a thread re-rendered every mounted message row in the channel timeline, even though none of them changed.

The workspace openLink callback in ChannelWorkspace listed the whole tabState object as a dependency. That object changes identity on every tab update, and opening a thread updates the tabs, so each click created a new openLink. The new identity reached every MessageRow and defeated its memo. The callback only calls setTabs, select and leaveVisit, which are stable per channel, so it now depends on those instead.

Measurements

Browser fixture: a 60-message channel, one "View thread" click on a visible root, Chromium, 4x CPU throttle. Each case was measured in two alternating rounds. Values are median [min–max].

Production build, 12 samples per case:

metric before after
click → first thread rows ms 216 [186–296] 182 [155–252]
click → settled ms 321 [294–449] 297 [260–413]
longest frame ms 219 [190–300] 186 [160–258]
script ms 341 [306–458] 309 [282–425]

Development React, where render cost is magnified, 10 samples per case:

metric before after
click → first thread rows ms 830 [771–1015] 633 [602–683]
longest frame ms 911 [853–1104] 724 [686–778]
LoAF blocking ms 1192 [905–1522] 788 [683–1145]

React commit instrumentation on the same click, development React: component renders went from 5308 to 3903. MessageRow renders went from 42 to 8, and the remaining 8 are thread-panel rows.

Testing

  • tsc --noEmit, biome check
  • vitest run: the full suite had 5 failures under load, all timeouts in unrelated files (inbox, notification settings, codex, relay startup, message edit). Those files pass when run on their own. src/bundled/channels and src/features/messages pass after the rebase.
  • Chromium browser specs: buzz-links, channel-tabs, external-links, navigation-thread-history, tab-close-others, thread-follow, thread-opening, thread-tab-moves, thread-unread, thread-window (51 passed)

🤖 Generated with Claude Code

- the workspace openLink callback depended on the whole tab state object,
  which changes identity on every tab update such as opening a thread
- each new openLink identity defeated MessageRow memoization, so every
  mounted timeline row re-rendered on thread open
- depend on the stable setTabs, select and leaveVisit actions instead

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Kalvin Chau <[email protected]>
@kalvinnchau
kalvinnchau requested a review from a team as a code owner October 22, 2026 01:32

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket (systems/performance/lifecycle), Mantis (correctness/security/concurrency), and Groot (product/accessibility/focus) reviews.

No actionable findings. Source-level assessment: 9/10 for minimalness, ownership and correctness.

The narrowed dependencies cover every tab-state action used by openLink. Those actions read current state through functional updates and change identity with the channel/session owner. Destination and route refs, connection/abort checks, and focus capture remain current at invocation. The callback reaches memoized message rows, supporting the intended reduction in unnecessary renders without changing navigation behavior.

Reviewed head: cdc6275
Base: 3d1938a

Scope: the complete one-file diff, tab-store actions, relevant consumers and lifecycle/focus paths, public PR description and commit metadata. No attached images or disclosure findings. No browser journeys were added or removed.

Validation limits: source-only; no tests, builds, app launches or benchmark reproduction. The 02:18 UTC hosted-check snapshot showed CI required and all executed checks successful; Windows native validation and flake-check jobs were skipped. The PR reports five local full-suite timeout failures and successful isolated reruns; those results and the reported performance improvements were not independently reproduced. Live keyboard/screen-reader behavior, native acceptance, visual checks and human acceptance remain unverified. This COMMENT is not an approval or merge authorization.

@kalvinnchau
kalvinnchau merged commit c336309 into main Oct 26, 2026
36 checks passed
@kalvinnchau
kalvinnchau deleted the perf/thread-open branch October 26, 2026 16:11
johnmatthewtennant added a commit that referenced this pull request Oct 26, 2026
…r-emoji-size

* origin/main: (26 commits)
  perf(profiles): notify once per batch of owner attestations (#1650)
  feat(pi): load Pi durable extensions and providers from Pi packages (#1610)
  Teach agents memory links and source metadata; frame memory as information (#1562)
  Make avatar choices visible in agent creation (#1631)
  fix(pi): take plugin wakes instead of ignoring them (#1612)
  perf(agents): parse activity records once per snapshot (#1449)
  perf(terminal): parse hidden terminal output in slices (#1459)
  perf(composer): reuse the document projection for editor reads (#1463)
  perf(read-state): load peer journals without a strict writeback (#1448)
  perf(channels): keep openLink stable across tab changes (#1525)
  fix: name the Cargo package Buzz so the dev build shows "Buzz" (#789)
  test(browser): re-type a dropped page name in the search palette (#1635)
  test(browser): fix two main CI timing failures (#1567)
  feat(agents): deliver Attention watches and timers to conversations (#1578)
  fix(inbox): stop listing tag-only composer state as drafts (#1531)
  Choose Builderlab agent models at creation and in Settings (#1533)
  feat(communities): sync known communities through Builderlab accounts (#644)
  Tag the last-tagged agent when a Solo chat composer is shown empty (#1615)
  perf(usage): read Claude usage once, then only what changed (#1586)
  feat(chats): ask to share access when sending a chat link into a DM (#1614)
  ...
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