Skip to content

Consolidate tap-family flags into shared OptionGroups and a typed executor entry point#49

Merged
onevcat merged 5 commits into
mainfrom
refactor/tap-optiongroup-consolidation
Jul 14, 2026
Merged

Consolidate tap-family flags into shared OptionGroups and a typed executor entry point#49
onevcat merged 5 commits into
mainfrom
refactor/tap-optiongroup-consolidation

Conversation

@onevcat

@onevcat onevcat commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Closes #42.

What this does

Three steps, one commit each (plus the plan doc in docs/ai/xxxx-tap-optiongroup-consolidation/):

  1. Reflection guardrail (93dd606) — every construct-and-assign forwarder site (14 sites, 13 verbs) extracts its copy block into a testable makeIOSSubcommand(), and ForwarderInitializationGuardTests Mirror-audits the result: a property left in ArgumentParser wrapper-definition state fails in CI instead of trapping at runtime (the Fix android tap crashing on ArgumentParser direct-init trap #41 failure class). The audit fails closed — unrecognized wrapper internals are a test failure, so an ArgumentParser upgrade can break the guard loudly but never silently blind it. Bonus: Paste.clientPreflight was a deliberately partial three-field copy; it now reuses the full copy.
  2. Shared OptionGroups (4622a52) — TapTargetingOptions (coords, five selectors, --element-type, --frame) and TapTimingOptions (--pre-delay/--post-delay/--wait-timeout/--poll-interval) live in SimUseCore/Options/, declared once and consumed by tap, long-press, and ios tap. Validation moves onto the groups; ArgumentParser does not auto-validate nested groups, so the new TapValidationParityTests pins that the same invalid argv fails with the same message on all three surfaces — exactly the regression a dropped validator call would cause. The positional alias and --duration deliberately stay per-command (verb-specific help / default).
  3. Typed executor entry point (17f434a) — IOSSimTapCommand.performTap(alias:targeting:timing:duration:multiTouch:device:json:), the iOS mirror of AndroidTapCommand.performTap, adopting @hiSandog's review suggestion on the issue. The tap/long-press forwarders pass their parsed groups by value; no backend instance is hand-built, so the wrapper-definition trap is impossible on this path by construction. The two tap-family guard tests retire with the copy code they pinned.

Behavior contract

Flag names, defaults, validation messages, and --json envelopes are byte-identical (pinned by a before/after --help golden diff and the parity tests). The only user-visible deltas, both recorded in CHANGELOG:

  • Help wording unifies to verb-neutral phrasing ("Tap the center…" / "Long-press the center…" → "Target the center…").
  • The timing flags render as one contiguous block after --duration.

Verification

  • Unit suite: 1079 (baseline) → 1094 green; coverage 63.8% → 64.4%.
  • --help golden diff on all three surfaces: only the two documented deltas.
  • Live spot-check on a booted simulator: describe-ui, tap --point (exact-coordinate hit), tap @N (alias cache path), long-press --label-contains --element-type, and both --json envelope paths (success + no-match error with hint).
  • Full E2E run green.

Follow-ups (out of scope)

  • Migrate the remaining 11 construct-and-assign verbs to the executor pattern verb-by-verb; each drops out of the guard suite with its copy code.
  • Decide whether AndroidTapCommand shares the targeting group (Int pixel coords + Android-only --value-contains/--value-regex).

🤖 Generated with Claude Code

onevcat added 5 commits July 13, 2026 17:12
Plan record for issue #42: shared TapTargetingOptions/TapTimingOptions,
a fail-closed reflection guardrail for the remaining construct-and-assign
forwarders, and a performTap-style executor entry point for the tap
family (adopting the review suggestion, mirroring the Android precedent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
…teness (#42)

Every construct-and-assign forwarder site (14 sites across 13 verbs)
now builds its iOS backend command in a testable makeIOSSubcommand().
New ForwarderInitializationGuardTests Mirror-audits the result: any
property left in ArgumentParser wrapper-definition state fails the
test instead of trapping at runtime on first read (the #41 failure
class). The audit fails closed — unrecognized wrapper internals or a
walk that finds zero wrappers is a failure, so an ArgumentParser
upgrade can break the guard loudly but never silently blind it.

Paste.clientPreflight previously copied only the three fields it read;
it now reuses the full copy so it can never trap if it grows another
field read.

Step 1 of docs/ai/xxxx-tap-optiongroup-consolidation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
TapTargetingOptions (coords, five selectors, --element-type, --frame)
and TapTimingOptions (--pre-delay/--post-delay/--wait-timeout/
--poll-interval) now live in SimUseCore/Options/, declared once and
consumed by Tap, LongPress, and IOSSimTapCommand. The forwarders
transfer whole parsed groups, so a field added inside a group appears
on every surface and forwards with zero forwarder edits. The positional
alias and --duration deliberately stay per-command (verb-specific help
/ default).

Validation moves onto the groups (TapTargetingOptions.validate(alias:),
TapTimingOptions.validate() + validateDuration), replacing the 15-param
IOSSimTapCommand.validateOptions static. ArgumentParser does not
auto-validate nested groups, so each surface's explicit calls are
load-bearing — the new TapValidationParityTests pins that the same
invalid argv fails with the same message on all three surfaces, which
is exactly the regression a dropped call would cause.

Behavior notes (pinned by a before/after --help diff): flag names,
defaults, validation messages, and --json envelopes are byte-identical;
help wording unifies to verb-neutral phrasing and the timing flags
render as one block — both deltas recorded in CHANGELOG and the plan
doc.

Step 2 of docs/ai/xxxx-tap-optiongroup-consolidation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
IOSSimTapCommand.performTap(alias:targeting:timing:duration:multiTouch:
device:json:) is now the single execution entry for the tap family —
the iOS mirror of AndroidTapCommand.performTap. `sim-use ios tap`
delegates from execute(), and the top-level `tap` / `long-press`
forwarders call it directly with their parsed groups: no backend
command instance is hand-built anymore, so the wrapper-definition trap
(#41's failure class) is impossible on this path by construction, not
by guardrail. The two tap-family guard tests retire together with the
copy code they pinned; the guard suite keeps covering the 11 verbs
still on the construct-and-assign pattern.

setup(logger:) body moves to a free performEssentialSetup(logger:) —
it never touched the command instance — so static entry points can run
the same Xcode/framework preflight.

Step 3 of docs/ai/xxxx-tap-optiongroup-consolidation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
@onevcat
onevcat merged commit 736f7d8 into main Jul 14, 2026
4 checks passed
@onevcat
onevcat deleted the refactor/tap-optiongroup-consolidation branch July 14, 2026 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consolidate the tap-family flag surface into shared OptionGroups to eliminate forwarder copy hazards

1 participant