@lingxia/types 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/automation/index.d.ts +1039 -128
  2. package/dist/automation/index.d.ts.map +1 -1
  3. package/dist/automation/index.js +61 -3
  4. package/dist/automation/index.js.map +1 -1
  5. package/dist/error.d.ts +19 -0
  6. package/dist/error.d.ts.map +1 -1
  7. package/dist/error.js +23 -1
  8. package/dist/error.js.map +1 -1
  9. package/dist/esm/automation/index.js +60 -4
  10. package/dist/esm/automation/index.js.map +1 -1
  11. package/dist/esm/error.js +21 -0
  12. package/dist/esm/error.js.map +1 -1
  13. package/dist/esm/generated/error.js +1 -0
  14. package/dist/esm/generated/error.js.map +1 -1
  15. package/dist/esm/generated/i18n.js +10 -1
  16. package/dist/esm/generated/i18n.js.map +1 -1
  17. package/dist/esm/index.js.map +1 -1
  18. package/dist/esm/mocks.js +2 -0
  19. package/dist/esm/mocks.js.map +1 -0
  20. package/dist/esm/page.js +2 -0
  21. package/dist/esm/page.js.map +1 -0
  22. package/dist/esm/testing/public-api.js +133 -48
  23. package/dist/esm/testing/public-api.js.map +1 -1
  24. package/dist/generated/error.d.ts +4 -0
  25. package/dist/generated/error.d.ts.map +1 -1
  26. package/dist/generated/error.js +1 -0
  27. package/dist/generated/error.js.map +1 -1
  28. package/dist/generated/i18n.d.ts +1 -1
  29. package/dist/generated/i18n.d.ts.map +1 -1
  30. package/dist/generated/i18n.js +10 -1
  31. package/dist/generated/i18n.js.map +1 -1
  32. package/dist/generated/logic-web.d.ts +24 -3
  33. package/dist/generated/logic.d.ts +772 -381
  34. package/dist/generated/logic.d.ts.map +1 -1
  35. package/dist/index.d.ts +17 -9
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/mocks.d.ts +58 -0
  39. package/dist/mocks.d.ts.map +1 -0
  40. package/dist/mocks.js +3 -0
  41. package/dist/mocks.js.map +1 -0
  42. package/dist/page.d.ts +26 -0
  43. package/dist/page.d.ts.map +1 -0
  44. package/dist/page.js +3 -0
  45. package/dist/page.js.map +1 -0
  46. package/dist/process.d.ts +15 -4
  47. package/dist/process.d.ts.map +1 -1
  48. package/dist/testing/public-api.d.ts +127 -51
  49. package/dist/testing/public-api.d.ts.map +1 -1
  50. package/dist/testing/public-api.js +133 -48
  51. package/dist/testing/public-api.js.map +1 -1
  52. package/package.json +8 -5
  53. package/src/automation/index.ts +1066 -128
  54. package/src/error.ts +23 -0
  55. package/src/generated/error.ts +1 -0
  56. package/src/generated/i18n.ts +10 -1
  57. package/src/generated/logic-web.d.ts +24 -3
  58. package/src/generated/logic.ts +830 -405
  59. package/src/index.ts +19 -10
  60. package/src/mocks.ts +62 -0
  61. package/src/page.ts +26 -0
  62. package/src/process.ts +14 -4
  63. package/src/testing/public-api.ts +155 -49
  64. package/dist/automation-test-globals.d.ts +0 -14
@@ -1,37 +1,91 @@
1
1
  /**
2
2
  * In-process UI/runtime automation — `lx.automation()`.
3
3
  *
4
- * Returns a stable selector root. Selecting the calling lxapp requires the
5
- * `automation` security privilege; cross-lxapp and host surfaces require
6
- * `host`. `lingxia dev` sessions and the Runner grant both implicitly.
4
+ * Two roots share one runtime object:
5
+ *
6
+ * - {@link Automation} is what app Logic may call. Selecting the calling lxapp
7
+ * requires the `automation` security privilege; cross-lxapp and host
8
+ * surfaces require `host`. A session holds one only when the privilege
9
+ * class is allowed for the app (the app registry, or its unrestricted
10
+ * default) and the native host seals a matching session grant — from a
11
+ * product `HostAddon`, or a devtools host such as the Runner, which grants
12
+ * the allowed automation privileges and nothing beyond them. Neither
13
+ * `lingxia dev` nor the Runner widens what app Logic is allowed.
14
+ * - {@link HostRunAutomation} is the root of a host automation run
15
+ * (`lxdev test`). That context carries the host's own authority, so every
16
+ * selector is available without a grant, and it adds the test-run-only
17
+ * members: `network`, nav `waitUntil: 'ready'`, and eval call tracing.
7
18
  *
8
19
  * This mirrors the devtool (`lxdev`) automation surface as a privilege-scoped,
9
20
  * product-side API.
10
21
  */
11
22
  import type { TerminalSettingsValue } from '../generated/logic.js';
12
- /** Stable automation root; it grants no capability until one is selected. */
23
+ /**
24
+ * Stable `code`s of automation driver rejections. Anything without a more
25
+ * specific code rejects with `E_AUTOMATION`. Desktop codes describe OS
26
+ * automation failures. Match on the code, not the message.
27
+ */
28
+ export declare const AUTOMATION_ERROR_CODES: readonly ["E_AUTOMATION", "E_SCENARIO_STATE_UNKNOWN", "E_AUTOMATION_PRIVILEGE", "E_PAGE_NOT_ACTIVE", "E_PAGE_NOT_READY", "E_PAGE_ACTION", "E_ELEMENT_NOT_FOUND", "E_ELEMENT_NOT_INTERACTABLE", "E_AUTOMATION_TIMEOUT", "E_EVAL_SCRIPT", "E_EVAL_TIMEOUT", "E_PROFILE_NOT_ISOLATED", "E_CLOCK_NOT_INSTALLED", "E_CLOCK_INSTALLED", "E_DESKTOP_USAGE", "E_DESKTOP_NOT_FOUND", "E_DESKTOP_AMBIGUOUS", "E_DESKTOP_TIMEOUT", "E_DESKTOP_PERMISSION", "E_DESKTOP_UNSUPPORTED", "E_DESKTOP_UNAVAILABLE", "E_DESKTOP_STALE", "E_DESKTOP_FAILED"];
29
+ export type AutomationErrorCode = (typeof AUTOMATION_ERROR_CODES)[number];
30
+ /** A page as automation error `data` names it. */
31
+ export interface AutomationPageRef {
32
+ /** Configured page name, else its path. */
33
+ page?: string;
34
+ /** The page instance the call resolved to, when it still resolves. */
35
+ instanceId?: string;
36
+ /** The page that was current when the call failed. */
37
+ current?: {
38
+ name: string | null;
39
+ path: string;
40
+ instanceId: string;
41
+ };
42
+ }
43
+ /**
44
+ * Automation root as app Logic sees it (`lx.automation()` in Logic). It grants
45
+ * no capability until one is selected. Reading a driver property never throws:
46
+ * each driver method checks the caller and rejects with
47
+ * `E_AUTOMATION_PRIVILEGE` when it is not allowed.
48
+ */
13
49
  export interface Automation {
14
- /** Drive the calling/current lxapp. Requires `automation` outside dev. */
15
- lxapp(): LxAppDriver;
16
- /** Drive a specific running lxapp. Requires `host` outside dev. */
17
- lxapp(appid: string): LxAppDriver;
18
- /** Cross-lxapp lifecycle and host-window capture. */
50
+ /**
51
+ * Drive the calling lxapp. Requires the `automation` privilege. Evaluating
52
+ * the calling Logic runtime itself (`eval`) rejects.
53
+ */
54
+ lxapp(): LogicLxAppDriver;
55
+ /** Drive a specific running lxapp. Requires the `host` privilege. */
56
+ lxapp(appid: string): LogicLxAppDriver;
57
+ /** Cross-lxapp lifecycle and host-window capture. Requires `host`. */
19
58
  readonly lxapps: LxAppManager;
20
- /** The host app's browser tabs. */
59
+ /** The host app's browser tabs. Requires `host`. */
21
60
  readonly browser: BrowserDriver;
22
- /** Persisted host-shell shortcuts for deterministic test setup/assertion. */
61
+ /** Persisted host-shell shortcuts for deterministic test setup/assertion. Requires `host`. */
23
62
  readonly shell: ShellDriver;
24
- /** Simulated-device selection in a host runner. */
63
+ /** Simulated-device selection in a host runner. Requires `host`. */
25
64
  readonly device: DeviceDriver;
26
65
  /**
27
- * Session-less local-OS desktop automation (`lxdev desktop`). Beyond the
28
- * app sandbox, so restricted to dev/test hosts (`lingxia dev` or the
29
- * Runner) on top of the `host` privilege. Windows/macOS only.
66
+ * Session-less local-OS desktop automation (`lxdev desktop`). Windows/macOS
67
+ * only. Gated by the `host` privilege alone, and present only in hosts
68
+ * built with desktop automation (the Runner, or a product that enables the
69
+ * `desktop-automation` feature); elsewhere reading it throws. It drives the
70
+ * whole OS, beyond the app sandbox — grant `host` only to lxapps you trust
71
+ * with that.
30
72
  */
31
73
  readonly desktop: DesktopDriver;
32
- /** Native terminal workspace state and pane actions in trusted dev/test hosts. */
74
+ /** Native terminal workspace state and pane actions. Requires `host`. */
33
75
  readonly terminal: TerminalDriver;
34
76
  }
77
+ /**
78
+ * Automation root of a host automation run — `rawAutomation()` (and, traced,
79
+ * `t.automation`) from `@lingxia/test` in an `lxdev test` program. The run
80
+ * carries host authority, so no selector needs a privilege grant, and the
81
+ * selected lxapp driver adds the test-run-only members.
82
+ */
83
+ export interface HostRunAutomation extends Automation {
84
+ /** Drive the host's current lxapp. */
85
+ lxapp(): LxAppDriver;
86
+ /** Drive a specific running lxapp. */
87
+ lxapp(appid: string): LxAppDriver;
88
+ }
35
89
  export type TerminalSplitDirection = 'left' | 'right' | 'up' | 'down';
36
90
  export interface TerminalSurfaceRef {
37
91
  /** Stable id returned by `lx.shell.openDeclared('terminal', { key })`. */
@@ -106,7 +160,10 @@ export interface TerminalWorkspaceSnapshot {
106
160
  export interface TerminalSplitOptions extends TerminalSurfaceRef {
107
161
  direction: TerminalSplitDirection;
108
162
  }
109
- /** Native terminal automation; available only to trusted dev/test hosts. */
163
+ /**
164
+ * Native terminal automation. Requires the `host` privilege from app Logic
165
+ * (a host automation run needs none) and a host with a native terminal.
166
+ */
110
167
  export interface TerminalDriver {
111
168
  snapshot(options: TerminalSurfaceRef): Promise<TerminalWorkspaceSnapshot>;
112
169
  /** Send text to the focused pane, as if typed into its PTY. */
@@ -145,6 +202,8 @@ export type SetAutomationShellPinOptions = AutomationShellPin & {
145
202
  export interface ShellDriver {
146
203
  /** Ordered shortcuts exactly as projected into the host sidebar. */
147
204
  pins(): Promise<AutomationShellPin[]>;
205
+ /** Submit every current Pin exactly once, in the desired mixed order. */
206
+ reorderPins(items: AutomationShellPin[]): Promise<AutomationShellPin[]>;
148
207
  /**
149
208
  * Idempotently persist or remove one shortcut. New Pins append to the
150
209
  * existing order; adding beyond the host limit rejects without mutation.
@@ -154,7 +213,7 @@ export interface ShellDriver {
154
213
  }
155
214
  /** Fields common to every page action; `page` defaults to the current page. */
156
215
  export interface PageTarget {
157
- /** Configured page name (from lxapp.json); defaults to the current page. */
216
+ /** Configured page name or live instance_id; defaults to the current page. */
158
217
  page?: string;
159
218
  }
160
219
  export interface PageEvalOptions extends PageTarget {
@@ -162,6 +221,14 @@ export interface PageEvalOptions extends PageTarget {
162
221
  script: string;
163
222
  timeoutMs?: number;
164
223
  }
224
+ export interface PageActionOptions extends PageTarget {
225
+ /** Name of a unary action of the page's `Page({...})`; stream (generator) actions are refused. */
226
+ name: string;
227
+ /** The action's one JSON argument, passed as is; omit for none. */
228
+ payload?: unknown;
229
+ /** Budget for the page to become ready and the action to settle (default 5000). */
230
+ timeoutMs?: number;
231
+ }
165
232
  export interface PageQueryOptions extends PageTarget {
166
233
  /** CSS selector. */
167
234
  css: string;
@@ -182,6 +249,18 @@ export interface PageSelectorOptions extends PageTarget {
182
249
  export interface PageTypeOptions extends PageSelectorOptions {
183
250
  text: string;
184
251
  }
252
+ export interface PageClickOptions extends PageSelectorOptions {
253
+ /**
254
+ * Dispatch to the element itself, without the in-viewport and hit-test
255
+ * checks; it must still exist and be enabled. For content no scroll can
256
+ * bring under the pointer, such as the lower part of an overflowing sheet.
257
+ */
258
+ force?: boolean;
259
+ }
260
+ export interface PageFillOptions extends PageTypeOptions {
261
+ /** Write without the in-viewport check; see `PageClickOptions.force`. */
262
+ force?: boolean;
263
+ }
185
264
  export interface PagePressOptions extends PageTarget {
186
265
  /** Key name, e.g. `Enter`, `Escape`, `Tab`. */
187
266
  key: string;
@@ -200,16 +279,24 @@ export interface PageScrollToOptions extends PageTarget {
200
279
  /** CSS selector of the element to reveal (first match). */
201
280
  css: string;
202
281
  }
203
- export type PageWaitState = 'attached' | 'detached' | 'visible' | 'hidden' | 'enabled' | 'editable'
204
- /** @deprecated Use `attached`. */
205
- | 'exists'
206
- /** @deprecated Use `detached`. */
207
- | 'gone';
282
+ /**
283
+ * Raw `page.waitFor` states check the first match of `css`:
284
+ * - `attached`: at least one match.
285
+ * - `detached`: no match (also while the page is not yet active).
286
+ * - `visible`: the first match is rendered (a non-empty box, not
287
+ * `display:none`, `visibility:hidden` or `opacity:0`), in the viewport or
288
+ * scrolled out of it.
289
+ * - `hidden`: the first match exists and is not rendered; no match does not
290
+ * satisfy it (wait for `detached`).
291
+ * - `enabled` / `editable`: the first match exists and is enabled / editable.
292
+ * `@lingxia/test` locators apply stricter, uniqueness-aware states.
293
+ */
294
+ export type PageWaitState = 'attached' | 'detached' | 'visible' | 'hidden' | 'enabled' | 'editable';
208
295
  export interface PageWaitForOptions extends PageTarget {
209
296
  css: string;
210
297
  /** Condition to await (default `visible`). */
211
298
  state?: PageWaitState;
212
- /** Timeout in ms (default 10000, capped at 60000). */
299
+ /** Timeout in ms (default 30000, capped at 60000). */
213
300
  timeoutMs?: number;
214
301
  }
215
302
  /**
@@ -225,12 +312,12 @@ export interface ElementRect {
225
312
  height: number;
226
313
  right: number;
227
314
  bottom: number;
228
- center_x: number;
229
- center_y: number;
230
- viewport_width: number;
231
- viewport_height: number;
315
+ centerX: number;
316
+ centerY: number;
317
+ viewportWidth: number;
318
+ viewportHeight: number;
232
319
  }
233
- /** A matched element. Keys are the raw automation payload (snake_case). */
320
+ /** A matched element. Uses JavaScript field names. */
234
321
  export interface PageElement {
235
322
  exists: true;
236
323
  index: number;
@@ -242,16 +329,21 @@ export interface PageElement {
242
329
  id: string | null;
243
330
  name: string | null;
244
331
  role: string | null;
245
- aria_label: string | null;
332
+ ariaLabel: string | null;
246
333
  placeholder: string | null;
247
- /** Viewport-aware visibility (size, style, and in-viewport). */
334
+ /**
335
+ * Rendered: a non-empty box that is not `display:none`,
336
+ * `visibility:hidden` or `opacity:0`, wherever it is scrolled.
337
+ */
248
338
  visible: boolean;
339
+ /** Rendered and intersecting the viewport. */
340
+ inViewport: boolean;
249
341
  enabled: boolean;
250
342
  editable: boolean;
251
343
  text: string;
252
- text_truncated: boolean;
344
+ textTruncated: boolean;
253
345
  value: string | null;
254
- value_truncated: boolean;
346
+ valueTruncated: boolean;
255
347
  rect: ElementRect;
256
348
  }
257
349
  /** Returned when no element matches (single-element mode). */
@@ -260,6 +352,7 @@ export interface PageElementMiss {
260
352
  index: number;
261
353
  count: number;
262
354
  visible: false;
355
+ inViewport: false;
263
356
  enabled: false;
264
357
  editable: false;
265
358
  }
@@ -278,8 +371,16 @@ export interface Screenshot {
278
371
  }
279
372
  /** Element-level automation of the selected lxapp's page WebViews. */
280
373
  export interface PageDriver {
281
- /** Evaluate JavaScript in the page WebView; resolves to the returned value. */
282
- eval(options: PageEvalOptions): Promise<unknown>;
374
+ /** Evaluate in the page WebView. T describes the expected JSON result; it is not runtime validation. */
375
+ eval<T = unknown>(options: PageEvalOptions): Promise<T>;
376
+ /**
377
+ * Invoke a page action as the View would and resolve to its JSON result.
378
+ * Waits for the page's runtime within `timeoutMs`. A rejection keeps the
379
+ * action's own `code` and message (`data.cause` has what it rejected with);
380
+ * `E_PAGE_NOT_READY` if the page never loaded its actions,
381
+ * `E_AUTOMATION_TIMEOUT` if the action did not settle in time.
382
+ */
383
+ action<T = unknown>(options: PageActionOptions): Promise<T>;
283
384
  /** Query one element's info. */
284
385
  query(options: PageQueryOptions & {
285
386
  all?: false;
@@ -288,11 +389,13 @@ export interface PageDriver {
288
389
  query(options: PageQueryOptions & {
289
390
  all: true;
290
391
  }): Promise<PageQueryAll>;
291
- click(options: PageSelectorOptions): Promise<void>;
392
+ query(options: PageQueryOptions): Promise<PageQueryResult | PageQueryAll>;
393
+ /** Single dispatch; test locators provide actionability waiting. */
394
+ click(options: PageClickOptions): Promise<void>;
292
395
  /** Type text into an element without clearing existing content. */
293
396
  type(options: PageTypeOptions): Promise<void>;
294
397
  /** Replace an element's current value. */
295
- fill(options: PageTypeOptions): Promise<void>;
398
+ fill(options: PageFillOptions): Promise<void>;
296
399
  press(options: PagePressOptions): Promise<void>;
297
400
  /** Scroll the first matching element into view. */
298
401
  scrollTo(options: PageScrollToOptions): Promise<void>;
@@ -306,70 +409,133 @@ export interface PageDriver {
306
409
  /** App-window keyboard input (`lxdev lxapp page key`). */
307
410
  readonly key: PageKey;
308
411
  }
309
- export interface NavOptions {
412
+ /**
413
+ * When a nav action resolves. `'commit'` (default) resolves once the page
414
+ * stack changed, before the landed page is ready. `'ready'` also waits for its
415
+ * `onReady` and rejects if the page is disposed or replaced first (e.g. by the
416
+ * app's own `lx.reLaunch`); it is available only in a host automation run
417
+ * ({@link NavDriver}), since app Logic awaiting its own `onReady` would
418
+ * deadlock. The `@lingxia/test` fixture's `t.app.nav` defaults to `'ready'`.
419
+ */
420
+ export type NavWaitUntil = 'commit' | 'ready';
421
+ /** Nav wait options available to app Logic: only `'commit'`. */
422
+ export interface LogicNavWaitOptions {
423
+ waitUntil?: 'commit';
424
+ }
425
+ /** Nav wait options of a host automation run. */
426
+ export interface NavWaitOptions {
427
+ waitUntil?: NavWaitUntil;
428
+ /** Bound for `waitUntil: 'ready'` in ms (default 15000, capped at 60000). */
429
+ timeoutMs?: number;
430
+ }
431
+ interface NavTargetFields {
310
432
  /** Configured page name (from lxapp.json). */
311
433
  page: string;
312
434
  /** Query forwarded to the destination page. */
313
435
  query?: Record<string, unknown>;
314
436
  }
315
- export interface NavBackOptions {
437
+ interface NavBackFields {
316
438
  /** Number of pages to pop (default 1). */
317
439
  delta?: number;
318
440
  }
441
+ export interface LogicNavOptions extends NavTargetFields, LogicNavWaitOptions {
442
+ }
443
+ export interface LogicNavBackOptions extends NavBackFields, LogicNavWaitOptions {
444
+ }
445
+ export interface NavOptions extends NavTargetFields, NavWaitOptions {
446
+ }
447
+ export interface NavBackOptions extends NavBackFields, NavWaitOptions {
448
+ }
319
449
  /** A page's runtime position. */
320
450
  export interface PageInfo {
321
451
  path: string;
322
452
  /** Configured page name, if the path maps to one. */
323
453
  name: string | null;
454
+ /**
455
+ * The page instance. Navigation that replaces a page (even with the same
456
+ * path) gives it a new id. `null` when no live instance backs a stack
457
+ * entry.
458
+ */
459
+ instanceId: string | null;
324
460
  current: boolean;
325
461
  inStack: boolean;
326
- /** Whether the page has an attached WebView. */
462
+ /** Whether the page has dispatched `onReady` (what `waitUntil: 'ready'` awaits). */
327
463
  ready: boolean;
464
+ /** Whether the page currently has an attached WebView; precedes `ready`. */
465
+ webviewAttached: boolean;
328
466
  }
329
467
  /**
330
- * Page-stack navigation for the selected lxapp. Action verbs take a configured
331
- * page name (`redirect` rejects a tab-bar page); `back` pops; `current`/`stack`
332
- * read. Unlike the JS `lx.navigateTo` family this returns the landed page, but
333
- * like it does not wait for the destination WebView (awaiting in-process would
334
- * deadlock the logic thread).
468
+ * Page-stack navigation for the selected lxapp, as app Logic sees it. Action
469
+ * verbs take a configured page name (`redirect` rejects a tab-bar page);
470
+ * `back` pops; `current`/`stack` read. Unlike the JS `lx.navigateTo` family
471
+ * this returns the landed page. It resolves once the stack changed, before the
472
+ * destination is ready.
335
473
  */
336
- export interface NavDriver {
474
+ export interface LogicNavDriver {
337
475
  /** Push a page onto the stack. */
338
- to(options: NavOptions): Promise<PageInfo>;
476
+ to(options: LogicNavOptions): Promise<PageInfo>;
339
477
  /** Replace the current page (rejects tab-bar targets). */
340
- redirect(options: NavOptions): Promise<PageInfo>;
478
+ redirect(options: LogicNavOptions): Promise<PageInfo>;
341
479
  /** Switch to a configured tab page. */
342
- switchTab(options: NavOptions): Promise<PageInfo>;
343
- /** Clear the stack and relaunch at a page. */
344
- relaunch(options: NavOptions): Promise<PageInfo>;
345
- back(options?: NavBackOptions): Promise<PageInfo>;
480
+ switchTab(options: LogicNavOptions): Promise<PageInfo>;
481
+ /** Unload every page (cached tab pages included) and open a fresh instance of a page. */
482
+ relaunch(options: LogicNavOptions): Promise<PageInfo>;
483
+ back(options?: LogicNavBackOptions): Promise<PageInfo>;
346
484
  current(): Promise<PageInfo>;
347
485
  /** Status of a configured page by name; omit `page` for the current page. */
348
486
  info(options?: PageTarget): Promise<PageInfo>;
349
487
  stack(): Promise<PageInfo[]>;
350
488
  }
489
+ /**
490
+ * Page-stack navigation in a host automation run. Same verbs as
491
+ * {@link LogicNavDriver}; pass `waitUntil: 'ready'` to also wait for the
492
+ * landed page's `onReady`.
493
+ */
494
+ export interface NavDriver extends LogicNavDriver {
495
+ to(options: NavOptions): Promise<PageInfo>;
496
+ redirect(options: NavOptions): Promise<PageInfo>;
497
+ switchTab(options: NavOptions): Promise<PageInfo>;
498
+ relaunch(options: NavOptions): Promise<PageInfo>;
499
+ back(options?: NavBackOptions): Promise<PageInfo>;
500
+ }
351
501
  export interface LxAppSummary {
352
- appid: string;
502
+ appId: string;
353
503
  currentPage: string | null;
354
504
  }
355
505
  export interface LxAppPageConfig {
356
506
  name: string;
357
507
  path: string;
358
508
  }
359
- export interface LxAppEvalOptions {
509
+ /**
510
+ * Envelope `eval` resolves to with `captureCalls: true`.
511
+ *
512
+ * @internal Report plumbing for the test runner; the `@lingxia/test` fixture
513
+ * unwraps it, and specs never see it.
514
+ */
515
+ export interface LxAppEvalTrace<T = unknown> {
516
+ __lxEval: 1;
517
+ value: T;
518
+ calls: string[];
519
+ }
520
+ /** Logic-runtime eval options available to app Logic. */
521
+ export interface LogicLxAppEvalOptions {
360
522
  /** JavaScript expression or function body run in the selected Logic runtime. */
361
523
  script: string;
362
524
  timeoutMs?: number;
525
+ }
526
+ /** Logic-runtime eval options of a host automation run. */
527
+ export interface LxAppEvalOptions extends LogicLxAppEvalOptions {
363
528
  /**
364
529
  * Resolve to `{ value, calls }`, where `calls` lists the `lx.*` members the
365
530
  * script reached, instead of the bare value.
366
531
  *
367
- * The test runner sets this so a report can tell a capability a spec
368
- * exercised from one it merely declared. Only the evaluated script is
532
+ * @internal The test runner sets this so a report can tell a capability a
533
+ * spec exercised from one it merely declared. Only the evaluated script is
369
534
  * observed — the lxapp's own concurrent work is not.
370
535
  */
371
536
  captureCalls?: boolean;
372
537
  }
538
+ /** Shell admission class. Content `lx.surface.watchContext` uses `compact` | `regular`. */
373
539
  export type SurfaceLayoutSizeClass = 'compact' | 'medium' | 'expanded';
374
540
  export type SurfaceLayoutSwitcherForm = 'none' | 'sidebar' | 'rail';
375
541
  export type SurfaceLayoutSplitForm = 'none' | 'split' | 'collapsible' | 'fullScreen';
@@ -479,74 +645,765 @@ export interface SurfaceLayoutSnapshot {
479
645
  floats: SurfaceLayoutFloat[];
480
646
  tree?: SurfaceLayoutTree;
481
647
  }
482
- /** Capability for one selected running lxapp. */
483
- export interface LxAppDriver {
648
+ /**
649
+ * Which Logic `fetch` requests a route handles: a URL glob over the whole URL
650
+ * (`**` any characters, `*` any except `/`, `{a,b}` alternatives; `?` is
651
+ * literal), a `RegExp` searched like `RegExp.test` (Rust regex syntax: no
652
+ * lookaround or backreferences), or either plus a method and a match budget.
653
+ */
654
+ export type NetworkRoutePattern = string | RegExp | {
655
+ url: string | RegExp;
656
+ /** Case-insensitive HTTP method; omit or `'*'` for any. */
657
+ method?: string;
658
+ /** Remove the route after this many matched requests. */
659
+ times?: number;
660
+ };
661
+ type NetworkRouteFulfillKey = 'status' | 'statusText' | 'headers' | 'contentType' | 'body' | 'json' | 'delay';
662
+ type NetworkRouteForbid<K extends string> = Partial<Record<K, never>>;
663
+ interface NetworkRouteFulfillFields {
664
+ /** 200..=599. Default 200. */
665
+ status?: number;
666
+ statusText?: string;
667
+ headers?: Record<string, string>;
668
+ /** Sets `content-type` unless `headers` already has one. */
669
+ contentType?: string;
670
+ /**
671
+ * Milliseconds to wait before the response resolves, 0..=30000; an
672
+ * `AbortSignal` passed to `fetch` still rejects it early.
673
+ */
674
+ delay?: number;
675
+ }
676
+ /**
677
+ * Answer the request without touching the network. The body is either `body`
678
+ * (text or bytes, sent verbatim) or `json` (serialized, implies
679
+ * `content-type: application/json`), never both.
680
+ */
681
+ export type NetworkRouteFulfill = NetworkRouteFulfillFields & NetworkRouteForbid<'abort' | 'continue' | 'patchJson' | 'hang' | 'sse' | 'sequence'> & ({
682
+ body?: string | ArrayBuffer | Uint8Array;
683
+ json?: never;
684
+ } | {
685
+ json: unknown;
686
+ body?: never;
687
+ });
688
+ /**
689
+ * Reject the `fetch` like a transport failure (`TypeError: fetch failed`).
690
+ * `'failed'` is the only failure the wrapper emulates.
691
+ */
692
+ export type NetworkRouteAbort = {
693
+ abort: 'failed';
694
+ } & NetworkRouteForbid<NetworkRouteFulfillKey | 'continue' | 'patchJson' | 'hang' | 'sse' | 'sequence'>;
695
+ /**
696
+ * Let the request reach the network, shadowing older matching routes. With
697
+ * `patchJson`, the real response's JSON body is rewritten host-side by that
698
+ * RFC 7396 merge patch: object keys merge, `null` deletes a key, and any
699
+ * other value (arrays included) replaces what it patches. Status and headers
700
+ * stay the real ones. A body that is not JSON rejects the `fetch` with a
701
+ * `TypeError` naming the URL; an empty body passes through. The patched
702
+ * body is re-serialized, so object keys come back sorted.
703
+ */
704
+ export type NetworkRouteContinue = {
705
+ continue: true;
706
+ patchJson?: unknown;
707
+ } & NetworkRouteForbid<NetworkRouteFulfillKey | 'abort' | 'hang' | 'sse' | 'sequence'>;
708
+ /**
709
+ * Never answer: the `fetch` stays pending until the route is removed (the
710
+ * spec ends, `unroute()`, or the run ends), then rejects like a transport
711
+ * failure. An `AbortSignal` passed to `fetch` still rejects it early. For
712
+ * loading states and client-side timeouts. `times` limits which requests are
713
+ * held, not how long.
714
+ */
715
+ export type NetworkRouteHang = {
716
+ hang: true;
717
+ } & NetworkRouteForbid<NetworkRouteFulfillKey | 'abort' | 'continue' | 'patchJson' | 'sse' | 'sequence'>;
718
+ /**
719
+ * One item of an SSE answer, played in order: an event (`data` that is not a
720
+ * string is sent as JSON; multi-line data becomes several `data:` lines), a
721
+ * `comment` line, a pause of `delayMs` (0..=30000), or `drop`, which closes
722
+ * the stream as a server dropping the connection would and must come last.
723
+ */
724
+ export type NetworkSseItem = {
725
+ event?: string;
726
+ data: unknown;
727
+ id?: string;
728
+ retry?: number;
729
+ } | {
730
+ comment: string;
731
+ } | {
732
+ delayMs: number;
733
+ } | {
734
+ drop: true;
735
+ };
736
+ /**
737
+ * Answer with a `text/event-stream` (status 200) that plays `sse`. Without a
738
+ * final `{ drop: true }` the stream stays open after its last item, like a
739
+ * live server, until the route is removed. `Rong.SSE` in Logic receives the
740
+ * events and, after a drop, reconnects with `Last-Event-ID`, which the next
741
+ * answer of a `sequence` can serve. `delay` (0..=30000 ms) holds the response
742
+ * before it opens.
743
+ */
744
+ export type NetworkRouteSse = {
745
+ sse: NetworkSseItem[];
746
+ headers?: Record<string, string>;
747
+ delay?: number;
748
+ } & NetworkRouteForbid<'status' | 'statusText' | 'contentType' | 'body' | 'json' | 'abort' | 'continue' | 'patchJson' | 'hang' | 'sequence'>;
749
+ /** Exactly one of fulfill, abort, continue, hang, or sse. */
750
+ export type NetworkRouteAnswer = NetworkRouteFulfill | NetworkRouteAbort | NetworkRouteContinue | NetworkRouteHang | NetworkRouteSse;
751
+ /**
752
+ * Answers served in call order: the first matched request gets the first
753
+ * answer, and the last answer repeats. Answers take text or `json` bodies,
754
+ * not bytes. Combine with a pattern's `times` to stop matching.
755
+ */
756
+ export type NetworkRouteSequence = {
757
+ sequence: NetworkRouteAnswer[];
758
+ } & NetworkRouteForbid<NetworkRouteFulfillKey | 'abort' | 'continue' | 'patchJson' | 'hang' | 'sse'>;
759
+ /**
760
+ * One answer, or a `sequence` of them. String values may contain
761
+ * relative-time templates rendered each time the answer is served:
762
+ * `{{now}}`, `{{now-2h}}`, `{{now+30m}}` (ISO-8601 UTC, like
763
+ * `Date.prototype.toISOString`; units `ms`, `s`, `m`, `h`, `d`) and
764
+ * `{{nowMs}}` (epoch milliseconds, as text).
765
+ */
766
+ export type NetworkRouteHandler = NetworkRouteAnswer | NetworkRouteSequence;
767
+ /** `bodyBase64` answers: bytes in a file, instead of `body` or `json`. */
768
+ export type ScenarioHttpBinary = NetworkRouteFulfillFields & {
769
+ bodyBase64: string;
770
+ } & NetworkRouteForbid<'body' | 'json'>;
771
+ /**
772
+ * A `match` value: objects match when every listed key is present and
773
+ * matches (other keys are ignored), arrays match element by element with the
774
+ * same length, a `"/regex/flags"` string (flags among `imsu`) matches a
775
+ * scalar whose text it finds, and any other value matches an equal value.
776
+ */
777
+ export type ScenarioMatchValue = unknown;
778
+ /** Fields every rule may have. */
779
+ type ScenarioRuleCommon = {
780
+ /** Answer this many matching calls, then stand aside. */
781
+ times?: number;
782
+ /** Free text, ignored. */
783
+ note?: string;
784
+ };
785
+ /**
786
+ * A rule for the app's Logic `fetch` / `Rong.SSE`: `http` is
787
+ * `"METHOD url-glob"` (`*` for any method; the URL may be `/regex/flags`),
788
+ * and the answer is a route handler (or `bodyBase64`).
789
+ */
790
+ export type ScenarioHttpRule = ScenarioRuleCommon & {
791
+ http: string;
792
+ function?: never;
793
+ /** Match the request's JSON body. */
794
+ match?: {
795
+ json: ScenarioMatchValue;
796
+ };
797
+ } & (NetworkRouteHandler | ScenarioHttpBinary);
798
+ /** How a `function` rule answers one call. */
799
+ export type ScenarioFunctionAnswer = {
800
+ delay?: number;
801
+ } & ({
802
+ result: unknown;
803
+ error?: never;
804
+ fault?: never;
805
+ } | {
806
+ error: {
807
+ code: string;
808
+ [key: string]: unknown;
809
+ };
810
+ result?: never;
811
+ fault?: never;
812
+ } | {
813
+ fault: 'notRun' | 'unknown';
814
+ result?: never;
815
+ error?: never;
816
+ });
817
+ /**
818
+ * A rule for a Worker Function call, answered by the dev session's
819
+ * companion: `result`, a declared `error`, or a transport `fault`.
820
+ */
821
+ export type ScenarioFunctionRule = ScenarioRuleCommon & {
822
+ function: string;
823
+ http?: never;
824
+ /** Match the call's arguments. */
825
+ match?: {
826
+ args: ScenarioMatchValue;
827
+ };
828
+ } & ((ScenarioFunctionAnswer & {
829
+ sequence?: never;
830
+ }) | {
831
+ sequence: ScenarioFunctionAnswer[];
832
+ result?: never;
833
+ error?: never;
834
+ fault?: never;
835
+ delay?: never;
836
+ });
837
+ export type ScenarioRule = ScenarioHttpRule | ScenarioFunctionRule;
838
+ /**
839
+ * A scenario file: rules tried in file order (the first match answers;
840
+ * nothing matched goes to the real backend), and named variants whose rules
841
+ * go before the shared ones. Unknown fields are rejected.
842
+ */
843
+ export type ScenarioDefinition = {
844
+ $schema?: string;
845
+ name?: string;
846
+ description?: string;
847
+ rules?: ScenarioRule[];
848
+ variants?: Record<string, {
849
+ description?: string;
850
+ rules: ScenarioRule[];
851
+ }>;
852
+ };
853
+ /**
854
+ * What `scenario()` accepts: a typed definition, or an imported JSON file,
855
+ * whose literal types TypeScript widens. Either is validated by the host.
856
+ */
857
+ export type ScenarioInput = ScenarioDefinition | {
858
+ readonly rules: readonly object[];
859
+ readonly [key: string]: unknown;
860
+ } | {
861
+ readonly variants: {
862
+ readonly [variant: string]: {
863
+ readonly rules: readonly object[];
864
+ };
865
+ };
866
+ readonly [key: string]: unknown;
867
+ };
868
+ /** One rule of an installed scenario. */
869
+ export interface ScenarioRuleInfo {
870
+ /** 1-based, in precedence order (the variant's rules first). */
871
+ index: number;
872
+ /** `GET **\/wifi/main`, `function orders.submit`. */
873
+ target: string;
874
+ kind: 'http' | 'function';
875
+ /** Calls it answered (`http` rules); `null` for `function` rules. */
876
+ hits: number | null;
877
+ }
878
+ /** One call that reached a scenario. */
879
+ export interface ScenarioCall {
880
+ /**
881
+ * Increasing and never reused within its log: the scenario's for `http`
882
+ * calls, the dev companion's for `function` calls. Calls in one
883
+ * millisecond differ by it.
884
+ */
885
+ seq: number;
886
+ /** Epoch milliseconds. */
887
+ time: number;
888
+ kind: 'http' | 'function';
889
+ /** `http`: upper-case method and URL. */
890
+ method?: string;
891
+ url?: string;
892
+ /** `http`: the request body, parsed when it is JSON. */
893
+ body?: unknown;
894
+ /** `http`: the answered status, `null` for abort/continue/hang or no answer. */
895
+ status?: number | null;
896
+ /** `function`: its name, arguments, and `result`/`error`/`fault`/`default`. */
897
+ function?: string;
898
+ args?: unknown;
899
+ outcome?: string;
900
+ /** The rule that answered, or `null` when none did. */
901
+ rule: number | null;
902
+ /** `rule 2 (name:variant)`, `real`, or `companion default`. */
903
+ answeredBy: string;
904
+ /** Why no rule matched, when rules targeted the call. */
905
+ noMatch?: string;
906
+ }
907
+ /** `calls()` filter: one rule's target, or its number. */
908
+ export type ScenarioCallFilter = {
909
+ http: string;
910
+ } | {
911
+ function: string;
912
+ } | {
913
+ rule: number;
914
+ };
915
+ /**
916
+ * Calls of one bounded log with a `seq` above the one asked for, oldest
917
+ * first. The logs drop their oldest entries first: `droppedThrough` is the
918
+ * highest `seq` a trim took (0 when none), so a reader whose last `seq` is
919
+ * below it may have missed calls.
920
+ */
921
+ export interface CallWindow<T> {
922
+ calls: T[];
923
+ droppedThrough: number;
924
+ }
925
+ /** `mock.reset()` result. */
926
+ export interface MockResetResult {
927
+ /** The app's new handler generation; `null` when it has no mocks loaded. */
928
+ generation: number | null;
929
+ /**
930
+ * The companion's answer for the run's Functions, `null` when it does not
931
+ * switch mocks.
932
+ */
933
+ function: {
934
+ reset: boolean;
935
+ reason?: string;
936
+ } | null;
937
+ }
938
+ /** `lx.automation().lxapp().mock` in a host run. */
939
+ export interface MockDriver {
940
+ /**
941
+ * Install a scenario file (with one of its variants) for the host run:
942
+ * `http` rules answer Logic `fetch` before the mock selection, `function`
943
+ * rules go to the dev session's companion. Validated as a whole; it
944
+ * replaces the scenario the run installed before (including another app), and the
945
+ * run's end removes it. Routes added with `network.route()` take
946
+ * precedence over it.
947
+ */
948
+ use(definition: ScenarioInput, variant?: string): Promise<Scenario>;
949
+ /**
950
+ * Start the app's mock handler state over: the next intercepted call
951
+ * evaluates `mocks/index.ts` again. A companion that switches mocks is
952
+ * asked to do the same for the run.
953
+ */
954
+ reset(): Promise<MockResetResult>;
955
+ }
956
+ /** Handle returned by `mock.use()`. */
957
+ export interface Scenario {
958
+ readonly name: string | null;
959
+ readonly variant: string | null;
960
+ /** Its rules with their hit counts, read when accessed. */
961
+ readonly rules: ScenarioRuleInfo[];
962
+ /** Calls that reached the scenario since it was installed, oldest first. */
963
+ calls(filter?: ScenarioCallFilter): Promise<ScenarioCall[]>;
964
+ /**
965
+ * Calls to one target with a `seq` above `after`. `droppedThrough` counts
966
+ * the log the target's kind reads (HTTP: the scenario's 200 calls;
967
+ * Function: the companion's log, across every Function).
968
+ */
969
+ callsAfter(target: ScenarioCallFilter, after: number): Promise<CallWindow<ScenarioCall>>;
970
+ /** Remove the scenario; resolves how many rules were still installed. */
971
+ unroute(): Promise<number>;
972
+ }
973
+ /** One request a route handled, as the app sent it. */
974
+ export interface NetworkRouteRequest {
975
+ /**
976
+ * Increasing across the host's request log and never reused: requests in
977
+ * one millisecond differ by it, and a trim leaves a gap.
978
+ */
979
+ seq: number;
980
+ routeId: number;
981
+ /** The route's glob or `/source/flags`. */
982
+ pattern: string;
983
+ /** Upper-case method. */
984
+ method: string;
985
+ url: string;
986
+ /** Request headers with lower-case names. */
987
+ headers: Record<string, string>;
988
+ /**
989
+ * Request body as UTF-8 text, cut to 64 KiB. `null` when there is none or
990
+ * it cannot be read without consuming it: a stream, `Blob`, `FormData`, or
991
+ * the body of a `Request` object passed as `fetch`'s first argument.
992
+ */
993
+ body: string | null;
994
+ /** `body` was cut to the 64 KiB (65536-byte) limit. */
995
+ bodyTruncated: boolean;
996
+ /**
997
+ * `continue` also covers a `patchJson` pass-through; `fulfill` also covers
998
+ * an `sse` answer.
999
+ */
1000
+ action: 'fulfill' | 'abort' | 'continue' | 'hang';
1001
+ /** Fulfilled status (200 for `sse`); `null` for abort/continue/hang. */
1002
+ status: number | null;
1003
+ /** Epoch milliseconds. */
1004
+ timestamp: number;
1005
+ }
1006
+ export interface NetworkRoute {
1007
+ readonly id: number;
1008
+ readonly pattern: string;
1009
+ /** Resolves `false` when the route already expired or was removed. */
1010
+ unroute(): Promise<boolean>;
1011
+ /** Requests this route handled, oldest first. */
1012
+ requests(): Promise<NetworkRouteRequest[]>;
1013
+ /**
1014
+ * Requests this route handled with a `seq` above `after`; `droppedThrough`
1015
+ * is the highest `seq` of this route's requests the host log (1000
1016
+ * entries, 16 MiB of bodies, shared by every app and run) dropped.
1017
+ */
1018
+ requestsAfter(after: number): Promise<{
1019
+ requests: NetworkRouteRequest[];
1020
+ droppedThrough: number;
1021
+ }>;
1022
+ }
1023
+ /**
1024
+ * One Logic `fetch` response recorded by `NetworkDriver.captureResponses()`.
1025
+ * Headers are never recorded, and `url` keeps only scheme, host and path.
1026
+ *
1027
+ * @internal Test-runner plumbing for `lxdev test --openapi`.
1028
+ */
1029
+ export interface NetworkResponseRecord {
1030
+ /** Increasing across the run; pass it as `since` to read only newer ones. */
1031
+ seq: number;
1032
+ /** Upper-case method. */
1033
+ method: string;
1034
+ /** `scheme://host[:port]/path`: userinfo, query and fragment removed. */
1035
+ url: string;
1036
+ /**
1037
+ * `route`: a route fulfilled it. `patch`: the real server answered and a
1038
+ * route merge-patched the body. `network`: the real server answered.
1039
+ */
1040
+ source: 'route' | 'patch' | 'network';
1041
+ /** The route's pattern for `route` and `patch`. */
1042
+ pattern: string | null;
1043
+ status: number;
1044
+ contentType: string | null;
1045
+ /** JSON body text, cut to `maxBodyBytes`; `null` for other content types. */
1046
+ body: string | null;
1047
+ bodyTruncated: boolean;
1048
+ /** Epoch milliseconds. */
1049
+ timestamp: number;
1050
+ }
1051
+ /** @internal `NetworkDriver.captureResponses()` options. */
1052
+ export interface NetworkCaptureOptions {
1053
+ /** Body bytes kept per response, 1..=1048576. Default 262144. */
1054
+ maxBodyBytes?: number;
1055
+ }
1056
+ /**
1057
+ * Test-only routing of the selected lxapp's Logic `fetch` and `Rong.SSE`.
1058
+ * Available only in a host automation run (`lxdev test`); every route is
1059
+ * removed when that run ends. The newest matching route handles a request; unmatched requests are
1060
+ * untouched. A fulfillment never answers a host the app's network policy
1061
+ * refuses — the request goes to the real `fetch`, which rejects it.
1062
+ * WebView page requests are not routed.
1063
+ */
1064
+ export interface NetworkDriver {
1065
+ route(pattern: NetworkRoutePattern, handler: NetworkRouteHandler): Promise<NetworkRoute>;
1066
+ /** Remove every route this run installed for the app; resolves the count. */
1067
+ unrouteAll(): Promise<number>;
1068
+ /**
1069
+ * Run-scoped: requests any route of this automation run handled for the
1070
+ * app, oldest first, across every spec. `t.app.network.calls()` narrows
1071
+ * this to the current spec.
1072
+ */
1073
+ requests(): Promise<NetworkRouteRequest[]>;
1074
+ /**
1075
+ * Record the app's Logic `fetch` responses — status, content type and
1076
+ * JSON body, never headers — until the run ends. A buffered body is read
1077
+ * from a clone without delaying the app's fetch; a streamed one (no or a
1078
+ * large Content-Length) is read once and the app gets an equivalent
1079
+ * buffered `Response`.
1080
+ *
1081
+ * @internal Test-runner plumbing for `lxdev test --openapi`.
1082
+ */
1083
+ captureResponses(options?: NetworkCaptureOptions): Promise<void>;
1084
+ /**
1085
+ * Responses captured for the app in this run, oldest first; `since` skips
1086
+ * records up to that `seq`. `waitMs` (0..3000) first waits for captures
1087
+ * already in flight; one still unfinished is returned by a later read.
1088
+ * Rejects once when unread responses were lost. At most 500 records and
1089
+ * 8 MiB of bodies are kept; the oldest go first.
1090
+ *
1091
+ * @internal Test-runner plumbing for `lxdev test --openapi`.
1092
+ */
1093
+ responses(options?: {
1094
+ since?: number;
1095
+ waitMs?: number;
1096
+ }): Promise<NetworkResponseRecord[]>;
1097
+ }
1098
+ /**
1099
+ * Checkpoint and roll back the isolated data profile of a host automation run
1100
+ * (`lxdev test --profile`). Each call rejects with `E_PROFILE_NOT_ISOLATED`
1101
+ * unless the selected lxapp runs on this run's profile, so it can never touch
1102
+ * the app's real data.
1103
+ *
1104
+ * `checkpoint` and `restore` close the app, copy or swap its closed data and
1105
+ * reopen it at its initial page: a driver selected before the call is bound to
1106
+ * the closed instance, so select the lxapp again afterwards.
1107
+ */
1108
+ export interface ProfileDriver {
1109
+ /** Snapshot the profile; resolves the checkpoint id. */
1110
+ checkpoint(): Promise<string>;
1111
+ /**
1112
+ * Replace the profile with checkpoint `id`. With `keep`, the storage keys
1113
+ * those globs match keep their current values across the rollback.
1114
+ */
1115
+ restore(id: string, options?: ProfileRestoreOptions): Promise<ProfileRestoreResult>;
1116
+ /** Discard checkpoint `id`; the app keeps running. */
1117
+ drop(id: string): Promise<void>;
1118
+ }
1119
+ /** `ProfileDriver.restore` options. */
1120
+ export interface ProfileRestoreOptions {
1121
+ /**
1122
+ * `lx.getStorage()` keys whose current state survives the rollback: globs
1123
+ * over the whole key, `*` any run of characters (dots included), `?` one
1124
+ * character, anything else literal (at most 64 patterns). A matching key
1125
+ * keeps its current value, one added since the checkpoint stays, and one
1126
+ * deleted since stays deleted. Files (`lx://userdata`, …) always roll back.
1127
+ */
1128
+ keep?: string[];
1129
+ }
1130
+ export interface ProfileRestoreResult {
1131
+ /** Kept keys that currently exist and were carried into the restored data. */
1132
+ kept: string[];
1133
+ }
1134
+ /** A time for the test clock: epoch milliseconds, a `Date.parse` string, or a `Date`. */
1135
+ export type ClockTime = number | string | Date;
1136
+ export interface ClockInstallOptions {
1137
+ /** What Logic's `Date` reads at install. Default: the real current time. */
1138
+ now?: ClockTime;
1139
+ }
1140
+ export interface ClockRunAllOptions {
1141
+ /** Reject once this many timers fired and more are pending. Default 1000. */
1142
+ maxTimers?: number;
1143
+ }
1144
+ /** Logic's test time after a clock call. */
1145
+ export interface ClockState {
1146
+ /** Logic's `Date.now()` afterwards. */
1147
+ now: number;
1148
+ /** Timers still scheduled on the test clock. */
1149
+ pending: number;
1150
+ }
1151
+ /** What `tick` / `runAll` did. */
1152
+ export interface ClockAdvance extends ClockState {
1153
+ /** Timers fired by this call. */
1154
+ fired: number;
1155
+ }
1156
+ export interface ClockUninstallResult {
1157
+ /** `false` when no clock was installed (never, or the app reopened since). */
1158
+ uninstalled: boolean;
1159
+ /** Timers still pending on the test clock; they are discarded, never fired. */
1160
+ dropped: number;
1161
+ }
1162
+ /**
1163
+ * Test clock for the selected lxapp's Logic context, scoped to the host
1164
+ * automation run (`lxdev test`); outside one every call rejects with
1165
+ * `E_AUTOMATION`. While installed, Logic's `Date` (no-argument
1166
+ * construction, `Date()`, `Date.now()`), `setTimeout` / `setInterval` and
1167
+ * their `clear*`, and `performance.now()` read test time, and those timers
1168
+ * fire only from `tick` / `runAll`, in time order. The page WebView, native
1169
+ * work, real network requests, test route `delay` / `hang` and timers started
1170
+ * before `install` keep real time. The run's end, or the app reopening,
1171
+ * returns the app to real time.
1172
+ */
1173
+ export interface ClockDriver {
1174
+ /**
1175
+ * Put Logic on test time; resolves its state (`pending` is 0: timers
1176
+ * started before `install` keep real time). Rejects with
1177
+ * `E_CLOCK_INSTALLED` when a clock is already installed.
1178
+ */
1179
+ install(options?: ClockInstallOptions): Promise<ClockState>;
1180
+ /**
1181
+ * Advance by `ms`, firing each timer due on the way at its own time. After
1182
+ * every firing, promise chains the callback started settle before the next
1183
+ * timer fires. Rejects with `E_CLOCK_NOT_INSTALLED` without a clock.
1184
+ */
1185
+ tick(ms: number): Promise<ClockAdvance>;
1186
+ /**
1187
+ * Fire timers, including those they schedule, until none is left; rejects
1188
+ * once `maxTimers` fired (an interval never lets the queue empty).
1189
+ */
1190
+ runAll(options?: ClockRunAllOptions): Promise<ClockAdvance>;
1191
+ /**
1192
+ * Change what `Date` reads without firing timers; timer due times and
1193
+ * `performance.now()` are unaffected. Resolves the new state.
1194
+ */
1195
+ setSystemTime(time: ClockTime): Promise<ClockState>;
1196
+ /** Return Logic to real time; pending test timers are dropped. */
1197
+ uninstall(): Promise<ClockUninstallResult>;
1198
+ }
1199
+ /** A toast Logic presented while watched; the host drew it as usual. */
1200
+ export interface ToastRecord {
1201
+ title: string;
1202
+ /** `success` / `error` / `loading` / `none`. */
1203
+ icon: string;
1204
+ /** Milliseconds the toast asked to stay. */
1205
+ duration: number;
1206
+ /** Epoch milliseconds it was presented. */
1207
+ at: number;
1208
+ }
1209
+ export interface ModalAnswer {
1210
+ confirm: boolean;
1211
+ }
1212
+ /**
1213
+ * A modal Logic opened while watched: drawn (`drawn: true`) until the spec
1214
+ * queued a modal answer, answered from the queue after.
1215
+ */
1216
+ export interface ModalRecord {
1217
+ title: string;
1218
+ content: string;
1219
+ /** Only when the app set it (the default is localized). */
1220
+ confirmText?: string;
1221
+ /** Only when the modal has a cancel button and the app set its text. */
1222
+ cancelText?: string;
1223
+ /**
1224
+ * The queued answer it got, or the user's choice when drawn. `null`: a
1225
+ * drawn one still open or that failed to present, or (answering) no answer
1226
+ * was queued, so the call rejected and the spec failed.
1227
+ */
1228
+ answer: ModalAnswer | null;
1229
+ /** Drawn for the user, not answered from the queue. */
1230
+ drawn?: true;
1231
+ }
1232
+ /** `{ index }` picks that item; `{ cancel: true }` dismisses the sheet. */
1233
+ export type ActionSheetAnswer = {
1234
+ index: number;
1235
+ } | {
1236
+ cancel: true;
1237
+ };
1238
+ /** An `lx.showActionSheet` while watched: drawn, or answered like modals. */
1239
+ export interface ActionSheetRecord {
1240
+ /** Item labels, in order. */
1241
+ items: string[];
1242
+ answer: ActionSheetAnswer | null;
1243
+ drawn?: true;
1244
+ }
1245
+ export interface DialogUnwatchResult {
1246
+ /** Modal answers queued that no modal used. */
1247
+ modalAnswers: number;
1248
+ /** Action sheet answers queued that no sheet used. */
1249
+ actionSheetAnswers: number;
1250
+ }
1251
+ /**
1252
+ * The dialogs the selected lxapp's Logic opens while a spec of a host
1253
+ * automation run (`lxdev test`) watches it; outside one every call rejects
1254
+ * with `E_AUTOMATION`. Toasts, modals (`showModal`, `alert`, `confirm`) and
1255
+ * `showActionSheet` are recorded and drawn. A queued answer applies once;
1256
+ * later dialogs are drawn unless strict mode explicitly requires an answer.
1257
+ * An unwatched app presents every dialog as usual.
1258
+ */
1259
+ export interface DialogDriver {
1260
+ /** Start watching for the open spec attempt, with nothing recorded or queued. */
1261
+ watch(): void;
1262
+ /** Stop watching; returns the queued answers no dialog used. */
1263
+ unwatch(): DialogUnwatchResult;
1264
+ /** The first dialog that found no answer, or `null` once the watch ends. */
1265
+ unanswered(): Promise<string | null>;
1266
+ toasts(): ToastRecord[];
1267
+ modals(): ModalRecord[];
1268
+ actionSheets(): ActionSheetRecord[];
1269
+ answerNextModal(answer: ModalAnswer): void;
1270
+ answerNextActionSheet(answer: ActionSheetAnswer): void;
1271
+ /** Strict mode refuses an unqueued dialog; returns the previous modes. */
1272
+ setAnswerMode(mode: {
1273
+ modals?: "draw" | "strict";
1274
+ actionSheets?: "draw" | "strict";
1275
+ }): {
1276
+ modals: "draw" | "strict";
1277
+ actionSheets: "draw" | "strict";
1278
+ };
1279
+ }
1280
+ /** Capability for one selected running lxapp, as app Logic sees it. */
1281
+ export interface LogicLxAppDriver {
484
1282
  readonly page: PageDriver;
485
- readonly nav: NavDriver;
1283
+ readonly nav: LogicNavDriver;
486
1284
  /** Complete runtime snapshot of the selected lxapp. */
487
1285
  info(): Promise<LxAppRuntimeInfo>;
488
1286
  /** Configured pages of the selected lxapp. */
489
1287
  pages(): Promise<LxAppPageConfig[]>;
490
1288
  /** Authoritative host surface render plan, for end-to-end assertions. */
491
1289
  surfaceLayout(): Promise<SurfaceLayoutSnapshot>;
492
- /** Logic-runtime eval; self-eval from that Logic runtime is rejected. */
493
- eval(options: LxAppEvalOptions): Promise<unknown>;
1290
+ /**
1291
+ * Logic-runtime eval of another lxapp; evaluating the calling Logic runtime
1292
+ * itself rejects. `T` describes the expected JSON result; it is not
1293
+ * runtime validation.
1294
+ */
1295
+ eval<T = unknown>(options: LogicLxAppEvalOptions): Promise<T>;
1296
+ }
1297
+ /**
1298
+ * Capability for one selected running lxapp in a host automation run
1299
+ * (`HostRunAutomation.lxapp()`): the Logic driver plus test-run-only members.
1300
+ */
1301
+ export interface LxAppDriver extends LogicLxAppDriver {
1302
+ readonly nav: NavDriver;
1303
+ /**
1304
+ * Test-only Logic `fetch` routing, scoped to the host automation run.
1305
+ *
1306
+ * @remarks Reading the property always works, but every call rejects with
1307
+ * `E_AUTOMATION` outside a host test run (`lxdev test`) — from app Logic,
1308
+ * or in a host built without the automation runtime.
1309
+ */
1310
+ readonly network: NetworkDriver;
1311
+ /**
1312
+ * The app's mocks in the host run: scenario states on top of the mock
1313
+ * selection, and a fresh handler state.
1314
+ *
1315
+ * @remarks Reading the property always works, but every call rejects with
1316
+ * `E_AUTOMATION` outside a host test run (`lxdev test`).
1317
+ */
1318
+ readonly mock: MockDriver;
1319
+ /**
1320
+ * Isolated data profile rollback, scoped to the host automation run.
1321
+ *
1322
+ * @remarks Reading the property always works; every call rejects with
1323
+ * `E_PROFILE_NOT_ISOLATED` outside an isolated run.
1324
+ */
1325
+ readonly profile: ProfileDriver;
1326
+ /**
1327
+ * Test clock for the selected lxapp's Logic, scoped to the host automation
1328
+ * run.
1329
+ *
1330
+ * @remarks Reading the property always works, but every call rejects with
1331
+ * `E_AUTOMATION` outside a host test run (`lxdev test`).
1332
+ */
1333
+ readonly clock: ClockDriver;
1334
+ /**
1335
+ * The dialogs this lxapp's Logic opens while a spec watches it.
1336
+ *
1337
+ * @remarks Reading the property always works, but every call rejects with
1338
+ * `E_AUTOMATION` outside a host test run (`lxdev test`).
1339
+ */
1340
+ readonly dialogs: DialogDriver;
1341
+ /** @internal Test-runner plumbing: resolves to the call-trace envelope. */
1342
+ eval<T = unknown>(options: LxAppEvalOptions & {
1343
+ captureCalls: true;
1344
+ }): Promise<LxAppEvalTrace<T>>;
1345
+ /**
1346
+ * Logic-runtime eval; evaluating the calling Logic runtime itself rejects.
1347
+ * `T` describes the expected JSON result; it is not runtime validation.
1348
+ */
1349
+ eval<T = unknown>(options: LxAppEvalOptions & {
1350
+ captureCalls?: false;
1351
+ }): Promise<T>;
1352
+ eval<T = unknown>(options: LxAppEvalOptions): Promise<T | LxAppEvalTrace<T>>;
494
1353
  }
495
1354
  /** One configured page in a runtime info payload. */
496
1355
  export interface LxAppPageEntry {
497
1356
  name: string;
498
1357
  path: string;
499
1358
  }
500
- /** Runtime snapshot of a running lxapp (raw payload, snake_case keys). */
1359
+ /** Runtime snapshot of a running lxapp. */
501
1360
  export interface LxAppRuntimeInfo {
502
- appid: string;
503
- app_name: string;
1361
+ appId: string;
1362
+ appName: string;
504
1363
  version: string;
505
- release_type: string;
506
- session_id: number;
1364
+ releaseType: string;
1365
+ sessionId: number;
507
1366
  status: string;
508
1367
  /** True while the lxapp holds a place in the host's page stack. */
509
- in_stack: boolean;
510
- is_home: boolean;
511
- current_page: string | null;
512
- initial_route: string;
513
- pages_count: number;
514
- page_entries: LxAppPageEntry[];
515
- page_stack: string[];
516
- tab_bar: LxAppRuntimeTabBarInfo | null;
517
- navigation_bar: LxAppRuntimeNavigationBarInfo | null;
518
- lxapp_dir: string;
519
- data_dir: string;
520
- cache_dir: string;
1368
+ inStack: boolean;
1369
+ isHome: boolean;
1370
+ currentPage: string | null;
1371
+ initialRoute: string;
1372
+ pagesCount: number;
1373
+ pageEntries: LxAppPageEntry[];
1374
+ pageStack: string[];
1375
+ tabBar: LxAppRuntimeTabBarInfo | null;
1376
+ navigationBar: LxAppRuntimeNavigationBarInfo | null;
1377
+ lxappDir: string;
1378
+ dataDir: string;
1379
+ cacheDir: string;
1380
+ /** Supported features keyed by Logic context id. */
1381
+ logicFeatures: Record<string, string[]>;
521
1382
  }
522
1383
  /** Runtime NavigationBar state exposed for deterministic host-level assertions. */
523
1384
  export interface LxAppRuntimeNavigationBarInfo {
524
1385
  title: string;
525
- home_button: 'auto' | 'hidden';
526
- home_button_visible: boolean;
527
- runtime_style: {
528
- background_color: string | null;
529
- foreground_color: string | null;
530
- divider_color: string | null;
1386
+ homeButton: 'auto' | 'hidden';
1387
+ homeButtonVisible: boolean;
1388
+ runtimeStyle: {
1389
+ backgroundColor: string | null;
1390
+ foregroundColor: string | null;
1391
+ dividerColor: string | null;
531
1392
  };
532
1393
  }
533
1394
  /** Runtime TabBar state exposed for deterministic host-level assertions. */
534
1395
  export interface LxAppRuntimeTabBarInfo {
535
1396
  presentation: 'standard' | 'immersive';
536
1397
  visibility: 'auto' | 'visible' | 'hidden';
537
- route_visible: boolean;
538
- effective_visible: boolean;
539
- selected_index: number;
540
- runtime_style: {
541
- foreground_color: string | null;
542
- selected_foreground_color: string | null;
543
- };
1398
+ routeVisible: boolean;
1399
+ effectiveVisible: boolean;
1400
+ selectedIndex: number;
544
1401
  items: Array<{
545
1402
  index: number;
546
1403
  text: string | null;
547
- icon_path: string | null;
1404
+ iconPath: string | null;
548
1405
  badge: string | null;
549
- red_dot: boolean;
1406
+ redDot: boolean;
550
1407
  }>;
551
1408
  }
552
1409
  /** Selects a running lxapp by id; defaults to the current app. */
@@ -555,13 +1412,13 @@ export interface LxAppRef {
555
1412
  app?: string;
556
1413
  }
557
1414
  export interface LxAppOpenOptions {
558
- appid: string;
1415
+ appId: string;
559
1416
  /** Initial page/path. */
560
1417
  path?: string;
561
1418
  channel?: 'release' | 'draft';
562
1419
  }
563
1420
  export interface LxAppOpenResult {
564
- appid: string;
1421
+ appId: string;
565
1422
  path: string;
566
1423
  }
567
1424
  export interface ApplinkOptions {
@@ -573,17 +1430,18 @@ export interface ApplinkResult {
573
1430
  code: number;
574
1431
  }
575
1432
  /**
576
- * Cross-lxapp lifecycle and host-window access. Requires `host` outside dev.
1433
+ * Cross-lxapp lifecycle and host-window access. Requires the `host` privilege
1434
+ * from app Logic; a host automation run needs no grant.
577
1435
  *
578
1436
  * `close`, `restart`, and `uninstall` reject when they target the calling app
579
- * itself. Use `lx.app.exit()` to self-exit.
1437
+ * itself. Use `lx.host.exit()` to self-exit.
580
1438
  */
581
1439
  export interface LxAppManager {
582
1440
  list(): Promise<LxAppRuntimeInfo[]>;
583
1441
  current(): Promise<LxAppSummary>;
584
1442
  open(options: LxAppOpenOptions): Promise<LxAppOpenResult>;
585
1443
  /**
586
- * Inject an App Link (`lxdev app applink`). Warm `onShow`, `scene === 8003`.
1444
+ * Inject an App Link (`lxdev host applink`). Warm `onShow`, `scene === 8003`.
587
1445
  * Host must match `appLinks.hosts`. Resolves when accepted, not when
588
1446
  * navigation finishes.
589
1447
  */
@@ -622,9 +1480,6 @@ export interface DeviceState {
622
1480
  landscape: boolean;
623
1481
  /** Simulated system appearance of the device screen. */
624
1482
  appearance: "system" | "light" | "dark";
625
- /** Whether the simulated host capsule is enabled (the setting, not
626
- * per-device visibility: desktop presets draw no phone chrome either way). */
627
- capsule: boolean;
628
1483
  }
629
1484
  export interface DeviceSetOptions {
630
1485
  /** Device preset id (see `list()`); omit to keep the current device. */
@@ -634,8 +1489,6 @@ export interface DeviceSetOptions {
634
1489
  landscape?: boolean;
635
1490
  /** Simulated appearance; omit to keep. */
636
1491
  appearance?: "system" | "light" | "dark";
637
- /** Show (`true`) or hide (`false`) the simulated host capsule; omit to keep. */
638
- capsule?: boolean;
639
1492
  }
640
1493
  /**
641
1494
  * Simulated-device control (`lxdev runner`). Only functional in a host
@@ -699,9 +1552,9 @@ export interface BrowserScrollOptions extends BrowserTabRef {
699
1552
  * A browser wait condition — pass **exactly one** of the condition fields.
700
1553
  * `navigation` may add `complete` to wait for load completion.
701
1554
  */
702
- export interface BrowserWaitOptions extends BrowserTabRef {
1555
+ interface BrowserWaitFields extends BrowserTabRef {
703
1556
  /** Wait for page load. */
704
- loaded?: boolean;
1557
+ loaded?: true;
705
1558
  /** Wait for a selector to exist. */
706
1559
  exists?: string;
707
1560
  /** Wait for a selector to be visible. */
@@ -717,7 +1570,7 @@ export interface BrowserWaitOptions extends BrowserTabRef {
717
1570
  /** Wait for the URL to contain this. */
718
1571
  urlContains?: string;
719
1572
  /** Wait for a navigation. */
720
- navigation?: boolean;
1573
+ navigation?: true;
721
1574
  /** With `navigation`: baseline URL to detect a change from (default: any
722
1575
  * navigation satisfies it). */
723
1576
  fromUrl?: string;
@@ -726,6 +1579,33 @@ export interface BrowserWaitOptions extends BrowserTabRef {
726
1579
  /** Timeout in ms (default 10000, capped at 60000). */
727
1580
  timeoutMs?: number;
728
1581
  }
1582
+ type BrowserWaitConditionKey = 'loaded' | 'exists' | 'visible' | 'hidden' | 'editable' | 'js' | 'url' | 'urlContains' | 'navigation';
1583
+ /** Exactly one condition; ambiguous and empty waits are rejected by the runtime. */
1584
+ export type BrowserWaitOptions = Pick<BrowserWaitFields, 'tab' | 'timeoutMs' | 'fromUrl' | 'complete'> & {
1585
+ [K in BrowserWaitConditionKey]: Required<Pick<BrowserWaitFields, K>> & Partial<Record<Exclude<BrowserWaitConditionKey, K>, never>>;
1586
+ }[BrowserWaitConditionKey];
1587
+ /** Browser query payload; it does not carry lxapp-only identity/index fields. */
1588
+ export interface BrowserElementInfo {
1589
+ exists: boolean;
1590
+ visible: boolean;
1591
+ enabled: boolean;
1592
+ editable: boolean;
1593
+ text?: string;
1594
+ textTruncated?: boolean;
1595
+ value?: string;
1596
+ valueTruncated?: boolean;
1597
+ rect?: ElementRect;
1598
+ }
1599
+ export interface BrowserWaitResult {
1600
+ elapsed_ms: number;
1601
+ current_url?: string;
1602
+ element?: BrowserElementInfo;
1603
+ value?: unknown;
1604
+ }
1605
+ export interface BrowserEvalResult<T = unknown> {
1606
+ value: T;
1607
+ navigation: BrowserWaitResult;
1608
+ }
729
1609
  export interface BrowserTab {
730
1610
  tab_id: string;
731
1611
  path: string;
@@ -791,16 +1671,34 @@ export interface BrowserDriver {
791
1671
  back(options?: BrowserTabRef): Promise<void>;
792
1672
  forward(options?: BrowserTabRef): Promise<void>;
793
1673
  /** Evaluate JS; with `waitNavigation` resolves to `{ value, navigation }`. */
794
- eval(options: BrowserEvalOptions): Promise<unknown>;
795
- query(options: BrowserQueryOptions): Promise<PageElement | PageElementMiss>;
1674
+ eval<T = unknown>(options: BrowserEvalOptions & {
1675
+ waitNavigation: true;
1676
+ }): Promise<BrowserEvalResult<T>>;
1677
+ eval<T = unknown>(options: BrowserEvalOptions & {
1678
+ waitNavigation?: false;
1679
+ }): Promise<T>;
1680
+ eval<T = unknown>(options: BrowserEvalOptions): Promise<T | BrowserEvalResult<T>>;
1681
+ query(options: BrowserQueryOptions): Promise<BrowserElementInfo>;
796
1682
  /** Wait for a condition (pass exactly one condition field). */
797
- wait(options: BrowserWaitOptions): Promise<unknown>;
1683
+ wait(options: BrowserWaitOptions): Promise<BrowserWaitResult>;
798
1684
  /** Click; with `waitNavigation` resolves to the navigation payload else `null`. */
799
- click(options: BrowserClickOptions): Promise<unknown>;
1685
+ click(options: BrowserClickOptions & {
1686
+ waitNavigation: true;
1687
+ }): Promise<BrowserWaitResult>;
1688
+ click(options: BrowserClickOptions & {
1689
+ waitNavigation?: false;
1690
+ }): Promise<null>;
1691
+ click(options: BrowserClickOptions): Promise<BrowserWaitResult | null>;
800
1692
  type(options: BrowserTypeOptions): Promise<void>;
801
1693
  fill(options: BrowserTypeOptions): Promise<void>;
802
1694
  /** Press; with `waitNavigation` resolves to the navigation payload else `null`. */
803
- press(options: BrowserPressOptions): Promise<unknown>;
1695
+ press(options: BrowserPressOptions & {
1696
+ waitNavigation: true;
1697
+ }): Promise<BrowserWaitResult>;
1698
+ press(options: BrowserPressOptions & {
1699
+ waitNavigation?: false;
1700
+ }): Promise<null>;
1701
+ press(options: BrowserPressOptions): Promise<BrowserWaitResult | null>;
804
1702
  scroll(options: BrowserScrollOptions): Promise<void>;
805
1703
  scrollTo(options: BrowserSelectorOptions): Promise<void>;
806
1704
  screenshot(options?: BrowserTabRef): Promise<Screenshot>;
@@ -1005,10 +1903,13 @@ export interface DesktopLaunchResult {
1005
1903
  * `match` (query `text | title: | class: | process: | pid:`, must resolve to
1006
1904
  * exactly one window).
1007
1905
  */
1008
- export interface DesktopWindowSel {
1009
- window?: string;
1010
- match?: string;
1011
- }
1906
+ export type DesktopWindowSel = {
1907
+ window: string;
1908
+ match?: never;
1909
+ } | {
1910
+ window?: never;
1911
+ match: string;
1912
+ };
1012
1913
  export interface DesktopWindowsOptions {
1013
1914
  /** Match query (`text | title: | class: | process: | pid:`). */
1014
1915
  match?: string;
@@ -1084,21 +1985,21 @@ export interface DesktopKey {
1084
1985
  down(options: DesktopKeyNameOptions): Promise<DesktopAck>;
1085
1986
  up(options: DesktopKeyNameOptions): Promise<DesktopAck>;
1086
1987
  }
1087
- export interface DesktopWindowMoveOptions extends DesktopWindowSel {
1988
+ export type DesktopWindowMoveOptions = DesktopWindowSel & {
1088
1989
  /** Target position as `[x, y]` in desktop coordinates. */
1089
1990
  to: Point;
1090
- }
1091
- export interface DesktopWindowResizeOptions extends DesktopWindowSel {
1991
+ };
1992
+ export type DesktopWindowResizeOptions = DesktopWindowSel & {
1092
1993
  width: number;
1093
1994
  height: number;
1094
- }
1095
- export interface DesktopWindowMoveDisplayOptions extends DesktopWindowSel {
1995
+ };
1996
+ export type DesktopWindowMoveDisplayOptions = DesktopWindowSel & {
1096
1997
  /** Display id from `displays()`. */
1097
1998
  display: string;
1098
- }
1099
- export interface DesktopWindowAlwaysOnTopOptions extends DesktopWindowSel {
1999
+ };
2000
+ export type DesktopWindowAlwaysOnTopOptions = DesktopWindowSel & {
1100
2001
  on: boolean;
1101
- }
2002
+ };
1102
2003
  /** Window management; every verb resolves to the resulting window state. */
1103
2004
  export interface DesktopWindowDriver {
1104
2005
  status(options: DesktopWindowSel): Promise<DesktopWindowInfo>;
@@ -1198,13 +2099,22 @@ export interface DesktopAppLaunchOptions {
1198
2099
  timeoutMs?: number;
1199
2100
  }
1200
2101
  /** Quit target — exactly one of `match` / `pid` / `window`. */
1201
- export interface DesktopAppQuitOptions {
1202
- match?: string;
1203
- pid?: number;
1204
- window?: string;
2102
+ export type DesktopAppQuitOptions = ({
2103
+ match: string;
2104
+ pid?: never;
2105
+ window?: never;
2106
+ } | {
2107
+ match?: never;
2108
+ pid: number;
2109
+ window?: never;
2110
+ } | {
2111
+ match?: never;
2112
+ pid?: never;
2113
+ window: string;
2114
+ }) & {
1205
2115
  /** Terminate instead of a graceful close. */
1206
2116
  force?: boolean;
1207
- }
2117
+ };
1208
2118
  /** App lifecycle. */
1209
2119
  export interface DesktopApp {
1210
2120
  launch(options: DesktopAppLaunchOptions): Promise<DesktopLaunchResult>;
@@ -1258,4 +2168,5 @@ export interface DesktopDriver {
1258
2168
  readonly app: DesktopApp;
1259
2169
  readonly process: DesktopProcess;
1260
2170
  }
2171
+ export {};
1261
2172
  //# sourceMappingURL=index.d.ts.map