Skip to content

Commit 365f8dc

Browse files
committed
refactor: move interaction handlers behind daemon facade
1 parent b643d0f commit 365f8dc

98 files changed

Lines changed: 1181 additions & 984 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.fallowrc.json

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,16 +182,29 @@
182182
},
183183
{
184184
"comment": "Daemon route handlers are reached only through the dynamic `import()` table in request-handler-chain.ts, which --production analysis cannot follow to a consumer.",
185-
"file": "src/daemon/handlers/{human-control,lease,session,snapshot,react-native,record-trace,find,interaction}.ts",
185+
"file": "src/daemon/handlers/{human-control,lease,session,snapshot,react-native,record-trace}.ts",
186186
"exports": [
187187
"handleHumanControlCommand",
188188
"handleLeaseCommands",
189189
"handleSessionCommands",
190190
"handleSnapshotCommands",
191191
"handleReactNativeCommands",
192-
"handleRecordTraceCommands",
193-
"handleFindCommands",
194-
"handleInteractionCommands"
192+
"handleRecordTraceCommands"
193+
]
194+
},
195+
{
196+
"comment": "The interaction façade is a named external seam: these helpers are part of the declared public surface, while Fallow cannot infer their consumers from the façade boundary.",
197+
"file": "src/daemon/interaction/index.ts",
198+
"exports": [
199+
"RefSnapshotFlagGuardResponse",
200+
"readTextForNode",
201+
"assertRecordedFillParameterization",
202+
"publishInteractionAmbiguityCandidates",
203+
"assertRefMutationAdmitted",
204+
"refMutationAdmissionResponse",
205+
"refSnapshotFlagGuardResponse",
206+
"handleInteractionCommands",
207+
"handleFindCommands"
195208
]
196209
},
197210
{

fallow-baselines/health.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,22 +138,22 @@
138138
"count": 1
139139
}
140140
},
141-
"src/daemon/handlers/__tests__/interaction-touch.test.ts": {
141+
"src/daemon/interaction/internal/__tests__/interaction-touch.test.ts": {
142142
"crap_moderate": {
143143
"count": 2
144144
}
145145
},
146-
"src/daemon/handlers/__tests__/interaction-touch-press.test.ts": {
146+
"src/daemon/interaction/internal/__tests__/interaction-touch-press.test.ts": {
147147
"crap_moderate": {
148148
"count": 3
149149
}
150150
},
151-
"src/daemon/handlers/__tests__/interaction-touch-press-admission.test.ts": {
151+
"src/daemon/interaction/internal/__tests__/interaction-touch-press-admission.test.ts": {
152152
"crap_moderate": {
153153
"count": 2
154154
}
155155
},
156-
"src/daemon/handlers/__tests__/interaction-touch-response.test.ts": {
156+
"src/daemon/interaction/internal/__tests__/interaction-touch-response.test.ts": {
157157
"crap_moderate": {
158158
"count": 1
159159
}
@@ -207,15 +207,15 @@
207207
"count": 1
208208
}
209209
},
210-
"src/daemon/handlers/find.ts": {
210+
"src/daemon/interaction/internal/find.ts": {
211211
"complexity_moderate": {
212212
"count": 1
213213
},
214214
"crap_moderate": {
215215
"count": 1
216216
}
217217
},
218-
"src/daemon/handlers/interaction-touch-reference-frame.ts": {
218+
"src/daemon/interaction/internal/interaction-touch-reference-frame.ts": {
219219
"crap_moderate": {
220220
"count": 1
221221
}

packages/contracts/src/interaction-guarantees.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ const PARENT_OWNED_TOUCH_POINT_GAP_ISSUE = 'https://github.com/callstack/agent-d
143143
// (interaction-response-construction-guard.test.ts) keeps new branches on it.
144144
const SHARED_RESPONSE_CONSTRUCTION: GuaranteeEnforcement = {
145145
kind: 'runtime',
146-
via: 'src/daemon/handlers/interaction-touch-response.ts#buildInteractionResponseData',
146+
via: 'src/daemon/interaction/internal/interaction-touch-response.ts#buildInteractionResponseData',
147147
};
148148

149149
// The two runtime tree paths (selector and ref resolution) run the SAME shared
@@ -183,7 +183,7 @@ const RUNTIME_TREE_SHARED_GUARANTEES = {
183183
responseConstruction: SHARED_RESPONSE_CONSTRUCTION,
184184
responseIdentity: {
185185
kind: 'runtime',
186-
via: 'src/daemon/handlers/interaction-touch-targets.ts#interactionResultExtra',
186+
via: 'src/daemon/interaction/internal/interaction-touch-targets.ts#interactionResultExtra',
187187
},
188188
verifyEvidence: {
189189
kind: 'runtime',
@@ -271,7 +271,7 @@ export const INTERACTION_DISPATCH_PATHS: Record<InteractionPathId, InteractionPa
271271
},
272272
responseConstruction: {
273273
kind: 'runtime',
274-
via: 'src/daemon/handlers/interaction-gesture-response.ts#gestureResponseData',
274+
via: 'src/daemon/interaction/internal/interaction-gesture-response.ts#gestureResponseData',
275275
},
276276
responseIdentity: {
277277
kind: 'runtime',
@@ -340,7 +340,7 @@ export const INTERACTION_DISPATCH_PATHS: Record<InteractionPathId, InteractionPa
340340
responseConstruction: SHARED_RESPONSE_CONSTRUCTION,
341341
responseIdentity: {
342342
kind: 'runtime',
343-
via: 'src/daemon/handlers/interaction-touch-targets.ts#interactionResultExtra',
343+
via: 'src/daemon/interaction/internal/interaction-touch-targets.ts#interactionResultExtra',
344344
},
345345
verifyEvidence: {
346346
kind: 'delegated',
@@ -464,7 +464,7 @@ export const INTERACTION_DISPATCH_PATHS: Record<InteractionPathId, InteractionPa
464464
},
465465
resolutionDisclosure: {
466466
kind: 'runtime',
467-
via: 'src/daemon/handlers/interaction-touch-response.ts#buildInteractionResponseData',
467+
via: 'src/daemon/interaction/internal/interaction-touch-response.ts#buildInteractionResponseData',
468468
},
469469
},
470470
},

scripts/__tests__/help-conformance-sample-producers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { interactionCliOutputFormatters } from '../../src/commands/interaction/o
1919
import { snapshotCliOutput } from '../../src/commands/capture/output.ts';
2020
import { openCliOutput } from '../../src/commands/management/output.ts';
2121
import { NEVER_SETTLED_HINT } from '../../src/commands/interaction/runtime/settle.ts';
22-
import { buildAmbiguousMatchError } from '../../src/daemon/handlers/find-match-resolution.ts';
22+
import { buildAmbiguousMatchError } from '../../src/daemon/interaction/internal/find-match-resolution.ts';
2323
import { refMutationAdmissionResponse } from '../../src/daemon/interaction/index.ts';
2424
import { buildDeviceInUseBySessionError } from '../../src/daemon/session-recovery-hints.ts';
2525
import { buildDeviceClaimConflictError } from '../../src/daemon/device-claim-conflict.ts';

scripts/__tests__/test-file-size-ratchet.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const PINNED_TEST_FILE_LINES: Readonly<Record<string, number>> = Object.freeze({
4747
'packages/platform-apple/src/runner/__tests__/runner-command-retry.test.ts': 1280,
4848
'src/__tests__/cli-client-commands.test.ts': 1304,
4949
'src/__tests__/cli-config.test.ts': 1282,
50-
'src/daemon/handlers/__tests__/find.test.ts': 1199,
50+
'src/daemon/interaction/internal/__tests__/find.test.ts': 1198,
5151
'packages/platform-apple/src/core/__tests__/perf.test.ts': 1222,
5252
'src/mcp/__tests__/command-tools.test.ts': 1216,
5353
'src/daemon/replay/internal/__tests__/session-replay-divergence.test.ts': 1100,

scripts/depgraph/affected.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,16 @@ test('commandsReaching follows the dynamic import a route uses to load its handl
8989
const edges = edgesOf({
9090
'src/daemon/handlers/session.ts':
9191
"import { helper } from '../../utils/helper.ts';\nexport const h = helper;",
92-
'src/daemon/handlers/find.ts': 'export const f = 1;',
92+
'src/daemon/interaction/index.ts': 'export const f = 1;',
9393
'src/utils/helper.ts': 'export const helper = 1;',
9494
'src/daemon/chain.ts': [
9595
"export const routes = { session: () => import('./handlers/session.ts'),",
96-
" find: () => import('./handlers/find.ts') };",
96+
" find: () => import('./interaction/index.ts') };",
9797
].join('\n'),
9898
});
9999
const chains = [
100100
{ command: 'open', route: 'session', entry: 'src/daemon/handlers/session.ts' },
101-
{ command: 'find', route: 'find', entry: 'src/daemon/handlers/find.ts' },
101+
{ command: 'find', route: 'find', entry: 'src/daemon/interaction/index.ts' },
102102
];
103103

104104
assert.deepEqual(
@@ -107,7 +107,9 @@ test('commandsReaching follows the dynamic import a route uses to load its handl
107107
);
108108
// The entry module itself counts as part of its own chain.
109109
assert.deepEqual(
110-
commandsReaching('src/daemon/handlers/find.ts', chains, edges).map((chain) => chain.command),
110+
commandsReaching('src/daemon/interaction/index.ts', chains, edges).map(
111+
(chain) => chain.command,
112+
),
111113
['find'],
112114
);
113115
});

scripts/help-conformance-sample-outputs.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export const STALE_REF_SAMPLE = {
110110
Hint: Ref @e12 was minted from snapshot s5 but the session's ref frame is now s7 — re-run snapshot -i.`,
111111
};
112112

113-
// AMBIGUOUS_MATCH from buildAmbiguousMatchError (src/daemon/handlers/find.ts)
113+
// AMBIGUOUS_MATCH from buildAmbiguousMatchError (src/daemon/interaction/internal/find.ts)
114114
// — the parity test drives that exact producer. The by-design rejection
115115
// instead of silent disambiguation: #1597 made the candidate refs (ref, role,
116116
// label/identifier — the same compact rendering as snapshot -i) print

scripts/layering/architecture-ownership.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,16 @@ const DAEMON_INTERACTION_FACADE = {
4949
exports: [
5050
'CaptureSnapshotForSession',
5151
'ContextFromFlags',
52+
'FindRouteInput',
5253
'InteractionRouteInput',
5354
'RefSnapshotFlagGuardResponse',
5455
'assertRecordedFillParameterization',
5556
'assertRefMutationAdmitted',
5657
'captureSnapshotForSession',
5758
'createInteractionRuntime',
5859
'finalizeTouchInteraction',
60+
'handleFindCommands',
61+
'handleInteractionCommands',
5962
'publishInteractionAmbiguityCandidates',
6063
'readSettleRequest',
6164
'readTextForNode',
@@ -65,6 +68,34 @@ const DAEMON_INTERACTION_FACADE = {
6568
],
6669
} as const;
6770

71+
export const INTERACTION_RETIRED_HANDLER_PATHS = [
72+
'src/daemon/handlers/find.ts',
73+
'src/daemon/handlers/find-match-ranking.ts',
74+
'src/daemon/handlers/find-match-resolution.ts',
75+
'src/daemon/handlers/find-target-capture.ts',
76+
'src/daemon/handlers/interaction.ts',
77+
'src/daemon/handlers/interaction-android-escape.ts',
78+
'src/daemon/handlers/interaction-gesture.ts',
79+
'src/daemon/handlers/interaction-gesture-response.ts',
80+
'src/daemon/handlers/interaction-ios-tap-outcome.ts',
81+
'src/daemon/handlers/interaction-targeting.ts',
82+
'src/daemon/handlers/interaction-touch.ts',
83+
'src/daemon/handlers/interaction-touch-android-freshness.ts',
84+
'src/daemon/handlers/interaction-touch-android-readiness.ts',
85+
'src/daemon/handlers/interaction-touch-direct-ios-eligibility.ts',
86+
'src/daemon/handlers/interaction-touch-direct-ios.ts',
87+
'src/daemon/handlers/interaction-touch-fill.ts',
88+
'src/daemon/handlers/interaction-touch-payload.ts',
89+
'src/daemon/handlers/interaction-touch-policy.ts',
90+
'src/daemon/handlers/interaction-touch-prepare.ts',
91+
'src/daemon/handlers/interaction-touch-press-admission.ts',
92+
'src/daemon/handlers/interaction-touch-press.ts',
93+
'src/daemon/handlers/interaction-touch-reference-frame.ts',
94+
'src/daemon/handlers/interaction-touch-response.ts',
95+
'src/daemon/handlers/interaction-touch-runtime.ts',
96+
'src/daemon/handlers/interaction-touch-targets.ts',
97+
] as const;
98+
6899
export const SESSION_OBSERVABILITY_RETIRED_HANDLER_PATHS = [
69100
'src/daemon/handlers/session-observability.ts',
70101
'src/daemon/handlers/session-perf-runtime.ts',

scripts/layering/check.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import {
7676
} from './model.ts';
7777
import {
7878
checkDaemonModularityRatchets,
79+
checkRetiredInteractionPaths,
7980
checkRetiredSessionLifecyclePaths,
8081
checkRetiredSessionObservabilityPaths,
8182
daemonModularitySummary,
@@ -587,6 +588,7 @@ export const LAYERING_RULES: Readonly<Record<LayeringRuleId, LayeringRule>> = {
587588
'session-state-ownership': (context) => checkSessionStateOwnership(context.sources),
588589
'daemon-modularity-ratchets': (context) => [
589590
...checkDaemonModularityRatchets(context.edges, context.typeCycleMembers),
591+
...checkRetiredInteractionPaths(context.sourceFiles),
590592
...checkRetiredSessionLifecyclePaths(context.sourceFiles),
591593
...checkRetiredSessionObservabilityPaths(context.sourceFiles),
592594
],

scripts/layering/daemon-modularity.test.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
22
import { test } from 'node:test';
33
import {
44
checkDaemonModularityRatchets,
5+
checkRetiredInteractionPaths,
56
checkRetiredSessionLifecyclePaths,
67
checkRetiredSessionObservabilityPaths,
78
DAEMON_MODULARITY_BASELINE,
@@ -317,10 +318,18 @@ test('interaction rejects handler crossings and deep imports around its facade',
317318
'src/daemon/handlers/interaction.ts',
318319
"import { refSnapshotFlagGuardResponse } from '../interaction/internal/interaction-flags.ts';\nexport function handleInteractionCommands() {}",
319320
],
321+
[
322+
'src/daemon/handlers/find.ts',
323+
"import { preferOnscreenMatches } from '../interaction/internal/find-match-ranking.ts';\nexport function handleFindCommands() {}",
324+
],
320325
[
321326
'src/daemon/interaction/internal/interaction-runtime.ts',
322327
"import { handleInteractionCommands } from '../../handlers/interaction.ts';\nexport function createInteractionRuntime() {}",
323328
],
329+
[
330+
'src/daemon/interaction/internal/find.ts',
331+
"import { handleFindCommands } from '../../handlers/find.ts';\nexport function find() {}",
332+
],
324333
[
325334
'src/daemon/generic-settle.ts',
326335
"import { createInteractionRuntime } from './interaction/internal/interaction-runtime.ts';",
@@ -337,6 +346,10 @@ test('interaction rejects handler crossings and deep imports around its facade',
337346
'src/daemon/interaction/internal/interaction-flags.ts',
338347
'export function refSnapshotFlagGuardResponse() {}',
339348
],
349+
[
350+
'src/daemon/interaction/internal/find-match-ranking.ts',
351+
'export function preferOnscreenMatches() {}',
352+
],
340353
[
341354
'src/daemon/interaction/internal/interaction-read.ts',
342355
'export function readTextForNode() {}',
@@ -348,7 +361,7 @@ test('interaction rejects handler crossings and deep imports around its facade',
348361
[...baselineEdges(), ...edges],
349362
baselineTypeCycleMembers(),
350363
);
351-
assert.equal(violations.length, 5);
364+
assert.equal(violations.length, 7);
352365
assert.ok(
353366
violations.some(({ message }) =>
354367
message.includes(
@@ -367,7 +380,7 @@ test('interaction rejects handler crossings and deep imports around its facade',
367380
violations.filter(({ message }) =>
368381
message.includes("must not import daemon-interaction's internal tree"),
369382
).length,
370-
4,
383+
5,
371384
);
372385
});
373386

@@ -491,6 +504,26 @@ test('session observability rejects restored handler paths', () => {
491504
);
492505
});
493506

507+
test('interaction rejects restored handler paths', () => {
508+
const restoredPaths = [
509+
'src/daemon/handlers/find.ts',
510+
'src/daemon/handlers/interaction-touch-direct-ios-regressed.ts',
511+
'src/daemon/handlers/interaction-common-regressed.ts',
512+
'src/daemon/interaction/internal/find.ts',
513+
] as const;
514+
const violations = checkRetiredInteractionPaths(restoredPaths);
515+
516+
assert.deepEqual(
517+
violations.map(({ file, message }) => ({ file, message })),
518+
restoredPaths.slice(0, 3).map((file) => ({
519+
file,
520+
message:
521+
`retired interaction path was restored: ${file}. ` +
522+
'Keep the neutral seam at its daemon owner instead of rebuilding a handler grab-bag.',
523+
})),
524+
);
525+
});
526+
494527
test('R9 records zone ceilings and keeps engine files outside the largest component', () => {
495528
// One commands file and one engine file traded for two provider-webdriver ones, so the
496529
// total stays at the baseline and only the per-zone claims are on trial.

0 commit comments

Comments
 (0)