Skip to content

fix(daemon): run find focus through the shared touch-point decisions - #4223

Merged
thymikee merged 11 commits into
callstack:mainfrom
airowe:worktree-fix-3734-find-focus-guarantees
Nov 5, 2026
Merged

thymikee merged 11 commits into
callstack:mainfrom
airowe:worktree-fix-3734-find-focus-guarantees

Conversation

@airowe

@airowe airowe commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

find focus and find type dispatch their own point, so they skipped the touch-point decisions the acting paths run. They could tap the middle of a container and report success.

Now they share those decisions. A match holding one text field aims at it. Several fields refuse with AMBIGUOUS_MATCH and name them. A field the locator named stays the target, and a disabled one refuses with target_disabled.

Touches 18 files. Scope expansion: the touch-point module moves to a new @agent-device/selectors/node-touch-point subpath. Refs #3734: the other waived guarantee cells remain.

Validation

Tested 2e1ba8a6d. pnpm check:affected --run --head: 41 passed, 25 skipped, exactCommit=true. Skipped includes unit-ci, coverage, Swift runners and replay lanes; CI owns those.

Live at 5daff16a6, not rerun since; iPhone 17 simulator, Contacts new-contact form. Comparison: #4219's branch, with base 035c2b6ca's find code.

  • find role=table focus: comparison taps (201, 507), reports success, and First name keeps focus; head refuses, naming 3 fields.
  • find value="Last name" type Lovelace: lands in Last name, 3/3 on each.

Risks:

  • One earlier run put that text in First name. It did not recur in 7 later runs; cause unknown.
  • Web textbox/searchbox are not recognized as text fields; the docs say so.
  • find role=cell focus --first taps the Ringtone row on both builds, because --first follows ranking.
  • A field inset inside another field refuses or aims at its gutter. No backend was seen producing this.

🤖 Generated with Claude Code

The find focus seam dispatches its own point instead of re-entering the
interaction leaf, so it skipped the touch-point decision the acting paths
run. A match fully tiled by interactive children now refuses before
dispatch, and a child owning the center moves the aim to free parent
space. Missing or zero-area rects get the typed invalid-bounds refusal.

Move resolveNodeTouchPoint to @agent-device/selectors/node-touch-point so
the daemon and command-runtime share one owner, and close the
parentOwnedTouchPoint cell of the find-focus-dispatch guarantee row.

Refs callstack#3734
The package-boundaries enumeration lists every selectors subpath, so the new export has to be declared for the R11 layering rule to accept it.
The offscreen and nonHittable waiver reasons still said the seam dispatches a rect center.
The touch-point rule treats a nested text field as a competitor to avoid,
so a match wrapping a field reported focus success beside the field and
the typed text then went wherever the focus already was. A sole enabled,
visible field now becomes the target, with the same touch-point, covered
and keyboard checks. Several fields refuse with AMBIGUOUS_MATCH and the
field candidates.

Refs callstack#3734
A disabled field promotes to its parent on iOS, and the parent's other
field then counted as the sole one, so find type wrote into a field the
locator never named. The located field now decides first: an enabled one
stays the target and a disabled one refuses with target_disabled, as does
a match holding only disabled fields.

Count only outermost on-screen typed fields, so a hosted text field, a
scrolled-away field and an untyped child no longer change the target. The
rule moves to the find description so MCP clients see it, and it names
web text boxes as unrecognized.

Refs callstack#3734
The disabled check only read the located node, so a same-rect wrapper over
a disabled field was still focused, and a label inside one was promoted
past it to an enabled sibling. find type then wrote into another field and
reported success. The field decision now walks from the located node to the
promoted one, and the nearest field on that path is the named field.

The find help, selector docs and nonHittable waiver now state that a named
text field stays the target.

Refs callstack#3734

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 17 files

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread website/docs/docs/selectors.md Resolved
Comment thread website/docs/docs/selectors.md Resolved
A field with no usable bounds counted as on-screen, so it could take the
focus or turn a single field into an ambiguity before the touch point could
reject it. A password fact on an unrecognized type was ignored, so focus
went to the container instead.

Say in the help and docs that a named field wins over its neighbours and
that target_disabled is a reason, and record why the nested-field refusal
follows find's refusal contract for printed refs.

Refs callstack#3734

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread website/docs/docs/selectors.md Resolved

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread website/docs/docs/selectors.md Resolved
@thymikee

thymikee commented Nov 4, 2026

Copy link
Copy Markdown
Member

At 5c8f01c, the code looks good. find ... focus now goes through resolveFocusTarget and the shared touch-point decisions, and the docs and help text match it, so the wording thread is fixed.

Not blocking, take or leave: the covered bullet in selectors.md names COMMAND_FAILED but, unlike the target_disabled bullet, not its reason target_covered.

The Android Smoke failure is at the step "open deterministic fixture route with system IME", where open returned COMMAND_FAILED before any find focus ran, so it looks unrelated; a rerun should clear it. No conflicts are known.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Nov 4, 2026
@thymikee

thymikee commented Nov 4, 2026

Copy link
Copy Markdown
Member

At 2e1ba8a, the code looks good. The new commit only changes one line in selectors.md: a covered match or field now names its refusal reason, target_covered, which matches what coveredInteractionDetails emits and what the help text says. The rest of the change stands as reviewed at 5c8f01c (#4223 (comment)). All 16 checks pass and no review threads are open.

@thymikee
thymikee merged commit 8539d87 into callstack:main Nov 5, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants