Skip to content

refactor(daemon): one capture-input builder and one admit-then-bind step - #1876

Merged
thymikee merged 1 commit into
mainfrom
agent/wave4-find
Aug 19, 2026
Merged

refactor(daemon): one capture-input builder and one admit-then-bind step#1876
thymikee merged 1 commit into
mainfrom
agent/wave4-find

Conversation

@thymikee

@thymikee thymikee commented Aug 19, 2026

Copy link
Copy Markdown
Member

Summary

Behaviour-neutral cleanup around the request-bound snapshot capture path. No descriptor changes its platform execution, no contract surface is added, and no workspace package is touched — the diff is five source files plus one test-helper import path. Three things:

  • One capture-input builder. buildRuntimeCaptureInput moves out of snapshot-runtime-binding.ts into snapshot-runtime-capture-input.ts — the one place a daemon request becomes neutral CaptureSnapshotInput. Its parameter list is exactly what snapshot/diff pass; nothing speculative.
  • One named admit-then-bind step. The snapshot/diff resolver's admission sequence — side-effect-free facts inspection, refusal before any binding, then exactly one bind on the device the admission was minted for — becomes admitAndBindSnapshotCapture, module-private. Same behaviour, one place.
  • handlers/find.ts splits, 600 → 346 lines. It was already past the 300-line tripwire and answered three questions at once. The target-capture policy (interactive-only tree plus find's two sparse-recovery re-captures) and the match-resolution pipeline (locator/selector matching, on-screen preference, interactive ranking, the ambiguous-match error) move to focused modules. Pure refactor: identical behaviour, response shapes, capture path, and tests.

Scope note

This PR was re-scoped twice under review, each time correctly:

  1. It began as the find cutover. Declaring find device-runtime claims its whole platform-execution projection (ADR 0019 §6), while find focus and find type still reach the device through dispatchCommand — Wave 5 surfaces — and a third edge of the same class, find <q> get textdispatchCommand(device, 'read'), is shared with get. find keeps LEGACY_PLATFORM_EXECUTION, its capability bucket, its requireCommandSupported('find', …) admission, and its HARMONYOS_SUPPORTED_COMMANDS / WEB_QUERY_COMMANDS entries. Rule id R35 is withdrawn from the cutover table entirely rather than left half-claimed, and stays reserved for find's real cutover.

  2. It then carried the selector capture seam, and then CaptureSnapshotInput.signal with its exported captureSnapshotSignal helper and Apple/Linux composition. Both are now gone, removed for the same reason: ADR 0019 §10 prohibits a facet PR with no consuming command, and §6 admits an early behaviour-neutral substrate only when it is dead-code clean.

    On the signal contract specifically: an earlier revision of this body carried it with a caveat that no caller set the field. Review escalated that caveat to a blocker, and rightly — an unset contract field means production exercises only the undefined branch, and this PR could not carry a regression proving the per-poll abort and quiescence behaviour it claimed. The field, the helper, and the Apple/Linux composition sites now land in refactor: migrate wait to request-bound runtime #1875 (wait), whose runWithinWaitDeadline is the route that actually sets it and which owns the regression proving the deadline aborts an in-flight capture, waits for it to quiesce, and produces capture-stalled rather than target-absent. The selector capture seam went to refactor: migrate get to the request-bound device runtime #1877 (get) on the same principle. Both were handed over as reviewed patches with design notes.

What remains has live production consumers today and is dead-code clean: check:production-exports is unchanged from main.

Validation

pnpm check:affected --run from a clean committed tree. Behaviour neutrality is carried by the existing suites rather than new assertions — snapshot, diff, and find keep their existing paths, and every touched command's test file is byte-identical to main, so they exercise the split find route unchanged. pnpm check:layering is green with the migrated-command list identical to main.

Tradeoffs and known gaps

  • Both extractions have a single consumer today. They are named steps, not deduplication — the second caller is the selector family, arriving with get. admitAndBindSnapshotCapture is deliberately module-private so this PR does not grow the export surface; get's patch flips it to exported, a one-word change.
  • Source size: root production TypeScript +3,394 B, workspace-package production TypeScript +0 B. Of the root figure, +1,250 B is the find.ts three-way split (module headers, imports, exports — the unavoidable cost of a split the tripwire required) and +2,144 B the two extractions, of which roughly 1.4 kB is the capture-input builder moving rather than new code. Packaged deltas: see the CI Size report on this head rather than a local comparison.
  • No behaviour, CLI, help, or docs change, so nothing in website/docs/** or skills/** was touched.

Touched files: 6. Scope narrowed three times; it never expanded.

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 2.32 MB 2.32 MB +290 B
JS gzip 762.2 kB 762.3 kB +52 B
npm tarball 885.6 kB 885.7 kB +32 B
npm unpacked 3.09 MB 3.09 MB +290 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 29.2 ms 27.4 ms -1.8 ms
CLI --help 69.3 ms 67.3 ms -2.1 ms

Top changed chunks: no changes in the largest emitted chunks.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed exact head 5029e01cd9ab2a3b3d519c8e27b1148e5af88de7. One blocking cutover issue: find is declared wholly device-runtime with only selector-capture uses, while find focus and find type still call legacy dispatchCommand directly in src/daemon/handlers/find.ts (type around line 290; focus around 312). ADR 0019 §6 requires the canonical descriptor’s whole platform-execution projection to move atomically with no old/new split; R35 currently observes only capture and cannot prove these paths. Please either keep find legacy until focus/type migrate, or move/reroute those paths in this unit and extend parity/cutover evidence. Separately, the posted source-size acceptance budget is breached (+10,736 B root; package +1,699 B vs +1,500), so this also needs an explicit tracker decision. CI is green and the Android capture/poll evidence is sound, but neither resolves those blockers. Not ready-for-human at this head.

@thymikee thymikee changed the title refactor: migrate find to the request-bound device runtime refactor(daemon): one capture-input builder and one admit-then-bind step Aug 19, 2026
@thymikee
thymikee marked this pull request as ready for review August 19, 2026 12:28
@thymikee

Copy link
Copy Markdown
Member Author

Re-scoped since your review at 5029e01cd, and moved out of draft at 30435df1b.

Both blockers from that review are addressed by removing the thing they applied to. This PR no longer migrates find:

  • The §6 old/new split is gone because the cutover is gone. find is back to LEGACY_PLATFORM_EXECUTION, its capability bucket and both capability-list entries are restored, the R35 row is withdrawn rather than left half-proved, and src/core/capabilities.ts, the registry, the cutover table, and both parity fixtures are byte-identical to main. find's cutover is deferred behind Wave 5 focus/type — it cannot flip atomically while find focus, find type, and find <q> get text reach platforms through legacy dispatchCommand.
  • The size breach shrank with the scope: root production TS +3,640 B (was +10,736), packages +1,018 B (was +1,699). Packaged: raw JS +451 B (+0.019%), gzip +121 B (+0.016%), tarball +88 B, unpacked +451 B. check:production-exports is back to 19, unchanged from main.

What is left is behavior-neutral and consumed: one capture-input builder (snapshot-runtime-capture-input.ts), one admit-then-bind step (module-private — with a single caller it does not earn an export), the CaptureSnapshotInput.signal / captureSnapshotSignal contract, and the handlers/find.ts 600→346 split. All four have live consumers in the already-migrated snapshot/diff path.

The selector seam that was here has moved to #1877, its first consuming command unit. ADR 0019 line 686 prohibits a facet PR with no consuming command, and line 448 permits an early substrate only when it is dead-code clean — with find reverted the selector half had no production consumer and pushed check:production-exports 19 → 20. Covering it with focused tests does not satisfy that condition, since --production is blind to test consumers by design. This is the same rule the tracker already records for wave 0, where gateway facts inspection was kept out of the substrate and landed inside boot.

Two honesty notes carried in the body rather than smoothed over: both extractions have a single consumer today, so they are named steps rather than deduplication — the second caller arrives with #1877 — and CaptureSnapshotInput.signal currently has no caller setting it (wait is the first, and depends on it for its poll deadline).

All 28 required checks green at this head.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed exact head 30435df1b6ec614839062937950a348e94c236e6. The reduced find split and capture-input move are behavior-neutral, all exact-head checks are green, and size is below the heightened-review thresholds. One blocker remains: CaptureSnapshotInput.signal and exported captureSnapshotSignal add public contract surface for a consumer that does not exist on this head. No caller sets the field, so current production routes exercise only the undefined branch, and this PR carries no regression proving the claimed per-poll abort/quiescence behavior—the body says that evidence travels with a later consumer. Please move this field/helper and its Apple/Linux composition into the first polling-command PR, with the consuming route and non-vacuous regression. Until then this is an unproved future seam rather than behavior-neutral cleanup. No ready-for-human yet.

thymikee pushed a commit that referenced this pull request Aug 19, 2026
…nsumer

Takes ownership of the request-bound selector capture seam from #1876, which
cannot ship standalone: with find's cutover deferred it had no consuming
command (ADR 0019 §10) and was not dead-code clean (check:production-exports
19 -> 20). `get` is its first consumer, so it lands here.

Adopts find's handoff as given. The one shape change, approved by the
coordinator: the selector family gets its own capture uses carrying a PREFERRED
`readTextAtPoint`, declared ALONGSIDE the snapshot uses so `snapshot`/`diff`
keep binding exactly what they bind today. The read is surfaced through the
existing arms of `bindSnapshotCaptureRuntime`, reusing the same
selectActiveAppSnapshot / selectSnapshotWithoutActiveApp selectors — no second
plan-to-operation dispatch.

`get` now runs through `createBoundSelectorRuntime`; `resolveBoundGetRuntime`
and its test are deleted as superseded, and `'get'` leaves the
`createSelectorRuntime` capability union.

The legacy read adapter survives for `find <q> get text` and is selected by
which command constructed the runtime — never by failure, family, environment,
or flag — so `get` cannot reach it. It retires in find's cutover, where the
last consumer moves.
@thymikee
thymikee marked this pull request as draft August 19, 2026 14:59
Behaviour-neutral. No descriptor changes platform execution, the cutover table
is untouched, and no contract surface is added.

- buildRuntimeCaptureInput moves to its own module so every request-bound
  capture consumer builds CaptureSnapshotInput one way.
- The admit-then-bind sequence in the snapshot/diff resolver becomes one named
  step, ready for the selector units' second caller.
- handlers/find.ts splits into focused target-capture and match-resolution
  concepts (600 -> 346 lines); behaviour unchanged.
@thymikee

Copy link
Copy Markdown
Member Author

Reviewed exact head 3bf786f5f82302de5494f9a9e494bbf050f9ee35. The prior public-cancellation blocker is resolved by removal: CaptureSnapshotInput carries no signal, captureSnapshotSignal is absent, and no Apple/Linux composition remains. The remaining snapshot/diff and find extractions preserve their existing production routes; find remains legacy, so no ADR 0019 partial-cutover claim remains. CI Size is +290 B unpacked. Code review is clean; merge readiness awaits the pending Smoke and Coverage checks, and the PR remains draft.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Aug 19, 2026
thymikee pushed a commit that referenced this pull request Aug 19, 2026
…nsumer

Takes ownership of the request-bound selector capture seam from #1876, which
cannot ship standalone: with find's cutover deferred it had no consuming
command (ADR 0019 §10) and was not dead-code clean (check:production-exports
19 -> 20). `get` is its first consumer, so it lands here.

Adopts find's handoff as given. The one shape change, approved by the
coordinator: the selector family gets its own capture uses carrying a PREFERRED
`readTextAtPoint`, declared ALONGSIDE the snapshot uses so `snapshot`/`diff`
keep binding exactly what they bind today. The read is surfaced through the
existing arms of `bindSnapshotCaptureRuntime`, reusing the same
selectActiveAppSnapshot / selectSnapshotWithoutActiveApp selectors — no second
plan-to-operation dispatch.

`get` now runs through `createBoundSelectorRuntime`; `resolveBoundGetRuntime`
and its test are deleted as superseded, and `'get'` leaves the
`createSelectorRuntime` capability union.

The legacy read adapter survives for `find <q> get text` and is selected by
which command constructed the runtime — never by failure, family, environment,
or flag — so `get` cannot reach it. It retires in find's cutover, where the
last consumer moves.
thymikee pushed a commit that referenced this pull request Aug 19, 2026
Two edits, per find's ADDENDUM.md:

1. `includeRects` returns to `buildRuntimeCaptureInput`. It was removed from
   #1876 as unconsumed; the selector capture path is genuinely its first
   consumer (a Web rect capture requests bounds explicitly), so it lands here
   under the same rule that moved the seam. `snapshot`/`diff` pass nothing.

2. The per-capture `signal` is dropped, not restored. `CaptureSnapshotInput`
   has no such field on this stack — it moved to `wait` (#1875) with the
   regression that proves per-poll abort and quiescence. `get` captures once
   per resolution and never polls, so nothing here needs it. The seam test and
   fixture coverage for it moves with the contract rather than being kept
   against a field that no longer exists.
@thymikee
thymikee marked this pull request as ready for review August 19, 2026 15:34
@thymikee
thymikee merged commit 735ab76 into main Aug 19, 2026
28 checks passed
@thymikee
thymikee deleted the agent/wave4-find branch August 19, 2026 15:34
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-19 15:35 UTC

thymikee pushed a commit that referenced this pull request Aug 19, 2026
…nsumer

Takes ownership of the request-bound selector capture seam from #1876, which
cannot ship standalone: with find's cutover deferred it had no consuming
command (ADR 0019 §10) and was not dead-code clean (check:production-exports
19 -> 20). `get` is its first consumer, so it lands here.

Adopts find's handoff as given. The one shape change, approved by the
coordinator: the selector family gets its own capture uses carrying a PREFERRED
`readTextAtPoint`, declared ALONGSIDE the snapshot uses so `snapshot`/`diff`
keep binding exactly what they bind today. The read is surfaced through the
existing arms of `bindSnapshotCaptureRuntime`, reusing the same
selectActiveAppSnapshot / selectSnapshotWithoutActiveApp selectors — no second
plan-to-operation dispatch.

`get` now runs through `createBoundSelectorRuntime`; `resolveBoundGetRuntime`
and its test are deleted as superseded, and `'get'` leaves the
`createSelectorRuntime` capability union.

The legacy read adapter survives for `find <q> get text` and is selected by
which command constructed the runtime — never by failure, family, environment,
or flag — so `get` cannot reach it. It retires in find's cutover, where the
last consumer moves.
thymikee pushed a commit that referenced this pull request Aug 19, 2026
Two edits, per find's ADDENDUM.md:

1. `includeRects` returns to `buildRuntimeCaptureInput`. It was removed from
   #1876 as unconsumed; the selector capture path is genuinely its first
   consumer (a Web rect capture requests bounds explicitly), so it lands here
   under the same rule that moved the seam. `snapshot`/`diff` pass nothing.

2. The per-capture `signal` is dropped, not restored. `CaptureSnapshotInput`
   has no such field on this stack — it moved to `wait` (#1875) with the
   regression that proves per-poll abort and quiescence. `get` captures once
   per resolution and never polls, so nothing here needs it. The seam test and
   fixture coverage for it moves with the contract rather than being kept
   against a field that no longer exists.
thymikee pushed a commit that referenced this pull request Aug 20, 2026
…nsumer

Takes ownership of the request-bound selector capture seam from #1876, which
cannot ship standalone: with find's cutover deferred it had no consuming
command (ADR 0019 §10) and was not dead-code clean (check:production-exports
19 -> 20). `get` is its first consumer, so it lands here.

Adopts find's handoff as given. The one shape change, approved by the
coordinator: the selector family gets its own capture uses carrying a PREFERRED
`readTextAtPoint`, declared ALONGSIDE the snapshot uses so `snapshot`/`diff`
keep binding exactly what they bind today. The read is surfaced through the
existing arms of `bindSnapshotCaptureRuntime`, reusing the same
selectActiveAppSnapshot / selectSnapshotWithoutActiveApp selectors — no second
plan-to-operation dispatch.

`get` now runs through `createBoundSelectorRuntime`; `resolveBoundGetRuntime`
and its test are deleted as superseded, and `'get'` leaves the
`createSelectorRuntime` capability union.

The legacy read adapter survives for `find <q> get text` and is selected by
which command constructed the runtime — never by failure, family, environment,
or flag — so `get` cannot reach it. It retires in find's cutover, where the
last consumer moves.
thymikee pushed a commit that referenced this pull request Aug 20, 2026
Two edits, per find's ADDENDUM.md:

1. `includeRects` returns to `buildRuntimeCaptureInput`. It was removed from
   #1876 as unconsumed; the selector capture path is genuinely its first
   consumer (a Web rect capture requests bounds explicitly), so it lands here
   under the same rule that moved the seam. `snapshot`/`diff` pass nothing.

2. The per-capture `signal` is dropped, not restored. `CaptureSnapshotInput`
   has no such field on this stack — it moved to `wait` (#1875) with the
   regression that proves per-poll abort and quiescence. `get` captures once
   per resolution and never polls, so nothing here needs it. The seam test and
   fixture coverage for it moves with the contract rather than being kept
   against a field that no longer exists.
thymikee pushed a commit that referenced this pull request Aug 20, 2026
…nsumer

Takes ownership of the request-bound selector capture seam from #1876, which
cannot ship standalone: with find's cutover deferred it had no consuming
command (ADR 0019 §10) and was not dead-code clean (check:production-exports
19 -> 20). `get` is its first consumer, so it lands here.

Adopts find's handoff as given. The one shape change, approved by the
coordinator: the selector family gets its own capture uses carrying a PREFERRED
`readTextAtPoint`, declared ALONGSIDE the snapshot uses so `snapshot`/`diff`
keep binding exactly what they bind today. The read is surfaced through the
existing arms of `bindSnapshotCaptureRuntime`, reusing the same
selectActiveAppSnapshot / selectSnapshotWithoutActiveApp selectors — no second
plan-to-operation dispatch.

`get` now runs through `createBoundSelectorRuntime`; `resolveBoundGetRuntime`
and its test are deleted as superseded, and `'get'` leaves the
`createSelectorRuntime` capability union.

The legacy read adapter survives for `find <q> get text` and is selected by
which command constructed the runtime — never by failure, family, environment,
or flag — so `get` cannot reach it. It retires in find's cutover, where the
last consumer moves.
thymikee pushed a commit that referenced this pull request Aug 20, 2026
Two edits, per find's ADDENDUM.md:

1. `includeRects` returns to `buildRuntimeCaptureInput`. It was removed from
   #1876 as unconsumed; the selector capture path is genuinely its first
   consumer (a Web rect capture requests bounds explicitly), so it lands here
   under the same rule that moved the seam. `snapshot`/`diff` pass nothing.

2. The per-capture `signal` is dropped, not restored. `CaptureSnapshotInput`
   has no such field on this stack — it moved to `wait` (#1875) with the
   regression that proves per-poll abort and quiescence. `get` captures once
   per resolution and never polls, so nothing here needs it. The seam test and
   fixture coverage for it moves with the contract rather than being kept
   against a field that no longer exists.
thymikee pushed a commit that referenced this pull request Aug 20, 2026
…nsumer

Takes ownership of the request-bound selector capture seam from #1876, which
cannot ship standalone: with find's cutover deferred it had no consuming
command (ADR 0019 §10) and was not dead-code clean (check:production-exports
19 -> 20). `get` is its first consumer, so it lands here.

Adopts find's handoff as given. The one shape change, approved by the
coordinator: the selector family gets its own capture uses carrying a PREFERRED
`readTextAtPoint`, declared ALONGSIDE the snapshot uses so `snapshot`/`diff`
keep binding exactly what they bind today. The read is surfaced through the
existing arms of `bindSnapshotCaptureRuntime`, reusing the same
selectActiveAppSnapshot / selectSnapshotWithoutActiveApp selectors — no second
plan-to-operation dispatch.

`get` now runs through `createBoundSelectorRuntime`; `resolveBoundGetRuntime`
and its test are deleted as superseded, and `'get'` leaves the
`createSelectorRuntime` capability union.

The legacy read adapter survives for `find <q> get text` and is selected by
which command constructed the runtime — never by failure, family, environment,
or flag — so `get` cannot reach it. It retires in find's cutover, where the
last consumer moves.
thymikee pushed a commit that referenced this pull request Aug 20, 2026
Two edits, per find's ADDENDUM.md:

1. `includeRects` returns to `buildRuntimeCaptureInput`. It was removed from
   #1876 as unconsumed; the selector capture path is genuinely its first
   consumer (a Web rect capture requests bounds explicitly), so it lands here
   under the same rule that moved the seam. `snapshot`/`diff` pass nothing.

2. The per-capture `signal` is dropped, not restored. `CaptureSnapshotInput`
   has no such field on this stack — it moved to `wait` (#1875) with the
   regression that proves per-poll abort and quiescence. `get` captures once
   per resolution and never polls, so nothing here needs it. The seam test and
   fixture coverage for it moves with the contract rather than being kept
   against a field that no longer exists.
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.

1 participant