Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 2 additions & 16 deletions src/client-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type {
} from './contracts/navigation.ts';
export type { ClipboardCommandResult } from './contracts/clipboard.ts';
export type { AppStateCommandResult } from './contracts/app-state.ts';
export type { KeyboardCommandResult } from './contracts/keyboard.ts';

export type AgentDeviceDaemonTransport = (
req: Omit<DaemonRequest, 'token'>,
Expand Down Expand Up @@ -475,21 +476,6 @@ export type AlertCommandResult = DaemonResponseData & {
items?: string[];
};

export type KeyboardCommandResult = DaemonResponseData & {
platform?: 'android' | 'ios';
action?: 'status' | 'dismiss' | 'enter';
visible?: boolean;
inputType?: string | null;
inputMethodPackage?: string | null;
type?: string | null;
focusedPackage?: string | null;
focusedResourceId?: string | null;
inputOwner?: 'app' | 'ime' | 'unknown';
wasVisible?: boolean;
dismissed?: boolean;
attempts?: number;
};

export type ReactNativeCommandOptions = DeviceCommandBaseOptions & {
action: 'dismiss-overlay';
};
Expand All @@ -512,7 +498,7 @@ export type AgentDeviceCommandClient = {
home: (options?: HomeCommandOptions) => Promise<CommandResult<'home'>>;
rotate: (options: RotateCommandOptions) => Promise<CommandResult<'rotate'>>;
appSwitcher: (options?: AppSwitcherCommandOptions) => Promise<CommandResult<'app-switcher'>>;
keyboard: (options?: KeyboardCommandOptions) => Promise<KeyboardCommandResult>;
keyboard: (options?: KeyboardCommandOptions) => Promise<CommandResult<'keyboard'>>;
clipboard: (options: ClipboardCommandOptions) => Promise<CommandResult<'clipboard'>>;
reactNative: (options: ReactNativeCommandOptions) => Promise<CommandRequestResult>;
prepare: (options: PrepareCommandOptions) => Promise<CommandRequestResult>;
Expand Down
3 changes: 2 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ export function createAgentDeviceClient(
rotate: async (options) => await executeCommand<CommandResult<'rotate'>>('rotate', options),
appSwitcher: async (options = {}) =>
await executeCommand<CommandResult<'app-switcher'>>('app-switcher', options),
keyboard: async (options = {}) => await executeCommand('keyboard', options),
keyboard: async (options = {}) =>
await executeCommand<CommandResult<'keyboard'>>('keyboard', options),
clipboard: async (options) =>
await executeCommand<CommandResult<'clipboard'>>('clipboard', options),
reactNative: async (options) => await executeCommand('react-native', options),
Expand Down
29 changes: 29 additions & 0 deletions src/contracts/keyboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Closed result of the `keyboard` command, grounded in the dispatch handlers'
* literal returns (src/core/dispatch.ts `handleAndroidKeyboardCommand` /
* `handleIosKeyboardCommand`).
*
* `platform` and `action` are always present; the remaining fields appear per
* branch (Android `status`/`dismiss` carry the keyboard-state fields; `enter`
* and iOS `dismiss` carry a `message`). It is kept as a flat closed shape rather
* than a five-way `platform`×`action` union because the per-branch field sets
* overlap heavily and the underlying Android keyboard-state types live in the
* platform layer (below the public contract). The `Record` index signature of
* the previous hand-written mirror is dropped, and the spurious `| null`s are
* removed (the handler never returns `null` for these).
*/
export type KeyboardCommandResult = {
platform: 'android' | 'ios';
action: 'status' | 'dismiss' | 'enter';
visible?: boolean;
wasVisible?: boolean;
dismissed?: boolean;
attempts?: number;
inputType?: string;
type?: 'text' | 'number' | 'email' | 'phone' | 'password' | 'datetime' | 'unknown';
inputMethodPackage?: string;
focusedPackage?: string;
focusedResourceId?: string;
inputOwner?: 'app' | 'ime' | 'unknown';
message?: string;
};
6 changes: 5 additions & 1 deletion src/core/command-descriptor/__tests__/command-result.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
} from '../../../contracts/navigation.ts';
import type { ClipboardCommandResult } from '../../../contracts/clipboard.ts';
import type { AppStateCommandResult } from '../../../contracts/app-state.ts';
import type { KeyboardCommandResult } from '../../../contracts/keyboard.ts';
import type { CommandResult, CommandResultMap } from '../command-result.ts';

/**
Expand All @@ -38,6 +39,7 @@ test('seeded CommandResult entries resolve to their existing contract result typ
const appSwitcher: Equal<CommandResult<'app-switcher'>, AppSwitcherCommandResult> = true;
const clipboard: Equal<CommandResult<'clipboard'>, ClipboardCommandResult> = true;
const appstate: Equal<CommandResult<'appstate'>, AppStateCommandResult> = true;
const keyboard: Equal<CommandResult<'keyboard'>, KeyboardCommandResult> = true;
expect([
press,
fill,
Expand All @@ -51,7 +53,8 @@ test('seeded CommandResult entries resolve to their existing contract result typ
appSwitcher,
clipboard,
appstate,
]).toEqual([true, true, true, true, true, true, true, true, true, true, true, true]);
keyboard,
]).toEqual([true, true, true, true, true, true, true, true, true, true, true, true, true]);
});

test('unmigrated commands fall back to the untyped Record bag, keeping the union total', () => {
Expand All @@ -76,6 +79,7 @@ test('CommandResultMap is seeded only from already-existing contract result type
| 'app-switcher'
| 'clipboard'
| 'appstate'
| 'keyboard'
> = true;
expect(keys).toBe(true);
});
5 changes: 4 additions & 1 deletion src/core/command-descriptor/command-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
} from '../../contracts/navigation.ts';
import type { ClipboardCommandResult } from '../../contracts/clipboard.ts';
import type { AppStateCommandResult } from '../../contracts/app-state.ts';
import type { KeyboardCommandResult } from '../../contracts/keyboard.ts';

/**
* The additive typed-result spine (ADR-0008, Phase 1 step 6).
Expand All @@ -28,7 +29,8 @@ import type { AppStateCommandResult } from '../../contracts/app-state.ts';
* commands `home` / `back` / `rotate` / `app-switcher` alongside the seed
* interaction trio. Batch 3 adds `clipboard` (a closed `read`/`write` union) and
* `appstate` (a closed `platform` union — Apple session state with the iOS-only
* device locators, or Android package/activity). Each entry is grounded in a
* device locators, or Android package/activity). Batch 4 adds `keyboard` (a
* closed flat shape). Each entry is grounded in a
* re-read of the handler's literal return; see the per-type docstrings.
*/
export interface CommandResultMap {
Expand All @@ -44,6 +46,7 @@ export interface CommandResultMap {
'app-switcher': AppSwitcherCommandResult;
clipboard: ClipboardCommandResult;
appstate: AppStateCommandResult;
keyboard: KeyboardCommandResult;
}

/**
Expand Down
Loading