From dec1e0bec15139644d6d33bc6dd19b72197be15d Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 Jun 2026 18:53:53 -0400 Subject: [PATCH] feat(embed): selection loupe + native-parity fixes for embedded text selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds out host-drawn text selection for embedded napplet/nsite/browser surfaces toward native parity, and fixes the bugs found while exercising it. - Magnifier loupe (#4): EmbeddedMagnifier + provider-side pixel capture (EmbeddedMagnifierProbe) shipped over IPC for both embed paths; the caret/selection handles drive it via OnMagnify. - SelectionUiState: single source of truth for the overlay show/hide rules (insertion caret / in-field range / page-text range + dragging/scrolling). - EmbeddedSelectionDrag: suspends the nav drawer's edge swipe while a handle is dragged (auto-scroll #9). Bug fixes: - No more overlay blink on word-select: the shim's selection-reveal scrolls (a textarea auto-scrolling to show a forming/re-asserted range) no longer trip the hide-on-scroll path, and the hide self-heals instead of being re-armed indefinitely. - RemoteImeView debounces the range-lost signal so a transient collapse that gets re-asserted doesn't flicker the handles/toolbar. - Focusing a field clears any page-text selection (shim + host), so the stale page handles/Copy bar no longer linger above — and stop stealing drags from — the field overlays; also cancels any in-flight scroll-hide on focus. - Caret insertion-handle drag now actually moves the caret: read the pointer delta with positionChangeIgnoreConsumed() before consuming, so the value isn't zeroed by our own consume (or the sandbox surface consuming the move). Co-Authored-By: Claude Opus 4.8 (1M context) --- ...6-25-embed-text-selection-native-parity.md | 114 +++- .../AccountSwitcherAndLeftDrawerLayout.kt | 12 +- .../browser/EmbeddedWebAppController.kt | 39 ++ .../loggedIn/embed/EmbeddedImeBridge.kt | 23 + .../loggedIn/embed/EmbeddedMagnifier.kt | 114 ++++ .../loggedIn/embed/EmbeddedMagnifierProbe.kt | 65 ++ .../loggedIn/embed/EmbeddedSelectionDrag.kt | 37 ++ .../screen/loggedIn/embed/EmbeddedTabLayer.kt | 594 ++++++++++++------ .../ui/screen/loggedIn/embed/RemoteImeView.kt | 36 +- .../screen/loggedIn/embed/SelectionUiState.kt | 183 ++++++ .../favorites/EmbeddedNostrAppController.kt | 39 +- .../composeResources/files/napplet/shim.js | 218 ++++++- .../napplethost/NappletBrowserContract.kt | 28 + .../napplethost/NappletBrowserService.kt | 49 ++ .../napplethost/NappletEmbedContract.kt | 23 + .../napplethost/NappletHostService.kt | 51 ++ tools/ime-test/index.html | 2 + 17 files changed, 1389 insertions(+), 238 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/EmbeddedMagnifier.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/EmbeddedMagnifierProbe.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/EmbeddedSelectionDrag.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/SelectionUiState.kt diff --git a/amethyst/plans/2026-06-25-embed-text-selection-native-parity.md b/amethyst/plans/2026-06-25-embed-text-selection-native-parity.md index 5a4af2c48f..29048ec155 100644 --- a/amethyst/plans/2026-06-25-embed-text-selection-native-parity.md +++ b/amethyst/plans/2026-06-25-embed-text-selection-native-parity.md @@ -31,18 +31,18 @@ rules, so we can check off coverage. Native impl lives in `android.widget.Editor | # | Native feature | Activates | Deactivates | Our status | |---|----------------|-----------|-------------|------------| -| 1 | **Insertion handle** (the teardrop "blob" under the caret) | tap in editable text; tap again to re-show | typing, scroll start, focus loss, ~4s inactivity timeout | ✅ `InsertionHandle` — shows on caret geometry, hides via `onEdited`. **Missing the inactivity timeout and the re-show-on-tap.** | -| 2 | **Selection handles** (asymmetric left/right teardrops) | long-press word, double-tap word, drag-extend | tap-collapse, typing, new selection | ✅ `SelectionHandle(isStart)` + drag-to-extend via `pageExtend`/caretmove | -| 3 | **Floating toolbar** (Cut/Copy/Paste/Select-All/Share/…) | selection made, or tap insertion handle (Paste/Select-All) | scroll/fling (hides, returns on settle), handle drag (hides), tap-collapse | ⚠️ `EmbeddedSelectionToolbar` has Cut/Copy/Paste/Select-All only. **No hide-during-drag, no hide-during-scroll, no insertion-handle Paste popup, no overflow, no Share/Web-Search/process-text.** | -| 4 | **Magnifier / loupe** (the zoom bubble above the finger while dragging a handle or the caret) | finger down + moving on a handle or the caret | finger up | ❌ **Not built.** This is the "zoom bubble" called out — highest-value missing piece. | -| 5 | **Word-granularity long-press** then char-extend | long-press | — | ⚠️ partial — selection works; granularity (word-snap while dragging, then char) not modelled | -| 6 | **Double-tap = word, long-press = word, (triple-tap/drag = paragraph)** | tap count | — | ❌ double-tap-to-select not wired (only long-press) | +| 1 | **Insertion handle** (the teardrop "blob" under the caret) | tap in editable text; tap again to re-show | typing, scroll start, focus loss, ~4s inactivity timeout | ✅ `InsertionHandle`. **Native availability rule now matched (2026-06-25):** only shown when the field is NON-EMPTY (`Editor` gates the handle behind `text.length() > 0`, via `SelectionUiState.fieldHasText`) — fixes it popping up on focus of an empty box; hides on typing (`onEdited`), scroll (`scrolling`), focus loss, and ~4s inactivity (`hideCaret` timeout), re-showing on the next tap — via an explicit `ime.carettap` shim signal (DOM `click`), so a tap that doesn't move the caret still re-shows it. Device-verified. | +| 2 | **Selection handles** (asymmetric left/right teardrops) | long-press word, double-tap word, drag-extend | tap-collapse, typing, new selection | ✅ `SelectionHandle(isStart)` + drag-to-extend, for BOTH plain page text (`pageExtend`) AND in-field ``/`
+ +
scroll region — drag up/down here