Skip to content

Commit 96bc7b1

Browse files
authored
feat: build capability matrix from descriptors, delete the hand table — Phase 1 step 3 (#908)
Build BASE_COMMAND_CAPABILITY_MATRIX from deriveCapabilityMatrix(commandDescriptors) and delete the hand-authored literal (ADR-0008, Phase 1 step 3). Behaviorless: #906 proved the derived matrix is byte-equal to the literal, including the supports/unsupportedHint closures across the sample-device matrix. Delete the now-dead local predicate helpers and capability bundles that only the literal referenced (isNotMacOs, isMacOsOrAppleSimulator, isIosMobileSimulator, synthesisGestureUnsupportedHint, ALL_DEVICE_COMMAND_CAPABILITY, APP_* bundles, LINUX_DEVICE, LINUX_NONE); their copies now live in registry.ts. WEB_* command sets and addWebCommandCapabilities are kept. The registry/derive/types modules only type-import CommandCapability from capabilities.ts, so the new value-level dependency forms no runtime cycle (tsc clean + runtime import() of capabilities.ts loads with no TDZ/circular-init error). Rewrite the capability parity tests (now tautologies) into an admission invariant: every matrix entry is selectable (platform bucket or supports predicate) and the public-command coverage floor is unchanged. Daemon and batch-name parity tests kept.
1 parent 47abc8c commit 96bc7b1

2 files changed

Lines changed: 45 additions & 301 deletions

File tree

‎src/core/capabilities.ts‎

Lines changed: 9 additions & 237 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { deriveCapabilityMatrix } from './command-descriptor/derive.ts';
2+
import { commandDescriptors } from './command-descriptor/registry.ts';
13
import type { DeviceInfo } from '../utils/device.ts';
24

35
type KindMatrix = {
@@ -17,29 +19,6 @@ export type CommandCapability = {
1719
unsupportedHint?: (device: DeviceInfo) => string | undefined;
1820
};
1921

20-
const isNotMacOs = (device: DeviceInfo): boolean => device.platform !== 'macos';
21-
const isMacOsOrAppleSimulator = (device: DeviceInfo): boolean =>
22-
device.platform === 'macos' || device.kind === 'simulator';
23-
const isIosMobileSimulator = (device: DeviceInfo): boolean =>
24-
device.platform === 'ios' && device.kind === 'simulator' && device.target !== 'tv';
25-
26-
// Two-finger gesture synthesis (RunnerSynthesizedGesture) is iOS-simulator-only (plus Android).
27-
// When such a gesture is rejected at admission, explain where it IS available so an agent can
28-
// redirect instead of getting a bare "not supported on this device".
29-
const synthesisGestureUnsupportedHint = (device: DeviceInfo): string | undefined => {
30-
if (device.platform === 'macos')
31-
return 'macOS automation has no multi-touch input — this gesture is supported on Android and the iOS simulator only.';
32-
if (device.platform === 'ios' && device.target === 'tv')
33-
return 'tvOS has no touch input — this gesture is supported on Android and the iOS simulator only.';
34-
if (device.platform === 'ios' && device.kind === 'device')
35-
return 'Two-finger gesture synthesis is iOS-simulator only — not available on physical iOS devices.';
36-
return undefined;
37-
};
38-
39-
// Linux desktop supports these commands via xdotool/ydotool + AT-SPI2.
40-
// Linux device kind is always 'device' (local desktop).
41-
const LINUX_DEVICE: KindMatrix = { device: true };
42-
const LINUX_NONE: KindMatrix = {};
4322
const WEB_DEVICE: KindMatrix = { device: true };
4423
const WEB_RUNTIME_COMMANDS = ['open', 'close'] as const;
4524
const WEB_RECORDING_COMMANDS = ['record'] as const;
@@ -61,220 +40,13 @@ const WEB_SUPPORTED_COMMANDS = new Set<string>([
6140
...WEB_INTERACTION_COMMANDS,
6241
...WEB_SETTING_COMMANDS,
6342
]);
64-
const ALL_DEVICE_COMMAND_CAPABILITY = {
65-
apple: { simulator: true, device: true },
66-
android: { emulator: true, device: true, unknown: true },
67-
linux: LINUX_DEVICE,
68-
} as const satisfies CommandCapability;
69-
const APP_RUNTIME_CAPABILITY = ALL_DEVICE_COMMAND_CAPABILITY;
70-
const APP_INVENTORY_CAPABILITY = {
71-
apple: { simulator: true, device: true },
72-
android: { emulator: true, device: true, unknown: true },
73-
linux: LINUX_NONE,
74-
} as const satisfies CommandCapability;
75-
const APP_INSTALL_CAPABILITY = {
76-
apple: { simulator: true, device: true },
77-
android: { emulator: true, device: true, unknown: true },
78-
linux: LINUX_NONE,
79-
supports: isNotMacOs,
80-
} as const satisfies CommandCapability;
81-
82-
export const BASE_COMMAND_CAPABILITY_MATRIX: Record<string, CommandCapability> = {
83-
// Apple simulator-only.
84-
alert: {
85-
// macOS desktop targets report kind=device, so this stays enabled here and the
86-
// supports() guard excludes iOS physical devices.
87-
apple: { simulator: true, device: true },
88-
android: { emulator: true, device: true, unknown: true },
89-
linux: LINUX_NONE,
90-
supports: (device) => device.platform === 'android' || isMacOsOrAppleSimulator(device),
91-
},
92-
pinch: {
93-
apple: { simulator: true, device: true },
94-
android: { emulator: true, device: true, unknown: true },
95-
linux: LINUX_NONE,
96-
// iOS-simulator-only (plus Android): pinch is driven by the two-finger XCTest synthesis
97-
// path (RunnerSynthesizedGesture), which is iOS-only. macOS has no multi-touch synthesis, so
98-
// it is excluded and fails fast at admission rather than round-tripping to an unsupported
99-
// runner. Matches rotate-gesture / transform-gesture.
100-
supports: (device) => device.platform === 'android' || isIosMobileSimulator(device),
101-
unsupportedHint: synthesisGestureUnsupportedHint,
102-
},
103-
'rotate-gesture': {
104-
apple: { simulator: true, device: true },
105-
android: { emulator: true, device: true, unknown: true },
106-
linux: LINUX_NONE,
107-
supports: (device) => device.platform === 'android' || isIosMobileSimulator(device),
108-
unsupportedHint: synthesisGestureUnsupportedHint,
109-
},
110-
'transform-gesture': {
111-
apple: { simulator: true, device: true },
112-
android: { emulator: true, device: true, unknown: true },
113-
linux: LINUX_NONE,
114-
supports: (device) => device.platform === 'android' || isIosMobileSimulator(device),
115-
unsupportedHint: synthesisGestureUnsupportedHint,
116-
},
117-
'app-switcher': {
118-
apple: { simulator: true, device: true },
119-
android: { emulator: true, device: true, unknown: true },
120-
linux: LINUX_NONE,
121-
supports: isNotMacOs,
122-
},
123-
open: APP_RUNTIME_CAPABILITY,
124-
close: APP_RUNTIME_CAPABILITY,
125-
reinstall: APP_INSTALL_CAPABILITY,
126-
install: APP_INSTALL_CAPABILITY,
127-
'install-from-source': APP_INSTALL_CAPABILITY,
128-
apps: APP_INVENTORY_CAPABILITY,
129-
back: {
130-
apple: { simulator: true, device: true },
131-
android: { emulator: true, device: true, unknown: true },
132-
linux: LINUX_DEVICE,
133-
},
134-
boot: {
135-
apple: { simulator: true, device: true },
136-
android: { emulator: true, device: true, unknown: true },
137-
linux: LINUX_NONE,
138-
supports: isNotMacOs,
139-
},
140-
shutdown: {
141-
apple: { simulator: true },
142-
android: { emulator: true },
143-
linux: LINUX_NONE,
144-
},
145-
click: {
146-
apple: { simulator: true, device: true },
147-
android: { emulator: true, device: true, unknown: true },
148-
linux: LINUX_DEVICE,
149-
},
150-
clipboard: {
151-
apple: { simulator: true, device: true },
152-
android: { emulator: true, device: true, unknown: true },
153-
linux: LINUX_DEVICE,
154-
supports: (device) =>
155-
device.platform === 'android' ||
156-
device.platform === 'linux' ||
157-
device.platform === 'macos' ||
158-
device.kind === 'simulator',
159-
},
160-
keyboard: {
161-
// iOS only supports keyboard dismiss/enter; status/get remains Android-only.
162-
apple: { simulator: true, device: true },
163-
android: { emulator: true, device: true, unknown: true },
164-
linux: LINUX_NONE,
165-
supports: (device) =>
166-
device.platform === 'android' || (device.platform === 'ios' && device.target !== 'tv'),
167-
},
168-
fill: {
169-
apple: { simulator: true, device: true },
170-
android: { emulator: true, device: true, unknown: true },
171-
linux: LINUX_DEVICE,
172-
},
173-
fling: {
174-
apple: { simulator: true, device: true },
175-
android: { emulator: true, device: true, unknown: true },
176-
linux: LINUX_NONE,
177-
},
178-
snapshot: ALL_DEVICE_COMMAND_CAPABILITY,
179-
diff: ALL_DEVICE_COMMAND_CAPABILITY,
180-
screenshot: ALL_DEVICE_COMMAND_CAPABILITY,
181-
wait: ALL_DEVICE_COMMAND_CAPABILITY,
182-
get: ALL_DEVICE_COMMAND_CAPABILITY,
183-
find: ALL_DEVICE_COMMAND_CAPABILITY,
184-
is: ALL_DEVICE_COMMAND_CAPABILITY,
185-
focus: {
186-
apple: { simulator: true, device: true },
187-
android: { emulator: true, device: true, unknown: true },
188-
linux: LINUX_DEVICE,
189-
},
190-
home: {
191-
apple: { simulator: true, device: true },
192-
android: { emulator: true, device: true, unknown: true },
193-
linux: LINUX_DEVICE,
194-
supports: isNotMacOs,
195-
},
196-
logs: {
197-
apple: { simulator: true, device: true },
198-
android: { emulator: true, device: true, unknown: true },
199-
linux: LINUX_NONE,
200-
},
201-
network: {
202-
apple: { simulator: true, device: true },
203-
android: { emulator: true, device: true, unknown: true },
204-
linux: LINUX_NONE,
205-
},
206-
longpress: {
207-
apple: { simulator: true, device: true },
208-
android: { emulator: true, device: true, unknown: true },
209-
linux: LINUX_DEVICE,
210-
},
211-
perf: {
212-
apple: { simulator: true, device: true },
213-
android: { emulator: true, device: true, unknown: true },
214-
linux: LINUX_NONE,
215-
},
216-
pan: {
217-
apple: { simulator: true, device: true },
218-
android: { emulator: true, device: true, unknown: true },
219-
linux: LINUX_DEVICE,
220-
},
221-
press: {
222-
apple: { simulator: true, device: true },
223-
android: { emulator: true, device: true, unknown: true },
224-
linux: LINUX_DEVICE,
225-
},
226-
push: {
227-
apple: { simulator: true },
228-
android: { emulator: true, device: true, unknown: true },
229-
linux: LINUX_NONE,
230-
supports: isNotMacOs,
231-
},
232-
record: {
233-
apple: { simulator: true, device: true },
234-
android: { emulator: true, device: true, unknown: true },
235-
linux: LINUX_NONE,
236-
},
237-
'react-native': {
238-
apple: { simulator: true, device: true },
239-
android: { emulator: true, device: true, unknown: true },
240-
linux: LINUX_NONE,
241-
},
242-
rotate: {
243-
apple: { simulator: true, device: true },
244-
android: { emulator: true, device: true, unknown: true },
245-
linux: LINUX_NONE,
246-
supports: (device) =>
247-
device.platform === 'android' || (device.platform === 'ios' && device.target !== 'tv'),
248-
},
249-
scroll: {
250-
apple: { simulator: true, device: true },
251-
android: { emulator: true, device: true, unknown: true },
252-
linux: LINUX_DEVICE,
253-
},
254-
swipe: {
255-
apple: { simulator: true, device: true },
256-
android: { emulator: true, device: true, unknown: true },
257-
linux: LINUX_DEVICE,
258-
},
259-
settings: {
260-
apple: { simulator: true, device: true },
261-
android: { emulator: true, device: true, unknown: true },
262-
linux: LINUX_NONE,
263-
supports: (device) =>
264-
device.platform === 'android' || device.platform === 'macos' || device.kind === 'simulator',
265-
},
266-
viewport: {
267-
apple: { simulator: true, device: true },
268-
android: { emulator: true, device: true, unknown: true },
269-
linux: LINUX_NONE,
270-
},
271-
'trigger-app-event': {
272-
apple: { simulator: true, device: true },
273-
android: { emulator: true, device: true, unknown: true },
274-
linux: LINUX_NONE,
275-
},
276-
type: ALL_DEVICE_COMMAND_CAPABILITY,
277-
};
43+
// Built from the additive command-descriptor registry (ADR-0008, Phase 1 step 3).
44+
// The hand-authored literal was deleted after #906 proved deriveCapabilityMatrix is
45+
// byte-equal to it (platform/kind buckets plus the supports/unsupportedHint closures,
46+
// across the sample-device matrix). The registry only type-imports CommandCapability
47+
// from here, so this value-level dependency does not form a runtime cycle.
48+
export const BASE_COMMAND_CAPABILITY_MATRIX: Record<string, CommandCapability> =
49+
deriveCapabilityMatrix(commandDescriptors);
27850

27951
const COMMAND_CAPABILITY_MATRIX = addWebCommandCapabilities(BASE_COMMAND_CAPABILITY_MATRIX);
28052

0 commit comments

Comments
 (0)