diff --git a/fallow-baselines/health.json b/fallow-baselines/health.json index 0dd017e755..98666fd656 100644 --- a/fallow-baselines/health.json +++ b/fallow-baselines/health.json @@ -567,7 +567,7 @@ "src/daemon/replay/internal/native-command.ts:complexity", "packages/platform-apple/src/core/debug-symbols/utils.ts:high impact", "packages/platform-linux/src/snapshot.ts:high impact", - "src/core/interaction-targeting.ts:high impact", + "packages/selectors/src/interaction-targeting.ts:high impact", "packages/maestro/src/internal/runtime-targets.ts:high impact", "packages/kernel/src/source-value.ts:high impact", "src/request/cancel.ts:high impact", diff --git a/packages/ad-script/src/internal/target-annotation-identity.ts b/packages/ad-script/src/internal/target-annotation-identity.ts index 765f58c1ee..1a3faa5465 100644 --- a/packages/ad-script/src/internal/target-annotation-identity.ts +++ b/packages/ad-script/src/internal/target-annotation-identity.ts @@ -105,7 +105,7 @@ export function idMatchCountInTree(nodes: readonly IdentityTreeNode[], id: strin * `idMatchCountInTree` predicate `buildSelectorChainForNode`'s * `selectableId` keys off directly. `computeTargetEvidence` uses this * whole-identity form; extracted so a third call site (#1280's - * press-retarget identity-empty check, `src/core/press-retarget.ts`) + * press-retarget identity-empty check, `packages/selectors/src/press-retarget.ts`) * shares it rather than re-deriving the rule a third way. A demoted id * falls back to role+label, the same shape an unrecorded id already * computes. diff --git a/packages/contracts/src/interaction-guarantees.ts b/packages/contracts/src/interaction-guarantees.ts index 4c3aa26a68..825898cd82 100644 --- a/packages/contracts/src/interaction-guarantees.ts +++ b/packages/contracts/src/interaction-guarantees.ts @@ -155,11 +155,11 @@ const RUNTIME_TREE_SHARED_GUARANTEES = { // predicate it applies (and the annotation contract it reads). occlusion: { kind: 'runtime', - via: 'src/core/selector-pipeline.ts#runNodePipelineStages', + via: 'packages/selectors/src/selector-pipeline.ts#runNodePipelineStages', }, parentOwnedTouchPoint: { kind: 'runtime', - via: 'src/core/interaction-touch-point.ts#resolveInteractionTouchPoint', + via: 'packages/selectors/src/interaction-touch-point.ts#resolveInteractionTouchPoint', }, // #1542: the base decision is the contracts-owned snapshot visibility resolver (bulk accessibility // tree), but throwIfOffscreenInteractionTarget is the actual end-to-end @@ -178,7 +178,7 @@ const RUNTIME_TREE_SHARED_GUARANTEES = { // is still resolveActionableTouchResolution. nonHittable: { kind: 'runtime', - via: 'src/core/selector-pipeline.ts#runNodePipelineStages', + via: 'packages/selectors/src/selector-pipeline.ts#runNodePipelineStages', }, responseConstruction: SHARED_RESPONSE_CONSTRUCTION, responseIdentity: { @@ -254,11 +254,11 @@ export const INTERACTION_DISPATCH_PATHS: Record = fc + .record({ + x: fc.integer({ min: -200, max: 200 }), + y: fc.integer({ min: -200, max: 200 }), + width: fc.integer({ min: 1, max: 1200 }), + height: fc.integer({ min: 1, max: 1200 }), + }) + .chain((ancestor) => + fc.constantFrom('x', 'y', 'width', 'height').map((field) => ({ + ancestor, + target: { ...ancestor, [field]: ancestor[field] + 1 }, + })), + ); + +export type InteractionTouchPointScenario = { + nodes: SnapshotNode[]; + permutedNodes: SnapshotNode[]; + target: SnapshotNode; + bound: Rect; + competitorRects: Rect[]; +}; + +const halfPixel = (value: number): number => value / 2; +const touchPointAxisStepsArb = fc.oneof( + fc.integer({ min: 4, max: 46 }), + fc.integer({ min: 48, max: 800 }), +); + +const touchPointTargetRectArb = fc + .record({ + x: fc.integer({ min: -200, max: 200 }), + y: fc.integer({ min: -200, max: 200 }), + // Exercise dense desktop rows as well as standard mobile touch targets. + width: touchPointAxisStepsArb, + height: touchPointAxisStepsArb, + }) + .map(({ x, y, width, height }) => ({ + x: halfPixel(x), + y: halfPixel(y), + width: halfPixel(width), + height: halfPixel(height), + })); + +function containedRectArb(container: Rect): fc.Arbitrary { + const widthSteps = Math.round(container.width * 2); + const heightSteps = Math.round(container.height * 2); + return fc + .record({ + width: fc.integer({ min: 2, max: widthSteps - 2 }), + height: fc.integer({ min: 2, max: heightSteps - 2 }), + }) + .chain(({ width, height }) => + fc + .record({ + x: fc.integer({ min: 0, max: widthSteps - width }), + y: fc.integer({ min: 0, max: heightSteps - height }), + }) + .map(({ x, y }) => ({ + x: container.x + halfPixel(x), + y: container.y + halfPixel(y), + width: halfPixel(width), + height: halfPixel(height), + })), + ); +} + +export const interactionTouchPointScenarioArb: fc.Arbitrary = + touchPointTargetRectArb.chain((targetRect) => + fc + .tuple( + fc.array(containedRectArb(targetRect), { minLength: 1, maxLength: 6 }), + containedRectArb(targetRect), + ) + .chain(([competitorRects, bound]) => { + const nodes = attachRefs([ + { + index: 0, + depth: 0, + type: 'Link', + label: 'Generated parent', + rect: targetRect, + hittable: true, + }, + ...competitorRects.map((rect, offset) => ({ + index: offset + 1, + depth: 1, + parentIndex: 0, + type: 'Button', + label: `Generated child ${offset + 1}`, + rect, + hittable: true, + })), + ]); + return fc + .shuffledSubarray(nodes, { minLength: nodes.length, maxLength: nodes.length }) + .map((permutedNodes) => ({ + nodes, + permutedNodes, + target: nodes[0]!, + bound, + competitorRects, + })); + }), + ); + const SELECTOR_KEY_VALUE_KINDS = { id: 'text', role: 'text', diff --git a/packages/selectors/src/internal/__tests__/snapshot-builders.ts b/packages/selectors/src/internal/__tests__/snapshot-builders.ts new file mode 100644 index 0000000000..bcdffadfab --- /dev/null +++ b/packages/selectors/src/internal/__tests__/snapshot-builders.ts @@ -0,0 +1,19 @@ +import { + attachRefs, + type RawSnapshotNode, + type SnapshotState, + type SnapshotStateProvenance, +} from '@agent-device/kernel/snapshot'; + +export function makeSnapshotState( + raw: RawSnapshotNode[], + // The provenance pair stays correlated: overrides carry it as one value, never as two + // independently typed fields. + overrides?: Omit, 'backend' | 'producer'> & SnapshotStateProvenance, +): SnapshotState { + return { + nodes: attachRefs(raw), + createdAt: Date.now(), + ...overrides, + }; +} diff --git a/packages/selectors/src/internal/resolution-policy.ts b/packages/selectors/src/internal/resolution-policy.ts index c37e0d950b..742794248d 100644 --- a/packages/selectors/src/internal/resolution-policy.ts +++ b/packages/selectors/src/internal/resolution-policy.ts @@ -29,7 +29,7 @@ import type { SelectorResolutionOptions } from './public-resolution-types.ts'; * * The surrounding pipeline stages — occlusion, the off-screen guard, * hittable-ancestor promotion, and the wait poll budget — are declared in the - * companion structural table, `src/core/selector-pipeline-policy.ts` (#1656), + * companion structural table, `packages/selectors/src/selector-pipeline-policy.ts` (#1656), * whose rows each name one row of this matrix. They live there rather than * here because this package is deliberately blind to snapshot occlusion * annotations, backend visibility probes, and the wait clock: a column here diff --git a/src/core/press-retarget.ts b/packages/selectors/src/press-retarget.ts similarity index 100% rename from src/core/press-retarget.ts rename to packages/selectors/src/press-retarget.ts diff --git a/src/core/selector-pipeline-policy.ts b/packages/selectors/src/selector-pipeline-policy.ts similarity index 100% rename from src/core/selector-pipeline-policy.ts rename to packages/selectors/src/selector-pipeline-policy.ts diff --git a/src/core/selector-pipeline.test.ts b/packages/selectors/src/selector-pipeline.test.ts similarity index 99% rename from src/core/selector-pipeline.test.ts rename to packages/selectors/src/selector-pipeline.test.ts index f878e78a78..d955c8702c 100644 --- a/src/core/selector-pipeline.test.ts +++ b/packages/selectors/src/selector-pipeline.test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import { test } from 'vitest'; import type { RawSnapshotNode, SnapshotNode } from '@agent-device/kernel/snapshot'; import { SELECTOR_RESOLUTION_POLICIES } from '@agent-device/selectors'; -import { makeSnapshotState } from '../__tests__/test-utils/snapshot-builders.ts'; +import { makeSnapshotState } from './internal/__tests__/snapshot-builders.ts'; import { listSelectorPipelineMatches, resolveSelectorPipeline, diff --git a/src/core/selector-pipeline.ts b/packages/selectors/src/selector-pipeline.ts similarity index 100% rename from src/core/selector-pipeline.ts rename to packages/selectors/src/selector-pipeline.ts diff --git a/src/core/touch-semantics.ts b/packages/selectors/src/touch-semantics.ts similarity index 100% rename from src/core/touch-semantics.ts rename to packages/selectors/src/touch-semantics.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc78d4f62c..4dba7545f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -490,6 +490,9 @@ importers: '@agent-device/ad-script': specifier: workspace:* version: link:../ad-script + '@agent-device/capture-kit': + specifier: workspace:* + version: link:../capture-kit '@agent-device/contracts': specifier: workspace:* version: link:../contracts diff --git a/scripts/layering/package-boundaries.test.ts b/scripts/layering/package-boundaries.test.ts index 762f664cd6..002ab36f3b 100644 --- a/scripts/layering/package-boundaries.test.ts +++ b/scripts/layering/package-boundaries.test.ts @@ -617,23 +617,41 @@ test('the real tree parses, declares, and passes R11', () => { ); const selectorsPackage = packages.find((pkg) => pkg.name === '@agent-device/selectors'); assert.ok(selectorsPackage, 'selectors package must exist'); - // Four subpaths, and each split is the point: `.` is the string-only façade + // Subpaths, and each split is the point: `.` is the string-only façade // every in-repo consumer uses, `./ast` is the published parser surface that // `agent-device/selectors` has shipped since before the engine moved into // this package, `./engine` is the resolve/list surface reserved for the // selector-pipeline owner (R19, #1656) — a route reaching it skips the // structural stages its policy row declares — and // `./parameterized-recorded-fill` is the recorded-fill parameterization the - // daemon used to own (#2340). A fifth subpath, or the AST leaking into `.`, - // fails here. + // daemon used to own (#2340). The per-file subpaths under `./interaction-*`, + // `./selector-pipeline*`, `./press-retarget`, `./touch-semantics` are the + // execution surface the core selector pipeline moved into this package — + // one subpath per module so consumers pull only the stage they run; the + // `-fixtures` entry is the test-fixture surface (host-kit's + // `./audio-probe-fixtures` precedent). Any other subpath, or the AST + // leaking into `.`, fails here. assert.deepEqual([...selectorsPackage.exportTargets.keys()].sort(), [ '@agent-device/selectors', + '@agent-device/selectors/absence-observation', + '@agent-device/selectors/absence-observation-errors', + '@agent-device/selectors/absence-observation-resolution', '@agent-device/selectors/ast', '@agent-device/selectors/engine', + '@agent-device/selectors/interaction-error', + '@agent-device/selectors/interaction-positionals', + '@agent-device/selectors/interaction-targeting', + '@agent-device/selectors/interaction-targeting-fixtures', + '@agent-device/selectors/interaction-touch-point', '@agent-device/selectors/parameterized-recorded-fill', + '@agent-device/selectors/press-retarget', + '@agent-device/selectors/selector-pipeline', + '@agent-device/selectors/selector-pipeline-policy', + '@agent-device/selectors/touch-semantics', ]); assert.deepEqual([...selectorsPackage.workspaceDependencies].sort(), [ '@agent-device/ad-script', + '@agent-device/capture-kit', '@agent-device/contracts', '@agent-device/kernel', ]); diff --git a/scripts/layering/selector-pipeline-ownership.test.ts b/scripts/layering/selector-pipeline-ownership.test.ts index 3d382f441b..f77e83c00d 100644 --- a/scripts/layering/selector-pipeline-ownership.test.ts +++ b/scripts/layering/selector-pipeline-ownership.test.ts @@ -2,6 +2,7 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; import { parseImports, type ResolvedImportEdge } from './model.ts'; import { + SELECTOR_ENGINE_FILE, SELECTOR_ENGINE_OWNER, SELECTOR_ENGINE_SPECIFIER, selectorPipelineOwnershipViolations, @@ -56,7 +57,7 @@ test('the owner holds the engine, and the root façade stays open to everyone', [ "import { buildSelectorChainForNode, formatSelectorFailure } from '@agent-device/selectors';", "import type { SelectorChainMatchList } from '@agent-device/selectors';", - "import { resolveSelectorPipeline } from '../../core/selector-pipeline.ts';", + "import { resolveSelectorPipeline } from '@agent-device/selectors/selector-pipeline';", ].join('\n'), ), [], @@ -68,6 +69,30 @@ test('the owner holds the engine, and the root façade stays open to everyone', ); }); +test('an in-package relative route to the engine file is refused, except for the owner', () => { + // The owner now lives beside the engine, so a same-package relative import + // is a second door the specifier scan cannot see. The resolved target is the + // enforcement key for that route. + const relativeRoute = (file: string): ResolvedImportEdge[] => [ + { + spec: './engine.ts', + dynamic: false, + typeOnly: false, + line: 1, + symbols: ['resolveSelectorChainWithPolicy'], + file, + target: SELECTOR_ENGINE_FILE, + fromZone: 'selectors', + toZone: 'selectors', + }, + ]; + const [message] = selectorPipelineOwnershipViolations( + relativeRoute('packages/selectors/src/internal/planted.ts'), + ).map((violation) => violation.message); + assert.ok(message?.includes(SELECTOR_ENGINE_OWNER), message); + assert.deepEqual(selectorPipelineOwnershipViolations(relativeRoute(SELECTOR_ENGINE_OWNER)), []); +}); + test('a missing engine door fails the gate instead of silencing it', () => { // The specifier is the whole enforcement, and `resolveImportEdges` drops an // edge that resolves to nothing: without this, retiring the subpath would diff --git a/scripts/layering/selector-pipeline-ownership.ts b/scripts/layering/selector-pipeline-ownership.ts index 1bf39705f8..ddf60e67a1 100644 --- a/scripts/layering/selector-pipeline-ownership.ts +++ b/scripts/layering/selector-pipeline-ownership.ts @@ -19,25 +19,31 @@ import type { LayeringViolation, ResolvedImportEdge } from './model.ts'; * * The structural stages of selector resolution — occlusion, off-screen, * hittable-ancestor promotion, the poll budget — are declared per caller in - * `src/core/selector-pipeline-policy.ts` and executed by - * `src/core/selector-pipeline.ts`. That only means something while the owner is - * the ONLY way in: a route that reaches the matching engine itself still gets a - * row's ambiguity contract while silently skipping every structural stage, - * which is how a declared cell turns back into an unverifiable claim (the - * failure #1649 caught in the first matrix and #1656's review caught in the - * second). + * `packages/selectors/src/selector-pipeline-policy.ts` and executed by + * `packages/selectors/src/selector-pipeline.ts`. That only means something + * while the owner is the ONLY way in: a route that reaches the matching engine + * itself still gets a row's ambiguity contract while silently skipping every + * structural stage, which is how a declared cell turns back into an + * unverifiable claim (the failure #1649 caught in the first matrix and + * #1656's review caught in the second). * * The engine therefore lives behind its own package subpath, and this rule * admits one importer. Enforcing on the SPECIFIER, over the resolved import * graph, is what makes the boundary hold in every import form: a namespace * import, a re-export, and a deferred `import()` are all the same edge, and - * none of them mentions the symbol a name-shaped check would look for. + * none of them mentions the symbol a name-shaped check would look for. Since + * the owner now lives in the same package as the engine, a relative import of + * the engine file is a second door — so the resolved TARGET is admitted as an + * equivalent edge, closing that in-package route. */ export const SELECTOR_ENGINE_SPECIFIER = '@agent-device/selectors/engine'; +/** The engine file: the target every admitted route must resolve to. */ +export const SELECTOR_ENGINE_FILE = 'packages/selectors/src/engine.ts'; + /** The pipeline owner: the one module that may hold the engine. */ -export const SELECTOR_ENGINE_OWNER = 'src/core/selector-pipeline.ts'; +export const SELECTOR_ENGINE_OWNER = 'packages/selectors/src/selector-pipeline.ts'; /** * `resolveImportEdges` DROPS an edge whose specifier resolves to nothing, so a @@ -65,7 +71,9 @@ export function selectorPipelineOwnershipViolations( } return edges .filter( - (edge) => edge.spec === SELECTOR_ENGINE_SPECIFIER && edge.file !== SELECTOR_ENGINE_OWNER, + (edge) => + edge.file !== SELECTOR_ENGINE_OWNER && + (edge.spec === SELECTOR_ENGINE_SPECIFIER || edge.target === SELECTOR_ENGINE_FILE), ) .map((edge) => ({ rule: 'R19 selector-pipeline-ownership', diff --git a/src/commands/capture/wait.ts b/src/commands/capture/wait.ts index 327d2473a5..d2adff067e 100644 --- a/src/commands/capture/wait.ts +++ b/src/commands/capture/wait.ts @@ -19,8 +19,8 @@ import { defineCommandFacet } from '../family/types.ts'; import { defineFieldCommandMetadata } from '../field-command-contract.ts'; import { messageOutput } from '../output-common.ts'; import { WAIT_KIND_VALUES } from './wait-command-contract.ts'; -import { absenceCaptureOptionRefusal } from '../../core/absence-observation.ts'; -import { absenceCaptureOptionError } from '../../core/absence-observation-errors.ts'; +import { absenceCaptureOptionRefusal } from '@agent-device/selectors/absence-observation'; +import { absenceCaptureOptionError } from '@agent-device/selectors/absence-observation-errors'; const WAIT_COMMAND_NAME = 'wait'; diff --git a/src/commands/interaction/interactions.ts b/src/commands/interaction/interactions.ts index 332bbfd6c3..47e4a1346d 100644 --- a/src/commands/interaction/interactions.ts +++ b/src/commands/interaction/interactions.ts @@ -14,7 +14,7 @@ import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; import { readFillTargetFromPositionals, readInteractionTargetFromPositionals, -} from '../../core/interaction-positionals.ts'; +} from '@agent-device/selectors/interaction-positionals'; import { commonInputFromFlags, direct, diff --git a/src/commands/interaction/runtime/__tests__/resolution-policy-parity.test.ts b/src/commands/interaction/runtime/__tests__/resolution-policy-parity.test.ts index e36fe62358..6760639f68 100644 --- a/src/commands/interaction/runtime/__tests__/resolution-policy-parity.test.ts +++ b/src/commands/interaction/runtime/__tests__/resolution-policy-parity.test.ts @@ -170,7 +170,7 @@ test('rect-requiring rows skip rectless nodes; read and wait rows accept them', * revision carried occlusion / off-screen / promotion / poll columns that no * code consumed, so changing them left both behavior and the suite green — * an unverifiable claim reading as truth. Those four stages belong to the - * structural table (`src/core/selector-pipeline-policy.ts`, #1656), where + * structural table (`packages/selectors/src/selector-pipeline-policy.ts`, #1656), where * runners consume them; this still fails if such a field appears HERE, where * the selectors package has nothing to enforce it with. */ diff --git a/src/commands/interaction/runtime/gestures.ts b/src/commands/interaction/runtime/gestures.ts index 653f95a026..da17ed61e3 100644 --- a/src/commands/interaction/runtime/gestures.ts +++ b/src/commands/interaction/runtime/gestures.ts @@ -16,7 +16,7 @@ import { resolveScrollExecutionOptions, } from '@agent-device/contracts/scroll-command'; import { AppError } from '@agent-device/kernel/errors'; -import { SELECTOR_PIPELINE_POLICIES } from '../../../core/selector-pipeline-policy.ts'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; import type { Point, Rect, SnapshotNode } from '@agent-device/kernel/snapshot'; import type { AgentDeviceRuntime, CommandContext } from '../../../runtime-contract.ts'; import { @@ -128,10 +128,7 @@ export type { HoverCommandResult }; export type GestureDirection = ScrollDirection; // The input vocabulary lives in contracts/scroll-gesture.ts beside the other scroll vocabularies, // so the public API can declare `ScrollOptions` without depending on this command runtime. -export { - SCROLL_INPUT_DIRECTIONS, - type ScrollInputDirection, -} from '@agent-device/contracts/scroll-gesture'; +export { type ScrollInputDirection } from '@agent-device/contracts/scroll-gesture'; export type ScrollTarget = | InteractionTarget diff --git a/src/commands/interaction/runtime/interactions.ts b/src/commands/interaction/runtime/interactions.ts index 3df732e103..a0ca7b795b 100644 --- a/src/commands/interaction/runtime/interactions.ts +++ b/src/commands/interaction/runtime/interactions.ts @@ -7,7 +7,7 @@ import type { ResolvedTarget, } from '@agent-device/contracts/interaction'; import { AppError } from '@agent-device/kernel/errors'; -import { SELECTOR_PIPELINE_POLICIES } from '../../../core/selector-pipeline-policy.ts'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; import type { Point } from '@agent-device/kernel/snapshot'; import type { AgentDeviceRuntime, CommandContext } from '../../../runtime-contract.ts'; import { isFillableType } from '@agent-device/contracts/snapshot'; diff --git a/src/commands/interaction/runtime/resolution.test.ts b/src/commands/interaction/runtime/resolution.test.ts index bf264a82bc..84f7b942da 100644 --- a/src/commands/interaction/runtime/resolution.test.ts +++ b/src/commands/interaction/runtime/resolution.test.ts @@ -10,7 +10,7 @@ import { import { resolveRecordedTarget } from '@agent-device/selectors'; import { makeSnapshotState } from '../../../__tests__/test-utils/snapshot-builders.ts'; import type { Point } from '@agent-device/kernel/snapshot'; -import { INTERACTION_ERROR_REASONS } from '../../../core/interaction-error.ts'; +import { INTERACTION_ERROR_REASONS } from '@agent-device/selectors/interaction-error'; import { clickRefE2, coveredByTabBarSnapshot, diff --git a/src/commands/interaction/runtime/resolution.ts b/src/commands/interaction/runtime/resolution.ts index 1beded2dd9..b52a0452d8 100644 --- a/src/commands/interaction/runtime/resolution.ts +++ b/src/commands/interaction/runtime/resolution.ts @@ -18,13 +18,13 @@ import { resolveSelectorPipeline, runNodePipelineStages, type SelectorPipelineHooks, -} from '../../../core/selector-pipeline.ts'; +} from '@agent-device/selectors/selector-pipeline'; import { SELECTOR_PIPELINE_POLICIES, type ActingPipelinePolicy, type SelectorPipelinePolicy, -} from '../../../core/selector-pipeline-policy.ts'; -import { resolvePressRecordingTarget } from '../../../core/press-retarget.ts'; +} from '@agent-device/selectors/selector-pipeline-policy'; +import { resolvePressRecordingTarget } from '@agent-device/selectors/press-retarget'; import { requireSnapshotSession } from './selector-read-shared.ts'; import { findNodeByLabel, resolveRefLabel } from '@agent-device/capture-kit/snapshot-node-lookup'; import { containsPoint } from '@agent-device/kernel/rect'; @@ -43,7 +43,7 @@ import type { ResolutionDisclosure, ResolvedInteractionTarget, } from '@agent-device/contracts/interaction'; -import { INTERACTION_ERROR_REASONS } from '../../../core/interaction-error.ts'; +import { INTERACTION_ERROR_REASONS } from '@agent-device/selectors/interaction-error'; import type { BackendActionResult, BackendCommandContext, @@ -51,7 +51,7 @@ import type { } from '../../../backend.ts'; import { now, toBackendContext } from '../../runtime-common.ts'; import { toBackendResult } from '../../runtime-types.ts'; -import { resolveInteractionTouchPoint } from '../../../core/interaction-touch-point.ts'; +import { resolveInteractionTouchPoint } from '@agent-device/selectors/interaction-touch-point'; import { localIdentitiesEqual, readNodeLocalIdentity, diff --git a/src/commands/interaction/runtime/selector-action-resolution.test.ts b/src/commands/interaction/runtime/selector-action-resolution.test.ts index 4eea260435..575767ebcf 100644 --- a/src/commands/interaction/runtime/selector-action-resolution.test.ts +++ b/src/commands/interaction/runtime/selector-action-resolution.test.ts @@ -4,8 +4,8 @@ import { makeSnapshotState } from '../../../__tests__/test-utils/snapshot-builde import { ELEMENT14_DISTINCT_SUBTREE_NODES, EQUIVALENT_WRAPPER_CHAIN_NODES, -} from '../../../core/interaction-targeting.fixtures.ts'; -import { SELECTOR_PIPELINE_POLICIES } from '../../../core/selector-pipeline-policy.ts'; +} from '@agent-device/selectors/interaction-targeting-fixtures'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; import { resolveActionSelector } from './selector-action-resolution.ts'; test('mutating selector collapses a wrapper chain that resolves to one actionable node', () => { diff --git a/src/commands/interaction/runtime/selector-action-resolution.ts b/src/commands/interaction/runtime/selector-action-resolution.ts index c9521b4d31..c0a1b5d9b4 100644 --- a/src/commands/interaction/runtime/selector-action-resolution.ts +++ b/src/commands/interaction/runtime/selector-action-resolution.ts @@ -2,9 +2,9 @@ import { AppError } from '@agent-device/kernel/errors'; import type { Platform, PublicPlatform } from '@agent-device/kernel/device'; import type { SnapshotNode } from '@agent-device/kernel/snapshot'; import type { SelectorResolution } from '@agent-device/selectors'; -import { classifyActionableTouchCandidates } from '../../../core/interaction-targeting.ts'; -import { listSelectorPipelineMatches } from '../../../core/selector-pipeline.ts'; -import type { ActingPipelinePolicy } from '../../../core/selector-pipeline-policy.ts'; +import { classifyActionableTouchCandidates } from '@agent-device/selectors/interaction-targeting'; +import { listSelectorPipelineMatches } from '@agent-device/selectors/selector-pipeline'; +import type { ActingPipelinePolicy } from '@agent-device/selectors/selector-pipeline-policy'; import { formatSnapshotLine } from '@agent-device/capture-kit/snapshot-lines'; const AMBIGUOUS_ACTION_CANDIDATE_LIMIT = 5; diff --git a/src/commands/interaction/runtime/selector-is.ts b/src/commands/interaction/runtime/selector-is.ts index 58fd29cfc1..332800c3df 100644 --- a/src/commands/interaction/runtime/selector-is.ts +++ b/src/commands/interaction/runtime/selector-is.ts @@ -7,13 +7,13 @@ import { selectorFailureHint, type IsPredicate, } from '@agent-device/selectors'; -import { resolveSelectorPipeline } from '../../../core/selector-pipeline.ts'; -import { SELECTOR_PIPELINE_POLICIES } from '../../../core/selector-pipeline-policy.ts'; +import { resolveSelectorPipeline } from '@agent-device/selectors/selector-pipeline'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; import type { SnapshotNode } from '@agent-device/kernel/snapshot'; import type { AgentDeviceRuntime, CommandContext } from '../../../runtime-contract.ts'; import { AppError, isRequestCanceledError } from '@agent-device/kernel/errors'; import type { SelectorTarget } from '@agent-device/contracts/interaction'; -import { INTERACTION_ERROR_REASONS } from '../../../core/interaction-error.ts'; +import { INTERACTION_ERROR_REASONS } from '@agent-device/selectors/interaction-error'; import type { RuntimeCommand } from '../../runtime-types.ts'; import { assertExpectedResolvedTarget, type ExpectedResolvedTarget } from './resolution.ts'; import { @@ -22,12 +22,12 @@ import { captureSelectorSnapshot, } from './selector-read-shared.ts'; import { deriveSelectorCapturePolicy } from './selector-capture-policy.ts'; -import { absenceCaptureOptionRefusal } from '../../../core/absence-observation.ts'; +import { absenceCaptureOptionRefusal } from '@agent-device/selectors/absence-observation'; import { absenceCaptureOptionError, absenceUnreadableError, -} from '../../../core/absence-observation-errors.ts'; -import { resolveAbsenceObservation } from '../../../core/absence-observation-resolution.ts'; +} from '@agent-device/selectors/absence-observation-errors'; +import { resolveAbsenceObservation } from '@agent-device/selectors/absence-observation-resolution'; export type IsCommandOptions = CommandContext & SelectorSnapshotOptions & { diff --git a/src/commands/interaction/runtime/selector-read.ts b/src/commands/interaction/runtime/selector-read.ts index 6f66df4a54..f69d8c956c 100644 --- a/src/commands/interaction/runtime/selector-read.ts +++ b/src/commands/interaction/runtime/selector-read.ts @@ -12,8 +12,8 @@ import { listSelectorPipelineMatches, resolveSelectorPipeline, type SelectorPipelineHooks, -} from '../../../core/selector-pipeline.ts'; -import { SELECTOR_PIPELINE_POLICIES } from '../../../core/selector-pipeline-policy.ts'; +} from '@agent-device/selectors/selector-pipeline'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; import type { SnapshotNode } from '@agent-device/kernel/snapshot'; import { isSparseSnapshotQualityVerdict } from '@agent-device/capture-kit/snapshot-quality-verdict'; import type { AgentDeviceRuntime, CommandContext } from '../../../runtime-contract.ts'; diff --git a/src/commands/interaction/runtime/wait-absent.ts b/src/commands/interaction/runtime/wait-absent.ts index c5ac59efff..65ec917126 100644 --- a/src/commands/interaction/runtime/wait-absent.ts +++ b/src/commands/interaction/runtime/wait-absent.ts @@ -4,14 +4,14 @@ import { AppError } from '@agent-device/kernel/errors'; import { absenceCaptureOptionRefusal, type AbsenceObservation, -} from '../../../core/absence-observation.ts'; +} from '@agent-device/selectors/absence-observation'; import { absenceCaptureOptionError, absenceObservationError, absenceUnreadableError, -} from '../../../core/absence-observation-errors.ts'; -import { SELECTOR_PIPELINE_POLICIES } from '../../../core/selector-pipeline-policy.ts'; -import { resolveAbsenceObservationState } from '../../../core/absence-observation-resolution.ts'; +} from '@agent-device/selectors/absence-observation-errors'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; +import { resolveAbsenceObservationState } from '@agent-device/selectors/absence-observation-resolution'; import { deriveSelectorCapturePolicy } from './selector-capture-policy.ts'; import type { SelectorWaitOperations, diff --git a/src/commands/interaction/runtime/wait-polling.test.ts b/src/commands/interaction/runtime/wait-polling.test.ts index 1721d7e027..d15d3912e1 100644 --- a/src/commands/interaction/runtime/wait-polling.test.ts +++ b/src/commands/interaction/runtime/wait-polling.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { test } from 'vitest'; import type { AgentDeviceRuntime } from '../../../runtime-contract.ts'; -import { SELECTOR_PIPELINE_POLICIES } from '../../../core/selector-pipeline-policy.ts'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; import { createWaitPolling } from './wait-polling.ts'; test('poll delay is bounded by the remaining wait budget', async () => { diff --git a/src/commands/interaction/runtime/wait-polling.ts b/src/commands/interaction/runtime/wait-polling.ts index accfc6dce3..f331a2cad8 100644 --- a/src/commands/interaction/runtime/wait-polling.ts +++ b/src/commands/interaction/runtime/wait-polling.ts @@ -1,11 +1,11 @@ import { AppError, type AppErrorDetails } from '@agent-device/kernel/errors'; import { WAIT_REASONS, type WaitReason } from '@agent-device/contracts/wait'; import { isUnreadableCaptureContentError } from '@agent-device/contracts/android-snapshot-quality'; -import { selectorPollBudget } from '../../../core/selector-pipeline.ts'; +import { selectorPollBudget } from '@agent-device/selectors/selector-pipeline'; import { SELECTOR_PIPELINE_POLICIES, type SelectorPipelinePolicy, -} from '../../../core/selector-pipeline-policy.ts'; +} from '@agent-device/selectors/selector-pipeline-policy'; import { runWithinWaitDeadline } from './wait-deadline.ts'; /** diff --git a/src/commands/interaction/runtime/wait-selector.ts b/src/commands/interaction/runtime/wait-selector.ts index 5c07f4d6ac..bfb84624e4 100644 --- a/src/commands/interaction/runtime/wait-selector.ts +++ b/src/commands/interaction/runtime/wait-selector.ts @@ -16,8 +16,8 @@ import type { SelectorChainMatchList } from '@agent-device/selectors'; import { resolveSelectorPipeline, type SelectorPipelineOutcome, -} from '../../../core/selector-pipeline.ts'; -import { SELECTOR_PIPELINE_POLICIES } from '../../../core/selector-pipeline-policy.ts'; +} from '@agent-device/selectors/selector-pipeline'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; import { deriveSelectorCapturePolicy } from './selector-capture-policy.ts'; import type { SelectorWaitOperations, diff --git a/src/commands/interaction/runtime/wait-text.ts b/src/commands/interaction/runtime/wait-text.ts index 3137307001..70732cff07 100644 --- a/src/commands/interaction/runtime/wait-text.ts +++ b/src/commands/interaction/runtime/wait-text.ts @@ -6,7 +6,7 @@ import type { WaitCommandResult, } from './selector-wait.ts'; import { findNodeByLabel } from './selector-read-utils.ts'; -import { SELECTOR_PIPELINE_POLICIES } from '../../../core/selector-pipeline-policy.ts'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; import { createWaitPolling, type WaitPollDeadline, waitTimeoutError } from './wait-polling.ts'; export async function waitForText( diff --git a/src/core/__tests__/snapshot-state.test.ts b/src/core/__tests__/snapshot-state.test.ts index b358273d26..b408423e05 100644 --- a/src/core/__tests__/snapshot-state.test.ts +++ b/src/core/__tests__/snapshot-state.test.ts @@ -1,6 +1,6 @@ import { expect, test } from 'vitest'; import { buildSnapshotState } from '@agent-device/capture-kit/snapshot-state'; -import { resolveActionableTouchResolution } from '../interaction-targeting.ts'; +import { resolveActionableTouchResolution } from '@agent-device/selectors/interaction-targeting'; import { createSnapshotVisibility } from '@agent-device/contracts/snapshot'; import { attachSnapshotOcclusionContextEvidence } from '@agent-device/contracts/capture'; import { diff --git a/src/core/interactors/web.ts b/src/core/interactors/web.ts index e62df9d8cc..2f032b310d 100644 --- a/src/core/interactors/web.ts +++ b/src/core/interactors/web.ts @@ -1,6 +1,6 @@ import type { Interactor } from '@agent-device/contracts/interactor-types'; import { AppError } from '@agent-device/kernel/errors'; -import { stripAtPrefix } from '../interaction-positionals.ts'; +import { stripAtPrefix } from '@agent-device/selectors/interaction-positionals'; import { withDiagnosticTimer } from '@agent-device/host-kit/diagnostics'; import { resolveWebProvider, type WebProvider } from '@agent-device/platform-web'; import { createUnsupportedInteractor } from './unsupported-interactor.ts'; diff --git a/src/core/press-retarget.test.ts b/src/core/press-retarget.test.ts index 0ce8e9a354..d845070245 100644 --- a/src/core/press-retarget.test.ts +++ b/src/core/press-retarget.test.ts @@ -5,7 +5,7 @@ import { buildNodes } from '../__tests__/test-utils/snapshot-builders.ts'; import { computeTargetEvidence } from '../daemon/session-target-evidence.ts'; import { buildSelectorChainForNode, resolveRecordedTarget } from '@agent-device/selectors'; import { readNodeLocalIdentity } from '@agent-device/ad-script'; -import { resolvePressRecordingTarget } from './press-retarget.ts'; +import { resolvePressRecordingTarget } from '@agent-device/selectors/press-retarget'; function findByLabel(nodes: SnapshotNode[], label: string): SnapshotNode { const found = nodes.find((node) => node.label === label); @@ -397,7 +397,7 @@ test('resolvePressRecordingTarget P2a contrast: a container with a UNIQUE id kee // --------------------------------------------------------------------------- // P2b (#1280 re-review): the guard is built from the canonical interactive -// classification (`isSemanticTouchTarget`, core/touch-semantics.ts), +// classification (`isSemanticTouchTarget`, @agent-device/selectors/touch-semantics), // not a parallel list — roles the old private fragment list missed must // block. And a geometry condition: the selected descendant's rect center // must lie INSIDE the container's rect, else the replay tap point is not diff --git a/src/daemon/adapters/maestro/__tests__/daemon-runtime-port-selector-fallback.test.ts b/src/daemon/adapters/maestro/__tests__/daemon-runtime-port-selector-fallback.test.ts index 105b9453e4..68367b4a9e 100644 --- a/src/daemon/adapters/maestro/__tests__/daemon-runtime-port-selector-fallback.test.ts +++ b/src/daemon/adapters/maestro/__tests__/daemon-runtime-port-selector-fallback.test.ts @@ -1,4 +1,4 @@ -import { INTERACTION_ERROR_REASONS } from '../../../../core/interaction-error.ts'; +import { INTERACTION_ERROR_REASONS } from '@agent-device/selectors/interaction-error'; import type { DaemonError } from '@agent-device/kernel/errors'; import { expect, test } from 'vitest'; import type { DaemonRequest } from '../../../daemon-request.ts'; diff --git a/src/daemon/adapters/maestro/daemon-runtime-tap.ts b/src/daemon/adapters/maestro/daemon-runtime-tap.ts index 51a635f03d..f622529791 100644 --- a/src/daemon/adapters/maestro/daemon-runtime-tap.ts +++ b/src/daemon/adapters/maestro/daemon-runtime-tap.ts @@ -1,5 +1,5 @@ import { AppError, asAppError } from '@agent-device/kernel/errors'; -import { INTERACTION_ERROR_REASONS } from '../../../core/interaction-error.ts'; +import { INTERACTION_ERROR_REASONS } from '@agent-device/selectors/interaction-error'; import type { Rect } from '@agent-device/kernel/snapshot'; import { MAESTRO_RUNTIME_ADAPTER_POLICY, diff --git a/src/daemon/interaction/internal/find-match-ranking.ts b/src/daemon/interaction/internal/find-match-ranking.ts index 3ffe0b279e..649a824742 100644 --- a/src/daemon/interaction/internal/find-match-ranking.ts +++ b/src/daemon/interaction/internal/find-match-ranking.ts @@ -3,7 +3,7 @@ import { createActionableTouchResolver, isRootInteractionContainer, resolveActionableTouchResolution, -} from '../../../core/interaction-targeting.ts'; +} from '@agent-device/selectors/interaction-targeting'; /** * How `find` orders the candidates its locator matched, before the ambiguity diff --git a/src/daemon/interaction/internal/find-match-resolution.ts b/src/daemon/interaction/internal/find-match-resolution.ts index 487df3b697..5e37d3a800 100644 --- a/src/daemon/interaction/internal/find-match-resolution.ts +++ b/src/daemon/interaction/internal/find-match-resolution.ts @@ -3,10 +3,10 @@ import { type FindLocator, type SelectorResolutionPolicy, } from '@agent-device/selectors'; -import { listSelectorPipelineMatches } from '../../../core/selector-pipeline.ts'; -import { SELECTOR_PIPELINE_POLICIES } from '../../../core/selector-pipeline-policy.ts'; +import { listSelectorPipelineMatches } from '@agent-device/selectors/selector-pipeline'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; import type { SnapshotState } from '@agent-device/kernel/snapshot'; -import { isRootInteractionContainer } from '../../../core/interaction-targeting.ts'; +import { isRootInteractionContainer } from '@agent-device/selectors/interaction-targeting'; import { preferOnscreenMatches } from './find-match-ranking.ts'; import type { DaemonRequest, DaemonResponse } from '../../daemon-request.ts'; import type { SessionState } from '../../session-state.ts'; diff --git a/src/daemon/interaction/internal/find.ts b/src/daemon/interaction/internal/find.ts index 679f70e811..00b4a67a4b 100644 --- a/src/daemon/interaction/internal/find.ts +++ b/src/daemon/interaction/internal/find.ts @@ -5,8 +5,8 @@ import { parseFindSelectorExpression, type FindLocator, } from '@agent-device/selectors'; -import { runNodePipelineStages } from '../../../core/selector-pipeline.ts'; -import { SELECTOR_PIPELINE_POLICIES } from '../../../core/selector-pipeline-policy.ts'; +import { runNodePipelineStages } from '@agent-device/selectors/selector-pipeline'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; import { centerOfRect, type SnapshotState } from '@agent-device/kernel/snapshot'; import { expireRefFrame } from '../../ref-frame.ts'; import type { DaemonInvokeFn, DaemonRequest, DaemonResponse } from '../../daemon-request.ts'; diff --git a/src/daemon/interaction/internal/interaction-touch-targets.ts b/src/daemon/interaction/internal/interaction-touch-targets.ts index 8845f55bd8..dbcba014e3 100644 --- a/src/daemon/interaction/internal/interaction-touch-targets.ts +++ b/src/daemon/interaction/internal/interaction-touch-targets.ts @@ -8,7 +8,7 @@ import { readFillTargetFromPositionals, stripAtPrefix, type DecodedFillTarget, -} from '../../../core/interaction-positionals.ts'; +} from '@agent-device/selectors/interaction-positionals'; import type { DaemonResponse } from '../../daemon-request.ts'; import { parseCoordinateTarget } from './interaction-targeting.ts'; import { errorResponse } from '../../response.ts'; diff --git a/src/daemon/replay/internal/__tests__/session-replay-target-classification.test.ts b/src/daemon/replay/internal/__tests__/session-replay-target-classification.test.ts index abebf229d9..06af33d7ff 100644 --- a/src/daemon/replay/internal/__tests__/session-replay-target-classification.test.ts +++ b/src/daemon/replay/internal/__tests__/session-replay-target-classification.test.ts @@ -4,7 +4,7 @@ import type { RawSnapshotNode, SnapshotNode } from '@agent-device/kernel/snapsho import type { TargetAnnotationV1 } from '@agent-device/contracts/replay'; import { computeTargetEvidence } from '../../../session-target-evidence.ts'; import { buildSelectorChainForNode, resolveRecordedTarget } from '@agent-device/selectors'; -import { resolvePressRecordingTarget } from '../../../../core/press-retarget.ts'; +import { resolvePressRecordingTarget } from '@agent-device/selectors/press-retarget'; import { classifyReplayTarget } from '../session-replay-target-classification.ts'; import { bottomTabsRealCaptureFixture, diff --git a/src/daemon/screenshot-crop.ts b/src/daemon/screenshot-crop.ts index 655052da67..60f1faf87a 100644 --- a/src/daemon/screenshot-crop.ts +++ b/src/daemon/screenshot-crop.ts @@ -13,8 +13,8 @@ import { resolveSnapshotBounds, } from '@agent-device/capture-kit/snapshot-rect-projection'; import { buildSnapshotState } from '@agent-device/capture-kit/snapshot-state'; -import { SELECTOR_PIPELINE_POLICIES } from '../core/selector-pipeline-policy.ts'; -import { resolveSelectorPipeline } from '../core/selector-pipeline.ts'; +import { SELECTOR_PIPELINE_POLICIES } from '@agent-device/selectors/selector-pipeline-policy'; +import { resolveSelectorPipeline } from '@agent-device/selectors/selector-pipeline'; import type { DaemonCommandContext } from './context.ts'; import { captureSnapshotData } from './snapshot-capture.ts'; import { runtimeExecutionFromContext } from './snapshot-runtime-capture-input.ts'; diff --git a/src/daemon/selector-capture-runtime.ts b/src/daemon/selector-capture-runtime.ts index 28eeb22e31..0846b1065f 100644 --- a/src/daemon/selector-capture-runtime.ts +++ b/src/daemon/selector-capture-runtime.ts @@ -15,7 +15,7 @@ import { getActiveAndroidSnapshotFreshness } from './session-snapshot-freshness. import { isPostGestureStabilizationPending } from './deferred-interaction-outcome.ts'; import type { BoundSelectorCapture } from './selector-capture-binding.ts'; import { buildRuntimeCaptureInput } from './snapshot-runtime-capture-input.ts'; -import { isLegacySparseIosInteractiveSnapshot } from '../core/absence-observation.ts'; +import { isLegacySparseIosInteractiveSnapshot } from '@agent-device/selectors/absence-observation'; const SELECTOR_CAPTURE_CACHE_TTL_MS = 750; diff --git a/src/daemon/selector-runtime.ts b/src/daemon/selector-runtime.ts index 2e5c207f79..9505b6365a 100644 --- a/src/daemon/selector-runtime.ts +++ b/src/daemon/selector-runtime.ts @@ -1,7 +1,7 @@ import { asAppError } from '@agent-device/kernel/errors'; import type { SnapshotNode } from '@agent-device/kernel/snapshot'; -import { absenceCaptureOptionError } from '../core/absence-observation-errors.ts'; -import { absenceCaptureOptionRefusal } from '../core/absence-observation.ts'; +import { absenceCaptureOptionError } from '@agent-device/selectors/absence-observation-errors'; +import { absenceCaptureOptionRefusal } from '@agent-device/selectors/absence-observation'; import type { DaemonRequest, DaemonResponse } from './daemon-request.ts'; import { errorResponse } from './response.ts'; import { markSessionPartialRefsIssued, resolveRefStalenessWarning } from './session-snapshot.ts'; diff --git a/src/daemon/wait-runtime.ts b/src/daemon/wait-runtime.ts index bb1e6e37cc..9c1e9fef4f 100644 --- a/src/daemon/wait-runtime.ts +++ b/src/daemon/wait-runtime.ts @@ -4,8 +4,8 @@ import { AppError } from '@agent-device/kernel/errors'; import { checkWaitText } from '@agent-device/selectors'; import { parseWaitPositionals } from '@agent-device/command-registry/wait-positionals'; import type { WaitParsed } from '@agent-device/command-registry/wait-positionals'; -import { absenceCaptureOptionError } from '../core/absence-observation-errors.ts'; -import { absenceCaptureOptionRefusal } from '../core/absence-observation.ts'; +import { absenceCaptureOptionError } from '@agent-device/selectors/absence-observation-errors'; +import { absenceCaptureOptionRefusal } from '@agent-device/selectors/absence-observation'; import { parseVersionedRefPositional } from './ref-positionals.ts'; import { errorResponse } from './response.ts'; import { resolveRefStalenessWarning } from './session-snapshot.ts'; diff --git a/test/integration/ios-simulator-e2e/snapshot-backend-conformance.ts b/test/integration/ios-simulator-e2e/snapshot-backend-conformance.ts index ef2a9c2d21..5dffe82a02 100644 --- a/test/integration/ios-simulator-e2e/snapshot-backend-conformance.ts +++ b/test/integration/ios-simulator-e2e/snapshot-backend-conformance.ts @@ -12,7 +12,7 @@ import { type SnapshotPreferredBackend, } from '@agent-device/kernel/snapshot'; import { SNAPSHOT_BACKEND_CAPABILITIES } from '@agent-device/capture-kit/snapshot-quality-backend-capabilities'; -import { isSemanticTouchTarget } from '../../../src/core/touch-semantics.ts'; +import { isSemanticTouchTarget } from '@agent-device/selectors/touch-semantics'; export type SnapshotBackendConformanceFixture = { screen: string;