Before submitting
Bug summary
On Android, native undo can restore the correct plain text while silently corrupting the rich document structure returned by getHTML().
After pasting text into a document containing headings, inline emphasis, a link, and ordered/unordered lists, hardware Ctrl+Z visibly restores the original character count. A subsequent getHTML() call, however, has lost italic, underline, and link spans and has serialized every list item as a paragraph. Bold and heading structure survived in the tested fixture.
The equivalent iOS flow preserves the original HTML exactly.
This is particularly dangerous because the visible text looks successfully undone while persisted HTML has already lost semantic formatting and list structure.
Library version
react-native-enriched-html@1.1.0 (latest stable)
The Android implementation was also compared with current main / nightly commit 0fdac7a45; no relevant Android undo implementation change was present.
Environment info
System:
OS: macOS 26.5.2
Binaries:
Node: 26.3.1
Bun: 1.3.11
Java: OpenJDK 17.0.19
SDKs:
Expo: 57.0.11
React Native: 0.86.2
React: 19.2.5
Architecture:
React Native New Architecture / Fabric enabled
Android:
Emulator API: 36.1
IME: Gboard
Trigger: hardware keyboard Ctrl+Z
Comparison:
iOS Simulator 18.3 did not reproduce; HTML remained exact after undo
Steps to reproduce
Minimal component: https://gist.github.com/the-laughing-monkey/dab58e07e17b1bca7f15072f12bd0012
- Launch the component on Android.
- Focus the editor and place the caret at the end of the document.
- Paste
Pasted: Café 東京 한국어 مرحبا 🫖 using the native paste action.
- Press the Get HTML button and confirm that the original rich structure plus the pasted text is present.
- Send hardware
Ctrl+Z while the editor is focused.
- Observe that the pasted text disappears and the visible plain text returns to its original value.
- Press Get HTML again.
Test-fixture telemetry was 120 → 148 → 120 plain-text characters across paste and undo.
Expected behavior
Undo should atomically restore the previous attributed document and selection. getHTML() after undo should equal the pre-paste HTML, preserving the heading, bold/italic/underline spans, link, and ordered/unordered lists.
Actual behavior
Plain text returns to the correct pre-paste value, but exported HTML silently loses supported rich structure:
- italic is removed;
- underline is removed;
- the anchor/link is removed;
- ordered and unordered list items become paragraphs;
- bold and the heading happened to survive.
Investigation and attempted workarounds
Android framework behavior appears relevant: android.widget.Editor.UndoInputFilter.EditOperation stores old/new content as strings, and the platform source contains TODO: Make this span aware. That matches the observed result: text is restored, attributed spans are not.
We tested bounded application-side recovery, but it is not a safe workaround:
- A captured
getHTML() snapshot restored through setValue() recovers the rich HTML exactly.
setValue() resets selection to 0–0 for a long document.
- Replaying the captured selection through the public
setSelection() API is later overwritten by asynchronous parser/Fabric work; the package exposes no stable replacement-complete signal.
- A temporary native command applying content and selection together, tested on both sides of layout invalidation, showed the expected selection only transiently before it was reset to
0–0.
- View, input-connection, and focused-Activity shortcut interception hooks did not observe the destructive hardware undo path.
A dependency-owned, span-aware Android history implementation appears necessary. This may overlap with the implementation direction discussed in #675, but the key requirement here is that every native/system undo entry point preserve the attributed document and selection—not only that an imperative undo() method exists.
Impact
An application cannot safely persist getHTML() after Android native undo because the user may unknowingly save a structurally damaged document. Until the control owns span-aware history, Android rich-document editing must either disable every undo entry point reliably or be treated as unsafe for lossless HTML editing.
Before submitting
1.1.0).0.86.2).Bug summary
On Android, native undo can restore the correct plain text while silently corrupting the rich document structure returned by
getHTML().After pasting text into a document containing headings, inline emphasis, a link, and ordered/unordered lists, hardware
Ctrl+Zvisibly restores the original character count. A subsequentgetHTML()call, however, has lost italic, underline, and link spans and has serialized every list item as a paragraph. Bold and heading structure survived in the tested fixture.The equivalent iOS flow preserves the original HTML exactly.
This is particularly dangerous because the visible text looks successfully undone while persisted HTML has already lost semantic formatting and list structure.
Library version
react-native-enriched-html@1.1.0(latest stable)The Android implementation was also compared with current
main/ nightly commit0fdac7a45; no relevant Android undo implementation change was present.Environment info
System: OS: macOS 26.5.2 Binaries: Node: 26.3.1 Bun: 1.3.11 Java: OpenJDK 17.0.19 SDKs: Expo: 57.0.11 React Native: 0.86.2 React: 19.2.5 Architecture: React Native New Architecture / Fabric enabled Android: Emulator API: 36.1 IME: Gboard Trigger: hardware keyboard Ctrl+Z Comparison: iOS Simulator 18.3 did not reproduce; HTML remained exact after undoSteps to reproduce
Minimal component: https://gist.github.com/the-laughing-monkey/dab58e07e17b1bca7f15072f12bd0012
Pasted: Café 東京 한국어 مرحبا 🫖using the native paste action.Ctrl+Zwhile the editor is focused.Test-fixture telemetry was
120 → 148 → 120plain-text characters across paste and undo.Expected behavior
Undo should atomically restore the previous attributed document and selection.
getHTML()after undo should equal the pre-paste HTML, preserving the heading, bold/italic/underline spans, link, and ordered/unordered lists.Actual behavior
Plain text returns to the correct pre-paste value, but exported HTML silently loses supported rich structure:
Investigation and attempted workarounds
Android framework behavior appears relevant:
android.widget.Editor.UndoInputFilter.EditOperationstores old/new content as strings, and the platform source containsTODO: Make this span aware. That matches the observed result: text is restored, attributed spans are not.We tested bounded application-side recovery, but it is not a safe workaround:
getHTML()snapshot restored throughsetValue()recovers the rich HTML exactly.setValue()resets selection to0–0for a long document.setSelection()API is later overwritten by asynchronous parser/Fabric work; the package exposes no stable replacement-complete signal.0–0.A dependency-owned, span-aware Android history implementation appears necessary. This may overlap with the implementation direction discussed in #675, but the key requirement here is that every native/system undo entry point preserve the attributed document and selection—not only that an imperative
undo()method exists.Impact
An application cannot safely persist
getHTML()after Android native undo because the user may unknowingly save a structurally damaged document. Until the control owns span-aware history, Android rich-document editing must either disable every undo entry point reliably or be treated as unsafe for lossless HTML editing.