fix(desktop): disable spellcheck/autocorrect/autocapitalize on emoji picker search#1438
Merged
Merged
Conversation
…picker search The emoji picker's search input is rendered inside an open shadow DOM by emoji-mart — outside CSS and props cannot reach it, and emoji-mart exposes no prop to set these attributes. After mount, traverse the shadow root of em-emoji-picker and set: - input.spellcheck = false - autocorrect="off" - autocapitalize="off" emoji-mart mounts its custom element asynchronously, so the input is not present on first render. A MutationObserver watches the shadow subtree, applies the attributes once the input appears, then disconnects. The fast path handles re-mounts where the input is already present. Cleanup disconnects the observer on unmount so nothing leaks. Scoped to EmojiPicker.tsx — the single centralized picker. All call sites (reactions, composer, status) get the fix automatically. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…nputCorrections tsc TS18047/TS18049: the closure inside disableSearchInputCorrections captured picker.shadowRoot through the narrowed-but-re-widened path — tsc cannot narrow a variable across a nested function boundary. Fix: capture picker?.shadowRoot as a typed const (root: ShadowRoot) after the null guard, then reference root inside the closure. The variable is a new binding with a definite non-null type, so tsc is satisfied and behavior is identical. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The spellcheck/autocorrect fix added a shadow-DOM traversal via a host div ref. This shifted the relative ordering of emoji-mart's async connectedCallback focus call and Radix's focus-scope onOpenAutoFocus handler, causing Radix to win the race and steal focus from the search input. Fix: thread autoFocus into disableSearchInputCorrections. When autoFocus is true, applyAttributes() calls input.focus() after setting the three attributes. This fires on both the fast path (input already present) and the slow MutationObserver path, making our code the deterministic focus owner rather than racing emoji-mart's own attempt. Call sites that pass autoFocus=false (ComposerEmojiPicker and others that never requested autofocus) are unaffected. Regression guard added to custom-emoji.spec.ts: asserts the shadow input is shadowRoot.activeElement immediately after the picker opens — fails if Radix or any other mechanism steals focus. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Per Will's request (Option 2), the composer emoji picker now autofocuses its search input on open — the same deterministic shadow-DOM path that the reaction popover already uses. One-line change: pass autoFocus to EmojiPicker in ComposerEmojiPicker. Focus-restore on select is safe: insertEmoji calls editor.chain().focus() before setIsEmojiPickerOpen(false), so the Tiptap editor owns focus before Radix's onCloseAutoFocus fires. No focus-stranding risk. New E2E test asserts shadowRoot.activeElement === the search input on open, and that picking an emoji returns focus to the composer input. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Without onOpenAutoFocus suppression, Radix's FocusScope fires after our
MutationObserver-owned input.focus() and moves focus to the first tabbable
element in the PopoverContent — which is not the shadow-DOM search input.
Result: composer autofocus E2E test fails (isFocused === false).
Add onOpenAutoFocus={(e) => e.preventDefault()} to let our
disableSearchInputCorrections traversal own focus on open. Add
onCloseAutoFocus={(e) => e.preventDefault()} to let insertEmoji's
editor.chain().focus() stand on close rather than Radix returning focus
to the trigger button.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…smiss The unconditional onCloseAutoFocus suppression left keyboard focus on a detached shadow input (or document.body) when the user dismissed the picker via Escape or click-outside without selecting an emoji. Fix: thread an onClose prop through ComposerEmojiPicker. MessageComposer Toolbar passes () => editor?.commands.focus(), which runs in onCloseAutoFocus after preventDefault(). On the emoji-select path insertEmoji already called editor.chain().focus() before the popover closes, making the onClose call a focus no-op. On the Escape/click-outside path, onClose explicitly returns focus to the editor so the user can keep typing without an extra click. E2E: add Escape-dismiss test asserting message-input is focused. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
d34b6ce to
5226fc3
Compare
wpfleger96
pushed a commit
that referenced
this pull request
Jul 2, 2026
…etrics Consolidate PR #1445 (kind 44200 relay/core implementation) into PR #1441 (NIP-AM doc + full relay/core). The relay/core changes are reviewed and Thufir-cleared (pass 2, all axes 9/10). No commits were rewritten. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> * origin/duncan/nip-am-kind-relay: chore(fmt): run rustfmt on NIP-AM kind 44200 relay changes fix(relay/core): plug COUNT existence-leak and StopReason forward-compat for NIP-AM fix(relay/core): close result-level read gate for kind:44200 (NIP-AM) feat(core/relay): add NIP-AM kind 44200 (agent turn metrics) with relay plumbing feat(desktop): restore observer-feed regressions from #1381 and classify 4 new session/update types (#1412) fix(desktop): disable spellcheck/autocorrect/autocapitalize on emoji picker search (#1438)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The emoji picker's search input (rendered inside
em-emoji-picker's open shadow DOM by emoji-mart) had spellcheck, autocorrect, and autocapitalize enabled. Emoji names aren't grammatical — autocorrect and auto-capitalization just add wasted correction hops when searching.Why it's not a prop
emoji-mart renders the search
<input type="search">inside a shadow root. Shadow DOM blocks outside CSS and attribute props from reaching the input, and emoji-mart exposes no API to configure these attributes.Fix
EmojiPicker.tsxnow wraps<Picker>in a host<div ref>. AuseEffectcallsdisableSearchInputCorrectionsafter mount, which:em-emoji-pickercustom element in the host.MutationObserveron the shadow root watches for it, applies the attributes once it appears, then disconnects.Scoped to
EmojiPicker.tsx— the single centralized picker every call site renders (reactions, composer, status).Test
New E2E assertion in
custom-emoji.spec.ts: opens the reaction picker on the seeded reactable message, waits for the search input to be visible, then evaluates the shadow root directly to assertspellcheck === false,autocorrect === "off",autocapitalize === "off".Checks
(E2E runs in CI via Playwright; unit suite confirmed green above.)