Skip to content

fix(desktop): cap emoji picker search-icon size in shadow root - #5229

Closed
kchung wants to merge 2 commits into
block:mainfrom
kchung:fix/emoji-search-loupe-size
Closed

fix(desktop): cap emoji picker search-icon size in shadow root#5229
kchung wants to merge 2 commits into
block:mainfrom
kchung:fix/emoji-search-loupe-size

Conversation

@kchung

@kchung kchung commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

What

Cap the emoji picker's search-icon size inside the <em-emoji-picker> shadow root so a runaway magnifying-glass ("loupe") can't balloon over the search field and emoji grid.

reported bug

Why

emoji-mart renders the search bar's loupe and clear controls as inline SVGs and sizes them with a stylesheet it injects into its own shadow root. When that stylesheet hasn't applied by the time the SVG paints, the loupe falls back to its intrinsic size and renders as a giant icon covering the search input and the "Frequently used" row (see screenshot).

This is not a Buzz regression:

  • The picker component was introduced once (feat(desktop): replace hardcoded emoji pickers with full emoji-mart picker #122) and the emoji-mart dependency (5.6.0) has never been bumped since — git log on EmojiPicker.tsx, desktop/package.json, and pnpm-lock.yaml shows only release-tag commits, no functional change to the picker.
  • Nothing in Buzz's CSS targets the search loupe. The shadow CSS we inject for the avatar editor sizes the category-nav icons (#nav svg) but never the search loupe.
  • The most likely trigger is the Tauri/WebKitGTK webview on Linux, where shadow-DOM style application can race first paint (same family as the issues in docs/linux-rendering-troubleshooting.md, though that doc doesn't cover this one).

How

Inject a tiny defensive stylesheet into every picker's shadow root that hard-caps the search-icon size. It:

  • Reuses the existing shadow-DOM plumbing in disableSearchInputCorrections, which every call site already runs (composer, reactions, status), so all pickers are covered — not just the avatar editor.
  • Is idempotent (#buzz-emoji-search-icon-fix id guard prevents re-injection).
  • Only ever shrinks a runaway icon: the 1.3em values match emoji-mart's intended glyph size, so it's a no-op when emoji-mart's own CSS is present.
.search .icon,
.search .icon svg,
.search > svg { height: 1.3em; width: 1.3em; }

.icon covers the current markup; svg directly under .search guards against markup shifts across emoji-mart versions.

Testing

  • biome check passes clean on the changed file (run via the repo's hermit biome).
  • Full tsc --noEmit / biome check . / unit tests could not be run locally in this environment: corepack can't fetch pnpm (self-signed cert in the chain), so desktop/node_modules isn't installed. Relying on CI to run the full desktop lint/typecheck/test gate.
  • Change is a small, JSX-free addition to an already-typed function using DOM APIs already used in this file.

Reviewer note: if you can repro the giant loupe on a Linux build, confirming it's gone with this branch would be the ideal manual check.


🐝 Filed from a Buzz channel. Opened as a draft per standing preference.

The emoji-mart picker renders its search magnifying-glass ("loupe") and clear controls as inline SVGs sized by emoji-mart's own shadow-root stylesheet. When that stylesheet hasn't applied at first paint — observed in the Tauri/WebKitGTK webview on Linux, where shadow-DOM style application can race the paint — the loupe falls back to its intrinsic size and balloons into a giant icon covering the search field and emoji grid.

Inject a small defensive stylesheet into every picker's shadow root that hard-caps the search-icon size. It reuses the existing shadow-DOM plumbing in disableSearchInputCorrections (which every call site already runs), is idempotent via an id guard, and only ever shrinks a runaway icon: the 1.3em values match emoji-mart's intended glyph size, so it is a no-op when emoji-mart's own CSS is present.

Co-authored-by: Kevin Chung <chung@squareup.com>
Signed-off-by: Kevin Chung <chung@squareup.com>
@kchung

kchung commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Verification — dev server + before/after

Booted the desktop dev server (vite, ?e2e=mock) and drove the emoji picker with a headless browser.

Reproduced the reported bug and confirmed the fix constrains it:

State Loupe SVG size
Bug (emoji-mart's icon sizing not applied, no defensive style) 288 × 288 px
With this PR's defensive style applied 20 × 20 px

Measured directly from the shadow root via getBoundingClientRect(). The 288px runaway matches the ~300px giant magnifier in the original report.

before-after

Left (red): the runaway loupe covering the search field + "Frequently used" row. Right (green): with this PR, the loupe is a small glyph tucked back inside the search box.

I also confirmed via the live shadow DOM that this PR's #buzz-emoji-search-icon-fix stylesheet is injected into the picker's shadow root and that its selectors match the real emoji-mart markup: <span class="icon loupe flex"><svg viewBox="0 0 20 20">…</svg></span>.

Full gate (run locally at HEAD)

  • tsc --noEmit: clean
  • biome check .: clean on the changed file (repo had 1 pre-existing unrelated warning)
  • Unit tests: 4535 passed, 0 failed

Note: Chromium reliably applies emoji-mart's shadow stylesheet, so the giant loupe does not occur there under normal conditions — it's the Linux/WebKitGTK webview that races the paint. The before/after above reproduces the failure condition explicitly (emoji-mart icon sizing removed) to demonstrate the defensive cap works regardless.

Co-authored-by: Kevin Chung <chung@squareup.com>
Signed-off-by: Kevin Chung <chung@squareup.com>
@kchung

kchung commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Closing per Kevin Chung's request — another contributor is picking up this work. Diagnosis + fix left here for reference.

@kchung kchung closed this Aug 7, 2026
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.

1 participant