From b7ef983bd83768b15968bebff05c237acbec494e Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 11 Aug 2026 00:51:42 -0400 Subject: [PATCH] fix(embed): no caret handle or Cut/Paste on a readonly field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suppressing the keyboard for a readonly field was only half of it. The selection UI still treated it as fully editable: a tap-and-hold raised the insertion caret handle, and its toolbar offered Cut and Paste — on a field the page will not let you modify. Cut appeared to work in the mirror while the page kept its text, so the two silently drifted apart. Native, on the same page in the full-screen WebView, gives a readonly field selection handles and a Copy / Select-all bar, and nothing else: no caret handle (there is no caret to place) and no editing actions. Carry the flag into SelectionUiState so the overlay can reason about it: `fieldReadOnly` gates the insertion handle (and with it the Paste/Select-all popup that hangs off it), and the field toolbar drops Cut and Paste. Selection, its handles, Copy and Select-all are untouched — that half is what native offers and it works today. `cutSelection`/`pasteClipboard` refuse on a readonly field too. The toolbar no longer offers them, so this is a backstop, placed next to the ops so a future call site can't reintroduce the drift. Device-verified: readonly long-press selects with handles and shows exactly "Copy | Select all"; a plain tap gives no keyboard and no caret droplet; an editable field still shows all four actions and keeps its caret handle. Co-Authored-By: Claude Opus 5 (1M context) --- .../screen/loggedIn/embed/EmbeddedTabLayer.kt | 26 ++++++++++++++----- .../ui/screen/loggedIn/embed/RemoteImeView.kt | 7 +++-- .../screen/loggedIn/embed/SelectionUiState.kt | 16 +++++++++++- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/EmbeddedTabLayer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/EmbeddedTabLayer.kt index d8441ae2ff..f0b00c2319 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/EmbeddedTabLayer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/EmbeddedTabLayer.kt @@ -377,6 +377,7 @@ fun EmbeddedTabLayer(barFavoriteIds: List) { // Cancel any in-flight scroll-hide from the page phase: otherwise the field's own // selection-reveal scrolls keep it armed and the new field handles/toolbar never appear. sel.scrolling = false + sel.onFieldReadOnly(event.readOnly) sel.onFieldGeometry(event.geometry, event.text.isNotEmpty()) } ImeEvent.WantKeyboard -> { @@ -398,6 +399,7 @@ fun EmbeddedTabLayer(barFavoriteIds: List) { // Re-arm "the field has text" so a tap can show the insertion handle again (a tab // switch resets it). Geometry stays null here, so this can't pop a handle up on its // own — native shows nothing until the user touches the field. + sel.onFieldReadOnly(event.focus.readOnly) sel.onFieldGeometry(null, event.focus.text.isNotEmpty()) } ImeEvent.Blur -> { @@ -529,19 +531,29 @@ fun EmbeddedTabLayer(barFavoriteIds: List) { // In-field (/