From 3df8a3b3ba9ed34c69b262f784524b4dc78d9f2e Mon Sep 17 00:00:00 2001 From: PJ Date: Sat, 15 Aug 2026 22:19:49 +0530 Subject: [PATCH] fix(sidecar): find the erased field by class, past the keyboard's own focus The check that decides whether the select-all worked looked for an "editable" attribute maestro's tree does not carry, so it answered "cannot tell" every time and every erase paid the per-character fallback. Worse, an open keyboard puts a second focused node in the tree, one of the IME's own keys, carrying no text: taking the first focused node would read a field still holding 4096 characters as empty, which is the one answer that stops the erase early. Match the text field by class instead. Measured against the real backend, 4096 characters now clear in 385ms on API 34, 409ms on API 35 and 870ms on API 36, verified empty, where the fallback took ~4s. --- .../dev/sanderling/sidecar/DriverBackend.kt | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt b/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt index 5e74b8a..3d69e2f 100644 --- a/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt +++ b/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt @@ -957,31 +957,38 @@ internal fun deleteKeyCommands(count: Int, batch: Int): List { } } -// focusedEditableTextLength reports how much text the focused editable node -// holds, or null when the tree names no focused editable node. Null is -// "cannot tell", which is not the same as empty and must not be read as it. +// focusedEditableTextLength reports how much text the focused text field +// holds, or null when the tree names no focused text field. Null is "cannot +// tell", which is not the same as empty and must not be read as it. +// +// The field is found by class, not by an "editable" attribute: maestro's tree +// carries no such attribute. Class also settles the trap an open keyboard +// sets, which is that the IME contributes a focused node of its own. That node +// holds no text, so taking the first focused node would read a field still +// holding 4096 characters as empty, and empty is the answer that stops the +// erase. internal fun focusedEditableTextLength(treeJson: String): Int? { if (treeJson.isBlank()) return null return try { - focusedEditableLength(jsonMapper.readTree(treeJson)) + focusedFieldLength(jsonMapper.readTree(treeJson)) } catch (_: Exception) { null } } -private fun focusedEditableLength( +private fun focusedFieldLength( node: com.fasterxml.jackson.databind.JsonNode, ): Int? { val attributes = node.get("attributes") if (attributes != null && attributes.isObject && attributes.get("focused")?.asText() == "true" && - attributes.get("editable")?.asText() == "true" + attributes.get("class")?.asText().orEmpty().endsWith("EditText") ) { return attributes.get("text")?.asText().orEmpty().length } val children = node.get("children") ?: return null if (!children.isArray) return null - for (child in children) focusedEditableLength(child)?.let { return it } + for (child in children) focusedFieldLength(child)?.let { return it } return null }