Skip to content

fix(backwards): backwards funbox doesn't handle complex characters (@Leonabcd123) - #8345

Draft
Leonabcd123 wants to merge 1 commit into
monkeytypegame:masterfrom
Leonabcd123:fix/backwards-funbox-splitting
Draft

fix(backwards): backwards funbox doesn't handle complex characters (@Leonabcd123)#8345
Leonabcd123 wants to merge 1 commit into
monkeytypegame:masterfrom
Leonabcd123:fix/backwards-funbox-splitting

Conversation

@Leonabcd123

@Leonabcd123 Leonabcd123 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

To reproduce:

  1. Change mode to custom
  2. Set the custom text to 🏳️‍🌈
  3. Enable the backwards funbox
  4. Notice how the character breaks

@monkeytypegeorge monkeytypegeorge added the frontend User interface or web stuff label Aug 15, 2026
@nadalaba

Copy link
Copy Markdown
Contributor

I believe Intl.Segmenter uses grapheme splitting, which will not work correctly for text "\u0628\u064B" (should be split into 2 characters).

I remember lett arr = []; for (const c of str) arr.push(c) doing a better job at splitting.

@Leonabcd123

Leonabcd123 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

I believe Intl.Segmenter uses grapheme splitting, which will not work correctly for text "\u0628\u064B" (should be split into 2 characters).

I remember lett arr = []; for (const c of str) arr.push(c) doing a better job at splitting.

You're correct that we're splitting by grapheme clusters, however I don't understand why \u0628\u064B should be split into 2 characters? Isn't it just a base character and a diacritic? With Intl.Segmenter it looks exactly the same with backwards or without, but with the loop approach it looks different with backwards (the diacritic is split from the base character).

@nadalaba

nadalaba commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Isn't it just a base character and a diacritic?

Yes, but there's no 1 key that can produce this combination in 1 go, so without backwards we should type "\u0628" first, and with backwards we should type "\u064B" first. However, with Intl.Segmenter we will be forced to type it \u0628\u064B regardless of funbox.

with the loop approach it looks different with backwards (the diacritic is split from the base character).

Try setting the language to Arabic first

p.s., for a list of key combinations that will cause issues with grapheme splitting see this

p.s.2, lett arr = []; for (const c of str) arr.push(c) can be shortened into [...str]

@Leonabcd123

Leonabcd123 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

Isn't it just a base character and a diacritic?

Yes, but there's no 1 key that can produce this combination in 1 go, so without backwards we should type "\u0628" first, and with backwards we should type "\u064B" first. However, with Intl.Segmenter we will be forced to type it \u0628\u064B regardless of funbox.

I see the problem now.

p.s.2, lett arr = []; for (const c of str) arr.push(c) can be shortened into [...str]

The problem with this approach is that it breaks surrogate pairs (although I still made it the fallback for when Intl.Segmenter doesn't exist, not sure of a better alternative).

@Leonabcd123
Leonabcd123 marked this pull request as draft August 15, 2026 18:44
@nadalaba

Copy link
Copy Markdown
Contributor

The problem with this approach is that it breaks surrogate pairs

[...str] does not break surrogate pairs, it splits any string into its Unicode characters (works ok with all planes of Unicode):

"𐑩".split("") // splits into 2 surrogate pairs.
[..."𐑩"] // splits into 1 Unicode character.

Your reproduction example uses emoji ZWJ sequences with variation selectors which are in essence 4 Unicode characters. I'd prioritize Unicode chars integrity over combining emoji's any day tbh

@Leonabcd123

Leonabcd123 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

The problem with this approach is that it breaks surrogate pairs

[...str] does not break surrogate pairs, it splits any string into its Unicode characters (works ok with all planes of Unicode):

"𐑩".split("") // splits into 2 surrogate pairs.
[..."𐑩"] // splits into 1 Unicode character.

Ah sorry I confused .split("") (which breaks surrogate pairs) with the spread.

Your reproduction example uses emoji ZWJ sequences with variation selectors which are in essence 4 Unicode characters. I'd prioritize Unicode chars integrity over combining emoji's any day tbh

Yea, that's why I chose that emoji as the reproduction, if I hadn't known about it I would've gone with the spread approach. I'd like to try and find another solution before going with the spread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontend User interface or web stuff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants