Add dark theme, richer Markdown and readable plugin results. Improve Android input evidence, response integrity and diagnostics. Unify lifecycle, guidance receipts and evidence sources across clients. Keep direct action dispatch without an added AI quota approval layer. Align version metadata at 2.2.0 and bundle Helper 1.2.2. Validation: 3063 backend tests before the version bump; 44 after. Frontend: 317 tests and production build passed.
81 lines
2.9 KiB
Python
81 lines
2.9 KiB
Python
"""Keep editable values separate from accessibility hints and visual labels."""
|
|
|
|
from typing import Any
|
|
|
|
|
|
def _boolean(node: dict, *keys: str) -> bool | None:
|
|
for key in keys:
|
|
value = node.get(key)
|
|
if isinstance(value, bool):
|
|
return value
|
|
if isinstance(value, str) and value.lower() in {"true", "false"}:
|
|
return value.lower() == "true"
|
|
return None
|
|
|
|
|
|
def input_state(node: dict[str, Any]) -> dict[str, Any]:
|
|
"""Return value evidence only for an actual editable node, never OCR text.
|
|
|
|
Equality with a hint is ambiguous on older backends: an application can
|
|
legitimately contain that exact value. Only an explicit showing-hint flag
|
|
proves that nonempty text is a placeholder.
|
|
"""
|
|
editable = _boolean(node, "editable", "isEditable")
|
|
class_name = str(node.get("class") or node.get("className") or "").lower()
|
|
if editable is None and "edittext" in class_name:
|
|
editable = True
|
|
if editable is not True:
|
|
return {}
|
|
hint = str(node.get("hint") or node.get("hintText") or node.get("hint-text") or "")
|
|
showing = _boolean(node, "showing_hint", "showing-hint-text", "isShowingHintText", "is_hint")
|
|
raw = node.get("text")
|
|
value = raw if isinstance(raw, str) else None
|
|
if showing is True:
|
|
hint = hint or value or ""
|
|
value = ""
|
|
status = "empty"
|
|
elif value is None:
|
|
status = "unknown"
|
|
elif not value:
|
|
status = "empty"
|
|
elif hint and value == hint and showing is not False:
|
|
value = None
|
|
status = "unknown"
|
|
else:
|
|
status = "present"
|
|
return {
|
|
"editable": True,
|
|
"input_value": value,
|
|
"input_value_state": status,
|
|
"hint": hint,
|
|
"showing_hint": showing,
|
|
"focused": _boolean(node, "focused"),
|
|
"password": _boolean(node, "password"),
|
|
}
|
|
|
|
|
|
def verify_input(elements: list[dict], target: dict | None = None) -> str:
|
|
"""Verify value presence against a fresh tree, without returning its value.
|
|
|
|
Resource identity wins over focus. Unidentifiable, masked, or unsupported
|
|
inputs remain unknown; visual text elsewhere cannot establish input success.
|
|
"""
|
|
candidates = []
|
|
target = target or {}
|
|
resource_id = target.get("target_resource_id")
|
|
for element in elements:
|
|
state = input_state(element) if "input_value_state" not in element else element
|
|
if not state:
|
|
continue
|
|
if resource_id:
|
|
if (element.get("resource_id") or element.get("resource-id")) == resource_id:
|
|
candidates.append(state)
|
|
elif state.get("focused") is True:
|
|
candidates.append(state)
|
|
if len(candidates) != 1:
|
|
return "unknown"
|
|
state = candidates[0]
|
|
if state.get("password") is True and state.get("input_value_state") != "empty":
|
|
return "unknown"
|
|
return str(state["input_value_state"])
|