Skip to content

refactor(daemon): split touch interaction orchestration into semantic modules - #1748

Merged
thymikee merged 6 commits into
mainfrom
refactor/1691-split-touch-interaction
Aug 11, 2026
Merged

refactor(daemon): split touch interaction orchestration into semantic modules#1748
thymikee merged 6 commits into
mainfrom
refactor/1691-split-touch-interaction

Conversation

@thymikee

@thymikee thymikee commented Aug 11, 2026

Copy link
Copy Markdown
Member

Closes #1691; prework for #1739 (wave 5).

Behavior-preserving decomposition of src/daemon/handlers/interaction-touch.ts (1,052 lines) into semantic modules, with the test suites split 1:1 along the same questions. No guarantee matrix, response shape, session field, command descriptor, or platform behavior changed.

Production module map

Module One question it answers LOC
interaction-touch.ts Which touch command handler owns this request? 27
interaction-touch-press-admission.ts May this targeted touch act, and on what? (surface/capability/button policy, target parsing, @ref staleness + ADR 0014 mutation admission) 203
interaction-touch-press.ts Which node does an admitted press/click/longpress act on, and how is it dispatched? 175
interaction-touch-fill.ts How is fill admitted, parameterized, executed, and projected? 171
interaction-touch-direct-ios-eligibility.ts May this click take the direct iOS selector fast path? 64
interaction-touch-direct-ios.ts How does the direct-iOS dispatch run, delegate, or corroborate? 212
interaction-touch-runtime.ts How does the shared runtime finalize outcomes, warnings, retries, and iOS corroboration? 229
interaction-touch-android-readiness.ts How does Android blocking-dialog readiness compose with ref admission? 65
interaction-touch-android-freshness.ts What does the Android ref-refresh capture do before a @ref mutation? 41
interaction-touch-response.ts How are touch results composed through the one response site? 373
interaction-touch-payload.ts What does a built touch payload look like (field merge + success message)? 84

Import DAG (no back-edges, no barrel, no forwarding wrappers):

interaction-touch  -> press, fill
press              -> press-admission, direct-ios-eligibility, direct-ios, runtime, response
press-admission    -> android-freshness
fill               -> android-freshness, runtime, response
direct-ios         -> response
runtime            -> android-readiness, response
response           -> payload

Test topology (1:1 with production)

interaction.test.ts (3,987 lines, 86 cases) + interaction-android-recovery-abort.test.ts (1 case) were redistributed. The sorted Vitest name manifest is identical before and after — 87 unique names, no rename, no rewritten assertion, bodies moved verbatim.

Test file LOC
interaction-touch.test.ts 228
interaction-touch-press-admission.test.ts 428
interaction-touch-press.test.ts 416
interaction-touch-fill.test.ts 395
interaction-touch-direct-ios-eligibility.test.ts 288
interaction-touch-direct-ios.test.ts 343
interaction-touch-runtime.test.ts 361
interaction-touch-android-readiness.test.ts 314
interaction-touch-android-freshness.test.ts 352
interaction-touch-response.test.ts 456
interaction-touch-payload.test.ts 423
interaction-touch-fixtures.ts (shared pure factories) 227
interaction.test.ts (retained get/is legacy) 845

Every touch suite is under the 500-line extraction tripwire. The retained interaction.test.ts holds only non-touch get/is cases and is acknowledged pre-existing debt (AGENTS.md names it as shrinking opportunistically).

interaction-android-recovery-abort.test.ts is deleted; its regression lives in the readiness suite, which spies on ensureAndroidBlockingSystemDialogReady and delegates to the real implementation by default so the neighbouring cases keep exercising it.

Every suite installs the same device mock set. Trimming mocks per file made three Android-session cases depend on the host toolchain: without adb on PATH they returned TOOL_MISSING instead of COMMAND_FAILED (caught by the Coverage lane, reproduced locally by removing adb from PATH, and re-verified green that way).

Deliberate deviations from the plan in #1691

  1. Four extra production modules beyond the plan's list: press-admission, direct-ios-eligibility, android-freshness, payload. The first came from removing the fallow-ignore-next-line complexity waiver the plan asks for (decomposing dispatchTargetedTouchViaRuntime pushed press past 300 lines); the other three give the over-budget suites a real production question to mirror rather than an arbitrary test shard.
  2. Fallow health baseline re-keyed, net −1 finding. interaction.test.ts (6 crap_moderate) and interaction-touch.ts (3) are replaced by four test-file entries totalling 8 — the same moved test bodies at their new paths. Zero findings on the new production modules: the two real ones (dispatchTargetedTouchViaRuntime, hasNonDefaultClickOptions) were fixed, not baselined. The baseline is path-keyed, so a split cannot avoid re-keying; nothing was regenerated or loosened, and the entries satisfy both audit bases (merge-base and origin/main).
  3. One ADR pointer updated. docs/adr/0014-session-ref-frame-lifetime.md named the deleted test file; it now names interaction-touch-android-readiness.test.ts. No decision text changed.

Verification

  • pnpm check:affected --run — green
  • pnpm check:layering — green (no new cycle, back-edge, or ratchet growth)
  • pnpm exec vitest run --project interaction-contract — 73/73
  • pnpm test:coverage (full suite) — 6,680 tests green
  • Touch suites re-run with adb removed from PATH — green (the regression the Coverage lane caught)
  • ADR 0011 response-construction guard proven to scan the new sources (planted responseData literal failed the guard; removed)
  • Test-name manifest: 87 unique names, sorted lists identical to origin/main's baseline
  • Rebased on origin/main after refactor(contracts): consolidate per-domain defineUse wrappers into one neutral defineUse #1741; gates re-run on the rebased head

@thymikee
thymikee force-pushed the refactor/1691-split-touch-interaction branch from d806774 to b017db4 Compare August 11, 2026 15:40
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 2.18 MB 2.18 MB +716 B
JS gzip 712.0 kB 712.3 kB +246 B
npm tarball 838.3 kB 838.5 kB +210 B
npm unpacked 2.92 MB 2.92 MB +716 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.5 ms 27.7 ms +0.2 ms
CLI --help 68.0 ms 67.1 ms -1.0 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/interaction.js +731 B +254 B

@thymikee

Copy link
Copy Markdown
Member Author

Exact-head review of b017db4d: P2 architecture/readiness — the test decomposition is not complete. Tests are not exempt from the module-size rules. The newly created interaction-touch-response.test.ts (839 LOC), interaction-touch-press.test.ts (753), interaction-touch-android-readiness.test.ts (558), and interaction-touch-direct-ios.test.ts (518) remain over the 500-line extraction tripwire. More concretely, production now has a distinct interaction-touch-press-admission.ts question, but no 1:1 sibling test; admission policy/ref-admission cases are still mixed into the press execution suite.

Please split these suites along coherent production questions—at minimum add the matching press-admission test, and introduce coherent production seams/subquestions for the other newly created over-budget suites—while retaining the shared fixture module and preserving the exact cases/assertions. The retained legacy interaction.test.ts at 845 LOC is still debt, but this PR materially shrinks it, so it is not the blocker.

I found no runtime behavior-preservation defect: the production route remains through handleInteractionCommands, the interaction/response and ADR 0014 seams are preserved, and moved-test coverage remains production-routed. CI is still pending, so those authoritative gates also remain merge blockers.

Closes part of #1691: interaction-touch.ts becomes a router; press, fill,
direct-iOS, shared runtime, Android readiness, and response projection each
own one module. Behavior is unchanged.
Redistributes all 87 discovered cases across the new module topology and
re-keys the two touch-family fallow baseline entries to the paths that now
hold the same (net one fewer) findings.
Keeps interaction-touch-press.ts inside the 300-line budget after the
complexity decomposition: admission (surface/capability/button policy, target
parsing, @ref staleness and mutation admission) answers its own question.
Adds the press-admission suite the production module was missing and splits the
four over-budget suites along new production seams (direct-iOS eligibility,
Android ref freshness, touch payload). Every suite installs the full device mock
set: three Android-session cases regressed to TOOL_MISSING on a runner without
adb when the mock set was trimmed per file.
@thymikee
thymikee force-pushed the refactor/1691-split-touch-interaction branch from b017db4 to 073f852 Compare August 11, 2026 16:55
@thymikee

Copy link
Copy Markdown
Member Author

Addressed on 073f852f — every touch suite is now under the 500-line tripwire, and each one mirrors a production question rather than an arbitrary shard.

Three new production seams carry the three over-budget suites that had no sibling to split along:

New module Question Suite it gives a 1:1 partner
interaction-touch-direct-ios-eligibility.ts (64) May this click take the fast path at all? …-direct-ios-eligibility.test.ts (288)
interaction-touch-android-freshness.ts (41) What does the @ref pre-dispatch refresh capture do? …-android-freshness.test.ts (352)
interaction-touch-payload.ts (84) What does a built touch payload look like? …-payload.test.ts (423)

Both splits follow a real consumer boundary, not a line count: eligibility is what press asks before dispatch while direct-ios is what runs after; freshness is consumed by press-admission/fill while dialog readiness is consumed by runtime; payload is the field merge and message, while response keeps identity and disclosure composition.

The press-admission suite you asked for exists (interaction-touch-press-admission.test.ts, 428): click-option/positional validation plus the ADR 0014 @ref rules (staleness, pins, frame expiry, locator provenance). Those cases came out of the press execution suite and out of the runtime suite, where the ref-frame sequences had also been misfiled. Press (416) now answers only "which node does an admitted press act on" — promotion, macOS menubar, button projection, and the #1654 pre-resolved find node — and runtime (361) answers "what the shared dispatch does with the resolved target", which is where the frame-evidence refusals belong.

Final suite sizes: touch 228 · press-admission 428 · press 416 · fill 395 · direct-ios-eligibility 288 · direct-ios 343 · runtime 361 · android-readiness 314 · android-freshness 352 · response 456 · payload 423 · fixtures 227. The shared fixture module is retained and grew to hold the #1654 helpers now used by two suites.

Manifest identity re-verified after the second split: the sorted Vitest name list is byte-identical to origin/main's (87 unique names), regenerated by listing the original two files at their original paths rather than trusting the earlier run.

One real defect the Coverage lane caught, now fixed. Trimming the mock set per file made three Android-session cases depend on the host toolchain — without adb on PATH they returned TOOL_MISSING instead of COMMAND_FAILED. Every suite now installs the same device mock set; I reproduced the failure locally by removing adb from PATH and re-ran all touch suites green that way.

Fallow: the baseline re-key is now net −1 against origin/main (interaction.test.ts 6 + interaction-touch.ts 3 → four test-file entries totalling 8), with zero findings on any new production module, and it satisfies both audit bases (merge-base and --base origin/main).

🤖 Addressed by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact head 073f852. The prior architecture blocker is fixed: every new touch production and test module is below the 500-line tripwire, and interaction-touch-press-admission.ts now has its own 1:1 admission suite. The additional direct-iOS eligibility, Android freshness, and payload seams are real production questions rather than arbitrary test shards. The 87-case decomposition remains behavior-preserving: source helper bodies move unchanged behind direct imports/call sites, with no forwarding wrappers, alternative routes, or new platform behavior; the sorted test-name manifest is unchanged. All authoritative checks, coverage/integration, and platform smokes are green. No new finding remains; code-review clean / ready-for-human despite draft status.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Aug 11, 2026
@thymikee
thymikee marked this pull request as ready for review August 11, 2026 18:06
@thymikee
thymikee merged commit 52402ae into main Aug 11, 2026
31 checks passed
@thymikee
thymikee deleted the refactor/1691-split-touch-interaction branch August 11, 2026 18:07
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-11 18:07 UTC

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.

Split touch interaction orchestration into semantic modules

1 participant