DEV Community

greymoth
greymoth

Posted on

I find Japan-shaped holes in global software

There's a category of bug that only exists if you type Japanese, and it keeps showing up in software built by people who don't. Same shape, different repo, over and over. After a while you stop being surprised and start being able to guess which file it's in before you open the project.

The one I hit most: you're typing into a search box, the IME pops up conversion candidates, you press Enter to pick the right kanji, and instead of confirming the word the app runs the search on half-finished text. No error. No stack trace. CI is green. The maintainers never see it, because most of them don't type with an IME.

Quick version of why this happens, if you've never used one. When you type Japanese you don't type final characters. You type romaji, the IME shows a preedit, and you press Enter (or Space) to confirm the conversion into kanji. That confirming Enter is the exact same physical key your form is already listening for. So the handler can't tell "I'm done picking a word" from "submit this."

Here's the part that actually got me writing this down. The main input is usually fine. The big composer, the primary message box, somebody already guarded that one. It's the search box, the inline rename field, the tag input sitting right next to it that fire early. The fix exists in the codebase. It just stopped one input short.

The fix itself is one property. While a conversion is in progress, isComposing is true:

// before
if (e.key === 'Enter') {
  onSearch(value);
}

// after: ignore the Enter that's confirming an IME conversion
if (e.key === 'Enter' && !e.nativeEvent.isComposing) {
  onSearch(value);
}
Enter fullscreen mode Exit fullscreen mode

That's the whole thing.

Two things I learned the annoying way, if you go to write this yourself. In React you reach down to e.nativeEvent.isComposing rather than trust the synthetic event. And isComposing on its own isn't quite enough: on some code paths the keydown fires reporting keyCode === 229 instead of setting the flag, so a lot of these end up keeping || e.keyCode === 229 as a fallback. There's also a genuinely fiddly moment at the exact instant a composition ends, where isComposing can already read false on the very Enter that confirmed it, depending on the browser. I haven't found one clean rule that holds everywhere. Checking both is what's survived for me in practice, and I'm honestly still not sure it's airtight.

So this isn't "teams don't know about IME." The knowledge is usually already in the repo, one screen over. The guard lives on the input everyone tests, and the secondary inputs are the ones nobody types Japanese into during review. That's the hole. Not the bug itself, the structure that keeps the bug alive.

I've sent one-line fixes of this exact shape to a handful of open source projects. Every one was the same move: find the keydown that does something on Enter, add the guard. Unglamorous. But you can only really catch it if you've sat there and hit it yourself, in Japanese, ten times.

And once you start seeing it, the IME Enter is just the most common one. The same blind spot produces a small family of these:

  • Dates. A form validates the year as a four-digit number between 1900 and 2100. A Japanese user types 令和7 (Reiwa 7, which is 2025). Era-based years are all over tax forms, government paperwork, official documents. The validator has never heard of them.
  • Names. A "First name / Last name" form assumes given-name-first with a space in the middle. Japanese names are family-name-first and written with no space at all. Split on the space and you mangle the name.

Full-width vs half-width digits is a third one in the same spirit (a phone field strips everything that isn't [0-9] and silently eats the 012 a Japanese keyboard produces by default), but you get the idea. None of these are hard. They're just invisible to someone who never types this way.

I keep the reproductions and the data public if you want to look at the actual cases:

  • github.com/greymoth-jp/cjk-agent-fixtures: runnable CI fixtures for the ways CJK / IME input breaks in editors, terminals, and agents.
  • github.com/greymoth-jp/sibling-leftover-dataset: the broader pattern, where a fix lands in one place and the identical sibling right next to it gets left behind. Mined from merged PRs.

You don't need any of that to check your own app, though. Switch your keyboard to a Japanese IME and type a word into every input that does something on Enter. The main form will probably be fine. Try the search box. Try the inline rename. Try the little tag input buried in a settings panel. If Enter fires before you've confirmed the word, there's a hole there.

There's usually one.

Top comments (0)