Skip to content

ci: type on real Gboard against the workspace editor on an Android emulator - #3511

Open
christianhg wants to merge 4 commits into
mainfrom
ci/android-ime-composition
Open

christianhg wants to merge 4 commits into
mainfrom
ci/android-ime-composition

Conversation

@christianhg

@christianhg christianhg commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Composition bugs on Android only show up with a real keyboard. Synthetic beforeinput and composition events in browser tests don't reproduce what Gboard sends, so changes to the input pipeline have shipped with no Android signal at all.

This adds apps/android-ime, which builds a test page from the workspace editor and types into it with real Gboard on an Android 16 emulator (API 36, Chrome 133, Gboard 15.1). Japanese exercises composition and English exercises plain typing, since current Gboard no longer composes English. A plain contenteditable control runs next to the editor so a keyboard change can be told apart from an editor regression. The driver is plain Node 24 TypeScript with no runtime dependencies. It talks to the device over adb, to the keyboard through uiautomator2 and to the page through the Chrome DevTools Protocol.

The workflow runs on pull requests that touch the input handling paths, nightly on main and on dispatch, as one Japanese and one English job. It reports and does not gate. Each case is checked against the table in apps/android-ime/scripts/cases.ts and the verdicts go to the job summary. A job turns red only when the run itself broke (no boot, failed setup, or a case with no result after its retry), never because of an editor result. The Japanese case that types into an empty block is marked expected to fail, because Gboard restarts the composition after the first character there today, which leaves a stray k in front of the converted words. When a fix lands it shows up as an unexpected pass above the summary table.

Each case gets one retry, and since the driver never asserts editor output, a retry can't hide an editor result. Measured on this branch over 16 runs (the pull request run plus 15 dispatches), all 32 jobs went green, 5 of 96 cases needed their retry, and the Japanese empty-block case left the stray k in all 16 runs. The plain controls vary on their own: Japanese Gboard sometimes commits the words as kana before converting them (9 of 16 runs), and Chrome once dropped the trailing non-breaking space in English, so those two cases accept either text. The README covers running it, reading the summary and adding a case.


Note

Low Risk
New CI and test-only app under apps/android-ime; no production editor runtime changes, and editor verdicts do not fail the workflow.

Overview
Adds apps/android-ime, an end-to-end harness that types “kyou kyou” through real Gboard in Chrome on an API 36 emulator, against Vite-built plain.html and PortableTextEditable test pages. A Node 24 driver (adb, a TypeScript uiautomator2 client, CDP) records final text and composition metrics; scripts/cases.ts defines expectations, including a Japanese empty-editor case marked expectedToFail for the known stray-k / nested-composition bug.

Introduces .github/workflows/android-ime.yml: matrix jobs for ja and en, PR path filters on editor input code, nightly main, and workflow_dispatch. The workflow reports via scripts/report.ts to the job summary and does not gate on editor mismatches—jobs fail only when boot/setup/driver breaks or a case prints no RESULT. Also wires knip.json entries for the new app.

Reviewed by Cursor Bugbot for commit ccbda14. Bugbot is set up for automated code reviews on this repo. Configure here.

…e workspace editor

`apps/android-ime` holds a driver that types on real Gboard in an
Android emulator into a page in Chrome and reads the text and the
composition events back over the DevTools protocol. It is TypeScript
run directly by Node 24 with type stripping, so the tsconfig allows
erasable syntax only. adb goes through `child_process`, the screen
through the uiautomator2 on-device server, CDP over the global
`WebSocket`.

The driver, the device script, the Japanese keyboard setup and the
unit tests come over unchanged in behavior: every retry, wait and log
line, and the `RESULT` line format. Comments that pointed at an
earlier Python driver or an issue number now describe the present.

The test page is built with Vite, which every other app here uses,
from the workspace `@portabletext/editor` instead of a published
version. The plain `contenteditable` page stays as the control.
`build:page` is not a `build` script, so root `pnpm build` and turbo
never pick it up. Knip gets the scripts the shell runs and the page
Vite builds as entries.

The app declares no React types of its own: a new importer with
`@types/react` resolves to a newer version than the root pins and
moves unrelated entries in the lockfile.
@changeset-bot

changeset-bot Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: ccbda14

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel

vercel Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
portable-text-editor-documentation Ready Ready Preview Oct 29, 2026 2:07am UTC
portable-text-example-basic Ready Ready Preview Oct 29, 2026 2:07am UTC
portable-text-playground Ready Ready Preview Oct 29, 2026 2:07am UTC

Request Review

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Bundle Stats

✅ No significant changes.

All scenario measurements (37)

🗺️ @portabletext/block-tools / @portabletext/block-tools · @portabletext/block-tools / @portabletext/block-tools/rules · @portabletext/editor / @portabletext/editor · @portabletext/editor / @portabletext/editor/behaviors · @portabletext/editor / @portabletext/editor/plugins · @portabletext/editor / @portabletext/editor/selectors · @portabletext/editor / @portabletext/editor/traversal · @portabletext/editor / @portabletext/editor/utils · @portabletext/editor / defineSchema · @portabletext/editor / useEditor · @portabletext/editor / typical · @portabletext/html / @portabletext/html · @portabletext/html / @portabletext/html/rules · @portabletext/html / htmlToPortableText · @portabletext/keyboard-shortcuts / @portabletext/keyboard-shortcuts · @portabletext/markdown / @portabletext/markdown · @portabletext/markdown / portableTextToMarkdown · @portabletext/patches / @portabletext/patches · @portabletext/repair / @portabletext/repair · @portabletext/sanity-bridge / @portabletext/sanity-bridge · @portabletext/schema / @portabletext/schema · @portabletext/plugin-character-pair-decorator / @portabletext/plugin-character-pair-decorator · @portabletext/plugin-decorations / @portabletext/plugin-decorations · @portabletext/plugin-dnd / @portabletext/plugin-dnd · @portabletext/plugin-emoji-picker / @portabletext/plugin-emoji-picker · @portabletext/plugin-input-rule / @portabletext/plugin-input-rule · @portabletext/plugin-list-index / @portabletext/plugin-list-index · @portabletext/plugin-markdown-shortcuts / @portabletext/plugin-markdown-shortcuts · @portabletext/plugin-one-line / @portabletext/plugin-one-line · @portabletext/plugin-paste-link / @portabletext/plugin-paste-link · @portabletext/plugin-sdk-value / @portabletext/plugin-sdk-value · @portabletext/plugin-typeahead-picker / @portabletext/plugin-typeahead-picker · @portabletext/plugin-typography / @portabletext/plugin-typography · @portabletext/toolbar / @portabletext/toolbar · @portabletext/toolbar / typical · @portabletext/plugin-table / @portabletext/plugin-table · @portabletext/plugin-table / @portabletext/plugin-table/ui · Artifacts

Scenario Kind Bundle (raw / gzip) Gzip change Import time Import change
⚪ @portabletext/block-tools / @portabletext/block-tools export 338.0 KB / 40.0 KB None 188 ms -7 ms, -3.4%
⚪ @portabletext/block-tools / @portabletext/block-tools/rules export 15.2 KB / 2.1 KB None 5 ms -0 ms, -0.7%
⚪ @portabletext/editor / @portabletext/editor export 1.02 MB / 129.6 KB None 71 ms -4 ms, -5.2%
⚪ @portabletext/editor / @portabletext/editor/behaviors export 4.0 KB / 147 B None 2 ms -0 ms, -4.4%
⚪ @portabletext/editor / @portabletext/editor/plugins export 5.1 KB / 645 B None 8 ms -0 ms, -0.3%
⚪ @portabletext/editor / @portabletext/editor/selectors export 94.5 KB / 9.2 KB None 9 ms -0 ms, -3.9%
⚪ @portabletext/editor / @portabletext/editor/traversal export 43.5 KB / 4.0 KB None 6 ms -0 ms, -2.2%
⚪ @portabletext/editor / @portabletext/editor/utils export 33.5 KB / 4.1 KB None 6 ms -0 ms, -3.3%
⚪ @portabletext/editor / defineSchema consumer 658 B / 69 B None None None
⚪ @portabletext/editor / typical consumer 1.01 MB / 129.3 KB None None None
⚪ @portabletext/editor / useEditor consumer 1.6 KB / 248 B None None None
⚪ @portabletext/html / @portabletext/html export 69.3 KB / 10.1 KB None 7 ms +0 ms, +0.2%
⚪ @portabletext/html / @portabletext/html/rules export 15.2 KB / 2.1 KB None 5 ms -0 ms, -0.7%
⚪ @portabletext/html / htmlToPortableText consumer 69.3 KB / 10.1 KB None None None
⚪ @portabletext/keyboard-shortcuts / @portabletext/keyboard-shortcuts export 7.0 KB / 858 B None 2 ms -0 ms, -2.5%
⚪ @portabletext/markdown / @portabletext/markdown export 419.8 KB / 65.7 KB None 39 ms +1 ms, +2.8%
⚪ @portabletext/markdown / portableTextToMarkdown consumer 86.8 KB / 12.0 KB None None None
⚪ @portabletext/patches / @portabletext/patches export 50.0 KB / 7.7 KB None 4 ms -0 ms, -3.4%
⚪ @portabletext/plugin-character-pair-decorator / @portabletext/plugin-character-pair-decorator export 123.9 KB / 15.8 KB None None None
⚪ @portabletext/plugin-decorations / @portabletext/plugin-decorations export 16.4 KB / 2.3 KB None None None
⚪ @portabletext/plugin-dnd / @portabletext/plugin-dnd export 10.4 KB / 1.8 KB None None None
⚪ @portabletext/plugin-emoji-picker / @portabletext/plugin-emoji-picker export 149.8 KB / 18.5 KB None None None
⚪ @portabletext/plugin-input-rule / @portabletext/plugin-input-rule export 124.0 KB / 15.7 KB None None None
⚪ @portabletext/plugin-list-index / @portabletext/plugin-list-index export 7.4 KB / 1.1 KB None None None
⚪ @portabletext/plugin-markdown-shortcuts / @portabletext/plugin-markdown-shortcuts export 136.2 KB / 17.6 KB None None None
⚪ @portabletext/plugin-one-line / @portabletext/plugin-one-line export 2.6 KB / 608 B None None None
⚪ @portabletext/plugin-paste-link / @portabletext/plugin-paste-link export 4.3 KB / 1.0 KB None None None
⚪ @portabletext/plugin-sdk-value / @portabletext/plugin-sdk-value export 242.1 KB / 32.8 KB None None None
⚪ @portabletext/plugin-table / @portabletext/plugin-table export 61.0 KB / 7.1 KB None None None
⚪ @portabletext/plugin-table / @portabletext/plugin-table/ui export 114.6 KB / 19.5 KB None None None
⚪ @portabletext/plugin-typeahead-picker / @portabletext/plugin-typeahead-picker export 165.9 KB / 19.9 KB None None None
⚪ @portabletext/plugin-typography / @portabletext/plugin-typography export 130.8 KB / 17.0 KB None None None
⚪ @portabletext/repair / @portabletext/repair export 4.5 KB / 636 B None 3 ms -0 ms, -3.7%
⚪ @portabletext/sanity-bridge / @portabletext/sanity-bridge export 274.7 KB / 30.2 KB None 184 ms -3 ms, -1.4%
⚪ @portabletext/schema / @portabletext/schema export 9.9 KB / 1.2 KB None 2 ms +0 ms, +0.3%
⚪ @portabletext/toolbar / @portabletext/toolbar export 153.3 KB / 17.3 KB None None None
⚪ @portabletext/toolbar / typical consumer 145.6 KB / 16.9 KB None None None

Significant means at least 1.0 KB and 1% gzip, or at least 5 ms and 10% import time.

…he run

`scripts/cases.ts` names each case by page, language and prefix, with
the text a correct editor ends up with and optional `nested`,
`compositionstart` and second word counts. A case can be marked
expected-to-fail with a one-line reason. The Japanese empty-block case
is: Gboard restarts the composition after the first character, so the
published editor leaves `k` behind and converts the first word
without it, `'k用今日'` with one nested `compositionstart`.

`run-on-device.sh` reads its cases for `IME_LANG` from the table
instead of a fixed list. The driver still asserts nothing about the
editor, so its one retry per case can only cover a driver fault.

`scripts/report.ts` parses the `RESULT` lines from `drive.log`, the
`RETRY` lines from `phases.log`, and writes a markdown table to
`$GITHUB_STEP_SUMMARY` with a verdict per case: pass, fail, expected
fail or unexpectedly passing, the last one called out above the table
since it means the mark should go. It exits 1 only when the run broke,
the Japanese setup failed or a case printed no `RESULT` line, never on
an editor verdict.
…ulator

Two jobs, `ja` and `en`, on an API 36 `google_apis_playstore` x86_64
emulator with the same options and the same single boot retry as
before: the second run starts only when the first never logged
`BOOTED`. Each job builds the editor packages the test page needs with
`turbo build --filter='android-ime^...'`, builds the page, runs the
cases and uploads `out/`.

It runs on pull requests that touch the editor's input handling or
the app, nightly against `main`, and on dispatch. It reports and does
not gate: the job summary holds the case table, and a job is red only
when the run broke, never on an editor verdict.

This branch was successfully deployed

3 active deployments
Preview – portable-text-editor-documentation — ccbda14f Deployed Oct 29, 2026 by vercel[bot]
Preview – portable-text-playground — ccbda14f Deployed Oct 29, 2026 by vercel[bot]
Preview – portable-text-example-basic — ccbda14f Deployed Oct 29, 2026 by vercel[bot]
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