Skip to content

perf(inbox): load the emoji name table on first use - #2774

Merged
kalvinnchau merged 1 commit into
mainfrom
peon/r3-lazy-reaction-name
Nov 6, 2026
Merged

kalvinnchau merged 1 commit into
mainfrom
peon/r3-lazy-reaction-name

Conversation

@kalvinnchau

Copy link
Copy Markdown
Contributor

Problem

#2638 added reaction rows to the Activity inbox. InboxRow.tsx imports reactionName statically to build each row's accessible label. That one import put features/messages/reaction-name and its emoji-mart name table (@emoji-mart/data/sets/15/native.json, a 413 KB chunk) into the first-paint closure. Every launch now downloads, parses and evaluates the table and builds its Map, whether or not a reaction row is ever shown. MessageReactions and StatusEmoji already load the same module with import().

Change

  • A new useReactionName hook (features/messages/use-reaction-name.ts) loads reaction-name with import() once, caches it at module level and rerenders when the table arrives. This is the load-once code useEmojiName in StatusEmoji already had, moved so it can be shared.
  • ReactionRowButton and useEmojiName both use the hook, so one table load serves both.

Behavior

  • The visible row doesn't change.
  • Until the table loads, a Unicode reaction's aria-label uses the emoji character, for example "reacted with ❤️". It switches to the name, "reacted with heart", once the table loads. The first reaction row to mount fetches the chunk.
  • Custom-emoji reactions don't use the table and are unaffected.

Evidence

Cold start of a production build in Chromium at 4× CPU throttling, from navigation to the first frame with the app shell rendered. Main 8c0cb0c1e and this PR ran as 12 interleaved samples each on one Linux and one macOS workstation. The table shows medians.

metric Linux: main Linux: this PR Linux Δ % macOS: main macOS: this PR macOS Δ %
script 392.9 ms 374.4 ms −4.7% 273.6 ms 240.0 ms −12.3%
module evaluation 160.5 ms 131.0 ms −18.4% 102.6 ms 79.7 ms −22.3%
time to shell 1,302.1 ms 1,214.2 ms −6.8% 862.5 ms 808.5 ms −6.3%
eager JS (Vite manifest) 6,296,425 B 5,884,309 B −6.5% same same −6.5%

Lower is better.

  • The eager JS row is the static-import closure from index.html. The native chunk (412,954 B) leaves it, and main grows by 273 B for the dynamic import.
  • On macOS, every sample of this PR reached the shell faster than every sample of main.

Checks:

  • The src/bundled/inbox and src/features/user-status vitest suites pass (239 tests).
  • tsc and biome pass.
  • A production build has no remaining static importer of reaction-name.

🤖 Generated with Claude Code

#2638 imported reaction-name statically in InboxRow, which put the
emoji-mart name table (native.json, ~413 KB) into the first-paint
closure. move StatusEmoji's load-once import() into a shared
useReactionName hook and use it in both; reaction rows fall back to
the raw emoji until the table arrives.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Kalvin Chau <[email protected]>
@kalvinnchau
kalvinnchau marked this pull request as ready for review November 6, 2026 09:52
@kalvinnchau
kalvinnchau requested a review from a team as a code owner November 6, 2026 09:52

@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 automated source review via Wes’s account, with independent Rocket, Mantis and Groot coverage.

No actionable findings in the three-file change. The shared loader preserves per-consumer unmount guards and current-value lookup; Activity retains a Unicode fallback until names load, while custom shortcode labels and StatusEmoji tooltip behavior are preserved. Existing asynchronous label assertions were inspected.

Reviewed head e8b138060465965ddad4cd66d86940d9917b1bec against target base fb9cd7cf45c6d80368cc79a7c34535f8f3b2fd20 (merge-base 8c0cb0c1efb68068b89c813ad6f890d8b8f07401). Source only: no tests, builds, app, screen-reader or CI execution. The reported performance measurements and test results were not independently verified.

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 the shared hook keeps the module-level cache and the per-consumer unmount guard, useEmojiName behaves the same, and the inbox tests already wait for the named label with findByRole. one nonblocking test suggestion inline

reaction.emoji
? `:${reaction.emoji.shortcode}:`
: reactionName(reaction.content)
: (reactionName?.(reaction.content) ?? reaction.content)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 nonblocking: nothing tests the label before the table loads ("reacted with ❤️", then "reacted with heart" once it arrives). mocking ./reaction-name behind a deferred promise and asserting both labels would pin down the fallback this adds

@kalvinnchau
kalvinnchau merged commit daa550f into main Nov 6, 2026
116 of 117 checks passed
@kalvinnchau
kalvinnchau deleted the peon/r3-lazy-reaction-name branch November 6, 2026 16:11
wpfleger96 added a commit that referenced this pull request Nov 6, 2026
…leanup

* origin/main:
  test(mentions): serve the mentions fixture as a build so cold loads fit (#2776)
  test(browser): let a late publication land after its page closed (#2565)
  test(relay): wait for the verified owner before a guest edits an agent (#2413)
  test: wait for the companion default width measurement (#2750)
  test(browser): preserve conversation scroll while revealing actions (#2715)
  test(browser): wait for link hit-testing after thread close (#2792)
  perf(messages): skip micromark for plain prose bodies (#2780)
  perf(inbox): load the emoji name table on first use (#2774)
  perf(messages): cut main-thread work while a busy channel is open (#2729)
  Refine agent Attention cards, watch details, and status controls (#2717)
  fix(bestie): remove a Bestie from its agent card on any device (#2720)
  feat(agents): preview Builderlab agent card links with Add to Buzz (#2719)
tlongwell-block added a commit that referenced this pull request Nov 6, 2026
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>

* origin/main:
  test(messages): wait for the row measurement frame before thread catch-up dwell (#2837)
  Keep agent coordination history readable after work finishes (#2454)
  test(mentions): serve the mentions fixture as a build so cold loads fit (#2776)
  test(browser): let a late publication land after its page closed (#2565)
  test(relay): wait for the verified owner before a guest edits an agent (#2413)
  test: wait for the companion default width measurement (#2750)
  test(browser): preserve conversation scroll while revealing actions (#2715)
  test(browser): wait for link hit-testing after thread close (#2792)
  perf(messages): skip micromark for plain prose bodies (#2780)
  perf(inbox): load the emoji name table on first use (#2774)
  perf(messages): cut main-thread work while a busy channel is open (#2729)
  Refine agent Attention cards, watch details, and status controls (#2717)
  fix(bestie): remove a Bestie from its agent card on any device (#2720)
  feat(agents): preview Builderlab agent card links with Add to Buzz (#2719)
  Restore macOS Cmd-W window closing when no tab handles it (#2443)
  Make agent transcripts quieter and link finished replies to activity (#2424)
johnmatthewtennant added a commit that referenced this pull request Nov 6, 2026
…ve-enter

* origin/main: (21 commits)
  Make drafts and sent part of Messages (#2728)
  fix(canvas): render every Canvas within the save limit (#2839)
  test(messages): wait for the row measurement frame before thread catch-up dwell (#2837)
  Keep agent coordination history readable after work finishes (#2454)
  test(mentions): serve the mentions fixture as a build so cold loads fit (#2776)
  test(browser): let a late publication land after its page closed (#2565)
  test(relay): wait for the verified owner before a guest edits an agent (#2413)
  test: wait for the companion default width measurement (#2750)
  test(browser): preserve conversation scroll while revealing actions (#2715)
  test(browser): wait for link hit-testing after thread close (#2792)
  perf(messages): skip micromark for plain prose bodies (#2780)
  perf(inbox): load the emoji name table on first use (#2774)
  perf(messages): cut main-thread work while a busy channel is open (#2729)
  Refine agent Attention cards, watch details, and status controls (#2717)
  fix(bestie): remove a Bestie from its agent card on any device (#2720)
  feat(agents): preview Builderlab agent card links with Add to Buzz (#2719)
  Restore macOS Cmd-W window closing when no tab handles it (#2443)
  Make agent transcripts quieter and link finished replies to activity (#2424)
  fix(channels): wrap unread conversation stepping around the sidebar (#2682)
  fix(navigation): let bare buzz:// open Buzz without a broken-link notice (#2718)
  ...
ravarora2 added a commit that referenced this pull request Nov 6, 2026
Co-authored-by: BuzzPRReviewAssistant1 <a581a7e8115222acdc32ffb14bfce55d48c24478d98e155f8eba126b69b83c7b@buzz.block.builderlab.xyz>

* origin/main: (71 commits)
  test(activity): scope preview assertions to the request row (#2843)
  perf(search): fold people names once across palette keystrokes (#2570)
  fix(github): keep PR watches running when localStorage is full (#2699)
  Make drafts and sent part of Messages (#2728)
  fix(canvas): render every Canvas within the save limit (#2839)
  test(messages): wait for the row measurement frame before thread catch-up dwell (#2837)
  Keep agent coordination history readable after work finishes (#2454)
  test(mentions): serve the mentions fixture as a build so cold loads fit (#2776)
  test(browser): let a late publication land after its page closed (#2565)
  test(relay): wait for the verified owner before a guest edits an agent (#2413)
  test: wait for the companion default width measurement (#2750)
  test(browser): preserve conversation scroll while revealing actions (#2715)
  test(browser): wait for link hit-testing after thread close (#2792)
  perf(messages): skip micromark for plain prose bodies (#2780)
  perf(inbox): load the emoji name table on first use (#2774)
  perf(messages): cut main-thread work while a busy channel is open (#2729)
  Refine agent Attention cards, watch details, and status controls (#2717)
  fix(bestie): remove a Bestie from its agent card on any device (#2720)
  feat(agents): preview Builderlab agent card links with Add to Buzz (#2719)
  Restore macOS Cmd-W window closing when no tab handles it (#2443)
  ...

Signed-off-by: Ravneet Arora <[email protected]>
cheng-block added a commit to cheng-block/buzz-app that referenced this pull request Nov 6, 2026
…ime-hint

* upstream/main: (26 commits)
  Make drafts and sent part of Messages (block#2728)
  fix(canvas): render every Canvas within the save limit (block#2839)
  test(messages): wait for the row measurement frame before thread catch-up dwell (block#2837)
  Keep agent coordination history readable after work finishes (block#2454)
  test(mentions): serve the mentions fixture as a build so cold loads fit (block#2776)
  test(browser): let a late publication land after its page closed (block#2565)
  test(relay): wait for the verified owner before a guest edits an agent (block#2413)
  test: wait for the companion default width measurement (block#2750)
  test(browser): preserve conversation scroll while revealing actions (block#2715)
  test(browser): wait for link hit-testing after thread close (block#2792)
  perf(messages): skip micromark for plain prose bodies (block#2780)
  perf(inbox): load the emoji name table on first use (block#2774)
  perf(messages): cut main-thread work while a busy channel is open (block#2729)
  Refine agent Attention cards, watch details, and status controls (block#2717)
  fix(bestie): remove a Bestie from its agent card on any device (block#2720)
  feat(agents): preview Builderlab agent card links with Add to Buzz (block#2719)
  Restore macOS Cmd-W window closing when no tab handles it (block#2443)
  Make agent transcripts quieter and link finished replies to activity (block#2424)
  fix(channels): wrap unread conversation stepping around the sidebar (block#2682)
  fix(navigation): let bare buzz:// open Buzz without a broken-link notice (block#2718)
  ...

Signed-off-by: Cheng Peng <[email protected]>
Co-authored-by: Cursor <[email protected]>
matt2e added a commit that referenced this pull request Nov 14, 2026
## Why

`user-status.spec.mjs`, "statuses edit, synchronize, clear, reject stale
traffic and retain failed drafts", fails intermittently on WebKit with
an unexplained page error: `Unhandled Promise Rejection: TypeError:
Importing a module script failed.`

| Sighting | Lane |
|---|---|
| [9 Nov,
PR](https://github.com/block/buzz-app/actions/runs/42573912834/job/129017542663),
and again on [its
re-run](https://github.com/block/buzz-app/actions/runs/42573912834/job/129030209036)
| Flake check, WebKit 2/11 |
| [10 Nov,
main](https://github.com/block/buzz-app/actions/runs/42766553248/job/129664645706)
| Browser journeys, WebKit 9/18 |
| [11 Nov,
PR](https://github.com/block/buzz-app/actions/runs/42981535351/job/130393321764)
| Flake check, WebKit 2/12 |

The spec's check for unexplained page errors is deliberate and correct.
The bug is in the product.

## Root cause

`useReactionName` and `ReactionEmoji`'s `revealName` both ran `void
import("./reaction-name").then(...)` with no rejection handler.
`reaction-name.ts` statically imports the emoji-mart native table, so
the first Unicode status or reaction name starts a large chunk load.
When a navigation lands while that load is in flight, WebKit cancels the
request, the `import()` rejects, and the page reports an unhandled
rejection.

In the journey, the click that opens a free-text status starts the load
and the next `page.goto` cancels it. The traces show the chunk request
failing with "Load request cancelled" and the page error 4 ms after the
navigation. The unguarded import arrived with #2384 and moved into
`useReactionName` in #2774.

## Fix

Handle the rejection at both sites.

- A failed load leaves the name unresolved. In the hook the next mount
tries again. `ReactionEmoji` keeps the raw content and the next hover or
focus tries again.
- Both handlers `console.warn` with the error, so a chunk that keeps
failing, such as a 404 after a deploy with a stale shell, stays visible.
The comments note that only a new document fetches the chunk again,
because some engines keep a failed module fetch for the document's life.

The warning does not affect browser checks: the fixture records only
error-level console messages, and the page-error helper watches
`pageerror`. There is no spec or fixture change, and the page-error
check stays as it is.

The second commit is the review follow-up that replaced the first
commit's empty handlers with the warning and made the two unit tests
wait for it.

## Tests

- `use-reaction-name.test.tsx` (new) and a `MessageReactions.test.tsx`
case mock `./reaction-name` to reject. They assert the name stays
unresolved, that the warning is logged, and that no unhandled rejection
reaches the process. They then unmock and show the next mount or reveal
resolves.
- On the unfixed code both fail with the unhandled rejection.

## Evidence

| Scope | Result |
|---|---|
| This branch's head, macOS: `bin/pnpm exec vitest run
src/features/messages/use-reaction-name.test.tsx
src/features/messages/MessageReactions.test.tsx` | 2 files, 29 passed |
| Before the split from a combined branch, Linux: the same two files |
27 passed in 8 of 8 runs, no stderr output |
| Linux, WebKit: `user-status.spec.mjs`, `--workers=1 --repeat-each=8` |
8/8 passed, 11.7 to 12.7 s each |
| Triage, WebKit, with the native table request held across the
navigation | unfixed 3/3 failed with the CI error, fixed 3/3 passed |
| Before the split, Linux: Biome on the touched files, `tsc --noEmit` |
clean |

The count is 29 here and 27 in the commit messages because #3397 has
since added tests to `MessageReactions.test.tsx`.

## Not reproduced, not run

- The cancelled chunk did not reproduce on an idle host without holding
the request, so the WebKit repeat guards against regression rather than
reproducing CI timing.
- Not tried in the desktop app. The user-visible change is the absence
of an unhandled rejection, plus a console warning when the chunk fails.
- The local pre-push checks for this branch (whole-project type check,
related unit tests, design-system check) were not run. The two-file run
in the first row is the only check run on this branch's head. The type
check and Biome results are from the combined branch, where the same
change passed.
- No human has tested this yet, so `buzz-review-completed` is not set.

## Left alone

- A second, unrelated failure of the same journey ("Duration: Today"
read as "8 hours" at exactly 16:00 UTC) is fixed in its own PR.
- In the persistent-failure case each hover or new mount logs another
warning. That is bounded and only visible in developer tools.
- Open PRs #2896, #2120, #2085 and #2009 edit other parts of
`MessageReactions.tsx`, and #2794, #2154 and #2120 edit its test file. A
trial merge of each against this change is clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: mtoohey <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
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.

3 participants