Skip to content

fix(core): handle Enter on Android via beforeinput and keypress - #3031

Open
YousefED wants to merge 4 commits into
mobile/link-popoverfrom
mobile/android-enter
Open

fix(core): handle Enter on Android via beforeinput and keypress#3031
YousefED wants to merge 4 commits into
mobile/link-popoverfrom
mobile/android-enter

Conversation

@YousefED

@YousefED YousefED commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Top of the stack, on #3030. Fixes #3001.

The bug

On Android, prosemirror-view deliberately bails out of keydown handling (the IME reports composing keys as keyCode 229, so key identity can't be trusted) — Enter never reached the keymap. Its DOM-diffing fallback fails to recognize the split in BlockNote's nested block DOM and corrupts the document instead: Enter inserting a space, doing nothing, or breaking tables.

The fix

Two interception points in KeyboardShortcutsExtension, Android-only, sharing one dispatchSynthesizedEnter helper (which restores the pre-keydown DOM flush prosemirror-view's bail skips, so the keymap never runs against a stale selection):

  • beforeinput (insertParagraph / insertLineBreak): the IME path — the intent arrives unambiguously regardless of what the keyboard reports.
  • keypress: the hardware/synthetic keyboard path. prosemirror-view's own keypress handler cancels the browser default for cross-block selections without doing anything in their place, so Enter over a selection spanning blocks was a silent no-op.

Tests

  • androidEnter.test.tsx covers three routes: keypress, the synthetic beforeinput-without-keypress sequence (only a real IME produces it, so it's dispatched as a synthetic InputEvent — proven red with the interception removed), and cross-block selections.
  • This layer also widens the android instance to the suites with distinct consumers of the synthesized Enter (keyboardhandlers: the keymap chain; emojipicker: the suggestion menu's own key handling) — held out of the test-infra layer precisely because, before this fix, every test that presses Enter failed under the emulation. Un-skipping Check Enter when selection is not empty there is the suite-level proof the keypress hole is closed. A follow-up commit then fixes the instance itself: the shared setup had been forcing a scaled desktop-width iframe onto it (displacing positional input — long misread as "mouse idioms don't translate"), so it now tests true phone geometry with self-healing touch emulation, and the include list is grounded per entry on one principle — a suite runs there when it can go red for a mobile-conditional reason no other suite pins (form/ and copypaste/ dropped under that bar).
  • Both IME delivery variants were confirmed against real keyboards during development (Gboard sends keydown 229 + beforeinput: insertParagraph; AOSP LatinIME sends 229 + a real keydown — discovered by pressing the emulator's on-screen Enter). Each variant is pinned red-first in androidEnter.test.tsx; the device suite that made the discovery is parked on mobile/emulator-layer (test(device): local emulator layer — real Chrome/Gboard as normal CI #3034).

@vercel

vercel Bot commented Aug 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
blocknote Error Error Sep 1, 2026 6:35pm UTC
blocknote-website Error Error Sep 1, 2026 6:35pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 4f8b80c4-cf40-4488-ac49-a772df60e951

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Aug 31, 2026

Copy link
Copy Markdown

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/@blocknote/ariakit@3031

@blocknote/code-block

npm i https://pkg.pr.new/@blocknote/code-block@3031

@blocknote/core

npm i https://pkg.pr.new/@blocknote/core@3031

@blocknote/diagram-block

npm i https://pkg.pr.new/@blocknote/diagram-block@3031

@blocknote/mantine

npm i https://pkg.pr.new/@blocknote/mantine@3031

@blocknote/math-block

npm i https://pkg.pr.new/@blocknote/math-block@3031

@blocknote/react

npm i https://pkg.pr.new/@blocknote/react@3031

@blocknote/server-util

npm i https://pkg.pr.new/@blocknote/server-util@3031

@blocknote/shadcn

npm i https://pkg.pr.new/@blocknote/shadcn@3031

@blocknote/xl-ai

npm i https://pkg.pr.new/@blocknote/xl-ai@3031

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/@blocknote/xl-docx-exporter@3031

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/@blocknote/xl-email-exporter@3031

@blocknote/xl-multi-column

npm i https://pkg.pr.new/@blocknote/xl-multi-column@3031

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/@blocknote/xl-odt-exporter@3031

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/@blocknote/xl-pdf-exporter@3031

commit: 003075f

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://TypeCellOS.github.io/BlockNote/pr-preview/pr-3031/

Built to branch gh-pages at 2026-09-01 09:03 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@YousefED
YousefED force-pushed the mobile/android-enter branch from fd54794 to 9e471b2 Compare August 31, 2026 17:29
@YousefED
YousefED force-pushed the mobile/android-enter branch from 9e471b2 to 62b914e Compare August 31, 2026 17:36
@YousefED
YousefED force-pushed the mobile/android-enter branch from 62b914e to 4ad7e77 Compare August 31, 2026 17:42
@YousefED
YousefED force-pushed the mobile/android-enter branch from 4ad7e77 to 581489e Compare August 31, 2026 17:49
@YousefED
YousefED force-pushed the mobile/android-enter branch from 581489e to 5a57b7c Compare August 31, 2026 17:51
@YousefED
YousefED force-pushed the mobile/android-enter branch from 5a57b7c to d4e0efd Compare August 31, 2026 17:59
@YousefED
YousefED force-pushed the mobile/android-enter branch from d4e0efd to 010102c Compare August 31, 2026 18:20
@YousefED
YousefED force-pushed the mobile/android-enter branch from e20ec37 to 0f53ee7 Compare August 31, 2026 20:07
@YousefED
YousefED force-pushed the mobile/android-enter branch from 0f53ee7 to b1ff1c7 Compare September 1, 2026 00:53
@YousefED
YousefED force-pushed the mobile/android-enter branch from b1ff1c7 to 789c9be Compare September 1, 2026 06:01
@YousefED
YousefED force-pushed the mobile/android-enter branch from 789c9be to a7a836b Compare September 1, 2026 07:01
@YousefED
YousefED force-pushed the mobile/android-enter branch from a7a836b to 6b4ee68 Compare September 1, 2026 08:10
@YousefED
YousefED force-pushed the mobile/android-enter branch from 6b4ee68 to 003075f Compare September 1, 2026 08:48
On Android, prosemirror-view deliberately bails out of its keydown
handling: the IME reports composing keys as keyCode 229, so the key
identity can't be trusted. Enter therefore never reached the keymap and
pressing it did nothing — no new block, no list continuation.

`beforeinput` carries the intent unambiguously (`insertParagraph` /
`insertLineBreak`) regardless of what the IME reports, so the shortcuts
extension intercepts it there and runs the same keymap command. Only on
Android, and only when not composing, so every other platform keeps the
existing path.

This also unblocks running the core behavioural suites under Android
emulation. They were held out of the android instance in the test-infra
change precisely because of this bug — every test that presses Enter to
make a second block failed there — so the instance's include list grows
here, where it can be green.
The beforeinput interception only covers the IME path. With a hardware or
synthetic keyboard, Enter arrives as a keypress instead — and
prosemirror-view's own keypress handler cancels the browser default for
cross-block selections without doing anything in their place (its
cross-parent branch skips newline characters), so Enter over a selection
spanning two blocks was a silent no-op.

Intercepting keypress too closes that hole, and the two paths now share one
`dispatchSynthesizedEnter` helper rather than repeating the flush-then-
synthesize sequence. The `domObserver` reach-through is typed against
`EditorView` instead of `typeof view`.

Test coverage goes from one path to three — keypress, beforeinput, and the
cross-block selection — and `Check Enter when selection is not empty` no
longer has to be skipped on the android instance, which is the suite-level
proof that the keypress hole is closed.

Also makes `Check Delete before shallower block` deterministic: it relied on
ArrowUp's goal-x landing on a particular side of a character boundary, which
varies with subpixel metrics and had been flaking across engines.
The popover form-submission tests exist because of Android bugs, yet only
ran on the desktop engines. The android instance is chromium, so even the
CDP composition tests run there; the keyboardhandlers and emojipicker
suites join for their distinct consumers of Enter handling. All pass
under the emulation.
The shared browser setup forced its 1280x720 iframe onto every project —
on the android instance (a 393x727 phone window) the harness then scaled
that desktop-width iframe down to fit, so every suite without its own
per-test viewport was silently testing desktop layout, optically shrunk.
Positional input was displaced by the same transform, which had been
misread as 'mouse idioms don't translate to touch emulation'. The setup
now sizes the iframe per project.

Touch emulation also gets self-healing: Chromium's beyond-viewport
screenshot capture (captureBeyondViewport, sent by Playwright for any
element taller than the viewport) can silently drop the context's touch
emulation. A restoreTouchEmulation command (persistent CDP session —
Emulation overrides revert when their session detaches) re-arms it before
every android test, and ensureTouchEmulation runs as an automatic
assertion right after, so no suite calls it manually anymore. The assert
stays because it guards a different failure than the heal: the mechanism
itself breaking (provider contextOptions silently ignored, an upgrade
rewiring the provider).

At true geometry the include list is re-grounded on one principle,
stated per entry in the config: a suite runs on this instance when it
can go red for a mobile-conditional reason no other suite here pins.
form/ drops out — its popover suite drives the desktop link toolbar
(hover, clipped at phone width) and its Enter mechanics are pinned
red-first by mobile/ and keyboardhandlers/. copypaste/ drops out — the
clipboard path has no platform conditionals at all, and its Enter
presses are setup scaffolding for routes androidEnter pins directly.
emojipicker/ stays: Enter-to-select goes through the suggestion menu's
own key handling, a distinct consumer of the synthesized-Enter route.
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.

Enter, newline, and down arrow broken on Android

2 participants