@lingxia/types 0.11.1 → 0.13.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.
@@ -9,6 +9,8 @@
9
9
  * product-side API.
10
10
  */
11
11
 
12
+ import type { TerminalSettingsValue } from '../generated/logic.js';
13
+
12
14
  // ============================ factory ============================
13
15
 
14
16
  /** Stable automation root; it grants no capability until one is selected. */
@@ -21,6 +23,8 @@ export interface Automation {
21
23
  readonly lxapps: LxAppManager;
22
24
  /** The host app's browser tabs. */
23
25
  readonly browser: BrowserDriver;
26
+ /** Persisted host-shell shortcuts for deterministic test setup/assertion. */
27
+ readonly shell: ShellDriver;
24
28
  /** Simulated-device selection in a host runner. */
25
29
  readonly device: DeviceDriver;
26
30
  /**
@@ -29,6 +33,141 @@ export interface Automation {
29
33
  * Runner) on top of the `host` privilege. Windows/macOS only.
30
34
  */
31
35
  readonly desktop: DesktopDriver;
36
+ /** Native terminal workspace state and pane actions in trusted dev/test hosts. */
37
+ readonly terminal: TerminalDriver;
38
+ }
39
+
40
+ // ========================== terminal tier ==========================
41
+
42
+ export type TerminalSplitDirection = 'left' | 'right' | 'up' | 'down';
43
+
44
+ export interface TerminalSurfaceRef {
45
+ /** Stable id returned by `lx.shell.openDeclared('terminal', { key })`. */
46
+ surface: string;
47
+ }
48
+
49
+ export interface TerminalPaneSnapshot {
50
+ paneId: string;
51
+ active: boolean;
52
+ visible: boolean;
53
+ frame: { x: number; y: number; width: number; height: number };
54
+ grid: {
55
+ cols: number;
56
+ rows: number;
57
+ generation: number;
58
+ imageGeneration: number;
59
+ imageCount: number;
60
+ imagePlacementCount: number;
61
+ defaultForeground: number;
62
+ defaultBackground: number;
63
+ cursorRow: number;
64
+ cursorCol: number;
65
+ cursorVisible: boolean;
66
+ cursorStyle: 'block' | 'bar' | 'underline' | 'block-hollow';
67
+ };
68
+ }
69
+
70
+ export type TerminalPaneTree =
71
+ | { kind: 'leaf'; pane: TerminalPaneSnapshot }
72
+ | {
73
+ kind: 'split';
74
+ /** `horizontal` places children left/right; `vertical` stacks them. */
75
+ axis: 'horizontal' | 'vertical';
76
+ children: TerminalPaneTree[];
77
+ };
78
+
79
+ export interface TerminalTabSnapshot {
80
+ id: string;
81
+ active: boolean;
82
+ activePaneId?: string;
83
+ paneCount: number;
84
+ tree?: TerminalPaneTree;
85
+ }
86
+
87
+ /** Semantic state published by the native terminal host after layout. */
88
+ export interface TerminalWorkspaceSnapshot {
89
+ surfaceId: string;
90
+ presentation: 'main' | 'aside';
91
+ visible: boolean;
92
+ /** Expanded to the full content area rather than its docked size. */
93
+ maximized: boolean;
94
+ activeTabId?: string;
95
+ tabCount: number;
96
+ paneCount: number;
97
+ configGeneration: number;
98
+ visualGeneration: number;
99
+ config: TerminalSettingsValue;
100
+ chrome: {
101
+ surface: string;
102
+ header: string;
103
+ separator: string;
104
+ text: string;
105
+ textMuted: string;
106
+ cursor: string;
107
+ selectionBackground: string;
108
+ selectionForeground: string;
109
+ };
110
+ tabs: TerminalTabSnapshot[];
111
+ }
112
+
113
+ export interface TerminalSplitOptions extends TerminalSurfaceRef {
114
+ direction: TerminalSplitDirection;
115
+ }
116
+
117
+ /** Native terminal automation; available only to trusted dev/test hosts. */
118
+ export interface TerminalDriver {
119
+ snapshot(options: TerminalSurfaceRef): Promise<TerminalWorkspaceSnapshot>;
120
+ /** Send text to the focused pane, as if typed into its PTY. */
121
+ input(options: TerminalInputOptions): Promise<TerminalWorkspaceSnapshot>;
122
+ split(options: TerminalSplitOptions): Promise<TerminalWorkspaceSnapshot>;
123
+ /** Open a tab and activate it. Resolves with the snapshot that follows. */
124
+ newTab(options: TerminalSurfaceRef): Promise<TerminalWorkspaceSnapshot>;
125
+ /** Expand to the full content area, or return to the docked size. */
126
+ setMaximized(options: TerminalMaximizeOptions): Promise<TerminalWorkspaceSnapshot>;
127
+ }
128
+
129
+ export interface TerminalInputOptions extends TerminalSurfaceRef {
130
+ text: string;
131
+ }
132
+
133
+ export interface TerminalMaximizeOptions extends TerminalSurfaceRef {
134
+ maximized: boolean;
135
+ }
136
+
137
+ // ============================ shell tier ============================
138
+
139
+ /** One ordered shortcut in the host shell's persisted Pin collection. */
140
+ export type AutomationShellPin =
141
+ | {
142
+ /** A host workspace shortcut; activation opens/focuses the app as main. */
143
+ kind: 'lxapp';
144
+ /** Installed lxapp id. This is not a page name or route. */
145
+ key: string;
146
+ }
147
+ | {
148
+ /** A website shortcut opened/focused as a main browser tab. */
149
+ kind: 'bookmark';
150
+ /** Persisted bookmark id. */
151
+ key: string;
152
+ };
153
+
154
+ export type SetAutomationShellPinOptions = AutomationShellPin & {
155
+ pinned: boolean;
156
+ };
157
+
158
+ /**
159
+ * Host-shell state for test setup and end-to-end assertions. This does not
160
+ * customize an lxapp's production shell; use `lx.shell` for app behavior.
161
+ */
162
+ export interface ShellDriver {
163
+ /** Ordered shortcuts exactly as projected into the host sidebar. */
164
+ pins(): Promise<AutomationShellPin[]>;
165
+ /**
166
+ * Idempotently persist or remove one shortcut. New Pins append to the
167
+ * existing order; adding beyond the host limit rejects without mutation.
168
+ * Returns the resulting complete order.
169
+ */
170
+ setPin(options: SetAutomationShellPinOptions): Promise<AutomationShellPin[]>;
32
171
  }
33
172
 
34
173
  // ============================ page tier ============================
@@ -89,7 +228,17 @@ export interface PageScrollToOptions extends PageTarget {
89
228
  css: string;
90
229
  }
91
230
 
92
- export type PageWaitState = 'exists' | 'visible' | 'gone';
231
+ export type PageWaitState =
232
+ | 'attached'
233
+ | 'detached'
234
+ | 'visible'
235
+ | 'hidden'
236
+ | 'enabled'
237
+ | 'editable'
238
+ /** @deprecated Use `attached`. */
239
+ | 'exists'
240
+ /** @deprecated Use `detached`. */
241
+ | 'gone';
93
242
 
94
243
  export interface PageWaitForOptions extends PageTarget {
95
244
  css: string;
@@ -99,7 +248,12 @@ export interface PageWaitForOptions extends PageTarget {
99
248
  timeoutMs?: number;
100
249
  }
101
250
 
102
- /** An element's viewport rectangle (viewport-relative CSS pixels). */
251
+ /**
252
+ * An element's viewport rectangle (viewport-relative CSS pixels).
253
+ * Windows LingXia WebViews use a 1:1 CSS-to-child-window rasterization scale,
254
+ * so combine this with that WebView's desktop bounds without multiplying by
255
+ * `DesktopWindowInfo.scale`.
256
+ */
103
257
  export interface ElementRect {
104
258
  left: number;
105
259
  top: number;
@@ -257,6 +411,103 @@ export interface LxAppEvalOptions {
257
411
  timeoutMs?: number;
258
412
  }
259
413
 
414
+ export type SurfaceLayoutSizeClass = 'compact' | 'medium' | 'expanded';
415
+ export type SurfaceLayoutSwitcherForm = 'none' | 'sidebar' | 'rail';
416
+ export type SurfaceLayoutSplitForm = 'none' | 'split' | 'collapsible' | 'fullScreen';
417
+ export type SurfaceLayoutEdge = 'left' | 'right' | 'top' | 'bottom';
418
+
419
+ export type SurfaceLayoutIcon =
420
+ | { source: 'builtIn'; name: string }
421
+ | { source: 'resource'; uri: string }
422
+ | { source: 'providerAsset'; provider: string; key: string };
423
+
424
+ /** Resolved content identity carried by one host switcher item. */
425
+ export type SurfaceSwitcherContent =
426
+ | { kind: 'lxapp'; appId: string }
427
+ | { kind: 'page'; appId: string }
428
+ | { kind: 'browser' }
429
+ | { kind: 'native'; capability: string };
430
+
431
+ export interface SurfaceSwitcherItem {
432
+ surfaceId: string;
433
+ content: SurfaceSwitcherContent;
434
+ title?: string;
435
+ icon?: SurfaceLayoutIcon;
436
+ active: boolean;
437
+ root: boolean;
438
+ closable: boolean;
439
+ renameable: boolean;
440
+ titleOverridden: boolean;
441
+ }
442
+
443
+ /** Ordered semantic model behind the host's main-surface switcher. */
444
+ export interface SurfaceSwitcherSnapshot {
445
+ /** Monotonically changes when the host surface model changes. */
446
+ revision: number;
447
+ rootSurfaceId?: string;
448
+ activeSurfaceId?: string;
449
+ items: SurfaceSwitcherItem[];
450
+ }
451
+
452
+ export interface SurfaceLayoutAside {
453
+ id: string;
454
+ edge?: SurfaceLayoutEdge;
455
+ preferredSize?: number;
456
+ }
457
+
458
+ export interface SurfaceLayoutAsideSlot {
459
+ kind: 'lxapp' | 'browser' | 'native';
460
+ edge?: SurfaceLayoutEdge;
461
+ /** Stable tab order within this content-kind region. */
462
+ children: string[];
463
+ activeChild?: string;
464
+ visible: boolean;
465
+ overlay: boolean;
466
+ }
467
+
468
+ export type SurfaceLayoutFloatAnchor =
469
+ | { to: 'screen' }
470
+ | { to: 'surface'; surfaceId: string };
471
+
472
+ export interface SurfaceLayoutFloat {
473
+ id: string;
474
+ anchor: SurfaceLayoutFloatAnchor;
475
+ dismiss: 'tapOutside' | 'manual';
476
+ modal: boolean;
477
+ closeButton: boolean;
478
+ }
479
+
480
+ /** Id-only surface tree emitted by the shared layout core. */
481
+ export type SurfaceLayoutTree =
482
+ | { kind: 'leaf'; surfaceId: string }
483
+ | {
484
+ kind: 'split';
485
+ axis: 'horizontal' | 'vertical';
486
+ children: SurfaceLayoutTree[];
487
+ weights: number[];
488
+ }
489
+ | { kind: 'tabs'; activeId: string; children: string[] }
490
+ | { kind: 'freeform'; surfaceId: string };
491
+
492
+ /**
493
+ * Read-only automation snapshot of the exact render plan consumed by the host
494
+ * skin. Use this for end-to-end assertions; production lxapp behavior should
495
+ * depend on `SurfaceHandle`, not host layout internals.
496
+ */
497
+ export interface SurfaceLayoutSnapshot {
498
+ sizeClass: SurfaceLayoutSizeClass;
499
+ bottomOwner: 'app';
500
+ switcherForm: SurfaceLayoutSwitcherForm;
501
+ splitForm: SurfaceLayoutSplitForm;
502
+ mains: string[];
503
+ activeMainId?: string;
504
+ mainSwitcher: SurfaceSwitcherSnapshot;
505
+ asides: SurfaceLayoutAside[];
506
+ asideSlots: SurfaceLayoutAsideSlot[];
507
+ floats: SurfaceLayoutFloat[];
508
+ tree?: SurfaceLayoutTree;
509
+ }
510
+
260
511
  /** Capability for one selected running lxapp. */
261
512
  export interface LxAppDriver {
262
513
  readonly page: PageDriver;
@@ -265,6 +516,8 @@ export interface LxAppDriver {
265
516
  info(): Promise<LxAppRuntimeInfo>;
266
517
  /** Configured pages of the selected lxapp. */
267
518
  pages(): Promise<LxAppPageConfig[]>;
519
+ /** Authoritative host surface render plan, for end-to-end assertions. */
520
+ surfaceLayout(): Promise<SurfaceLayoutSnapshot>;
268
521
  /** Logic-runtime eval; self-eval from that Logic runtime is rejected. */
269
522
  eval(options: LxAppEvalOptions): Promise<unknown>;
270
523
  }
@@ -291,11 +544,46 @@ export interface LxAppRuntimeInfo {
291
544
  pages_count: number;
292
545
  page_entries: LxAppPageEntry[];
293
546
  page_stack: string[];
547
+ tab_bar: LxAppRuntimeTabBarInfo | null;
548
+ navigation_bar: LxAppRuntimeNavigationBarInfo | null;
294
549
  lxapp_dir: string;
295
550
  data_dir: string;
296
551
  cache_dir: string;
297
552
  }
298
553
 
554
+ /** Runtime NavigationBar state exposed for deterministic host-level assertions. */
555
+ export interface LxAppRuntimeNavigationBarInfo {
556
+ title: string;
557
+ home_button: 'auto' | 'hidden';
558
+ home_button_visible: boolean;
559
+ runtime_style: {
560
+ background_color: string | null;
561
+ foreground_color: string | null;
562
+ divider_color: string | null;
563
+ };
564
+ }
565
+
566
+ /** Runtime TabBar state exposed for deterministic host-level assertions. */
567
+ export interface LxAppRuntimeTabBarInfo {
568
+ presentation: 'standard' | 'immersive';
569
+ visibility: 'auto' | 'visible' | 'hidden';
570
+ route_visible: boolean;
571
+ effective_visible: boolean;
572
+ selected_index: number;
573
+ runtime_style: {
574
+ foreground_color: string | null;
575
+ selected_foreground_color: string | null;
576
+ };
577
+ items: Array<{
578
+ index: number;
579
+ text: string | null;
580
+ icon_path: string | null;
581
+ selected_icon_path: string | null;
582
+ badge: string | null;
583
+ red_dot: boolean;
584
+ }>;
585
+ }
586
+
299
587
  /** Selects a running lxapp by id; defaults to the current app. */
300
588
  export interface LxAppRef {
301
589
  /** LxApp id, or `"current"` (default). */
@@ -371,7 +659,7 @@ export interface DeviceSetOptions {
371
659
  }
372
660
 
373
661
  /**
374
- * Simulated-device control (`lxdev lxapp device`). Only functional in a host
662
+ * Simulated-device control (`lxdev runner`). Only functional in a host
375
663
  * runner that registered a device controller; otherwise every call rejects.
376
664
  */
377
665
  export interface DeviceDriver {
@@ -676,6 +964,10 @@ export interface DesktopWindowInfo {
676
964
  pid: number;
677
965
  bounds: DesktopRect;
678
966
  display_id: string;
967
+ /**
968
+ * Display DIP-to-physical scale. `bounds` are already backend-native; do not
969
+ * multiply them by this value.
970
+ */
679
971
  scale: number;
680
972
  dpi: number;
681
973
  visible: boolean;
@@ -861,11 +1153,15 @@ export interface DesktopKeyTypeOptions extends DesktopInputTarget {
861
1153
  }
862
1154
 
863
1155
  export interface DesktopKeyPressOptions extends DesktopInputTarget {
1156
+ /** Case-insensitive named key (`Enter`, `ArrowDown`, `Down`, etc.) or one
1157
+ * printable character. */
864
1158
  key: string;
865
1159
  modifiers?: KeyModifier[];
866
1160
  }
867
1161
 
868
1162
  export interface DesktopKeyNameOptions extends DesktopInputTarget {
1163
+ /** Case-insensitive named key (`Enter`, `ArrowDown`, `Down`, etc.) or one
1164
+ * printable character. */
869
1165
  key: string;
870
1166
  }
871
1167
 
package/src/error.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { SurfaceErrorCode } from './generated/logic.js';
1
2
  import { ERR_CODE_INFO_BY_CODE, type LxErrorCodeInfo } from "./generated/error";
2
3
 
3
4
  const ERR_CODE_INDEX = ERR_CODE_INFO_BY_CODE as Record<number, LxErrorCodeInfo>;
@@ -9,11 +10,27 @@ export interface LxApiError {
9
10
  readonly raw: unknown;
10
11
  }
11
12
 
13
+ /** Check an already-normalized error without modifying the caught value. */
12
14
  export function isLxApiError(error: unknown): error is LxApiError {
13
- return parseLxApiError(error) !== null;
15
+ const target = toRecord(error);
16
+ if (!target) return false;
17
+ const code = parseIntegerCode(target.code);
18
+ if (code === null) return false;
19
+ const info = infoForLxErrorCode(code);
20
+ return Boolean(
21
+ info &&
22
+ target.code === code &&
23
+ target.key === info.key &&
24
+ typeof target.message === "string" &&
25
+ Object.prototype.hasOwnProperty.call(target, "raw"),
26
+ );
14
27
  }
15
28
 
16
29
  function readMessage(error: unknown): string {
30
+ const root = toRecord(error);
31
+ const data = root && toRecord(root.data);
32
+ const detail = data?.detail;
33
+ if (typeof detail === "string" && detail.trim() !== "") return detail;
17
34
  if (typeof error === "string") return error;
18
35
  if (error instanceof Error && typeof error.message === "string") return error.message;
19
36
  if (typeof error === "object" && error !== null) {
@@ -50,6 +67,35 @@ export function extractLxErrorCode(error: unknown): number | null {
50
67
  return parseIntegerCode(data.bizCode) ?? parseIntegerCode(data.code);
51
68
  }
52
69
 
70
+ /**
71
+ * The surface code carried by a `lx.surface.*` / `lx.shell.*` rejection.
72
+ *
73
+ * A rejection's `code` is the transport-level host code shared with every
74
+ * other `lx` API; the surface-specific member of `SurfaceErrorCode` rides on
75
+ * `data.code`. Reading it through this helper keeps callers off both the
76
+ * message text and the shape.
77
+ */
78
+ export function surfaceErrorCode(error: unknown): SurfaceErrorCode | null {
79
+ const root = toRecord(error);
80
+ const data = root ? toRecord(root.data) : null;
81
+ const code = data?.code;
82
+ return typeof code === 'string' && SURFACE_ERROR_CODES.includes(code as SurfaceErrorCode)
83
+ ? (code as SurfaceErrorCode)
84
+ : null;
85
+ }
86
+
87
+ /** Every member of `SurfaceErrorCode`, for runtime narrowing. */
88
+ export const SURFACE_ERROR_CODES = [
89
+ 'unsupported_placement',
90
+ 'denied',
91
+ 'not_declared',
92
+ 'invalid_arg',
93
+ 'already_open_other_role',
94
+ 'closed',
95
+ 'capability_missing',
96
+ 'failed',
97
+ ] as const satisfies readonly SurfaceErrorCode[];
98
+
53
99
  export function isKnownLxErrorCode(code: number): boolean {
54
100
  return Number.isInteger(code) && Object.prototype.hasOwnProperty.call(ERR_CODE_INDEX, code);
55
101
  }
@@ -27,6 +27,7 @@ export const I18N_KEYS = [
27
27
  "browser_page_menu",
28
28
  "browser_pin_to_sidebar",
29
29
  "browser_remove_bookmark",
30
+ "browser_tabs",
30
31
  "browser_unpin",
31
32
  "camera_access_denied",
32
33
  "camera_audio_init_failed",
@@ -57,6 +58,7 @@ export const I18N_KEYS = [
57
58
  "common_delete",
58
59
  "common_done",
59
60
  "common_exit",
61
+ "common_forward",
60
62
  "common_loading",
61
63
  "common_ok",
62
64
  "common_refresh",
@@ -132,6 +134,11 @@ export const I18N_KEYS = [
132
134
  "permission_wifi_reason",
133
135
  "shell_pin_limit_message",
134
136
  "shell_pin_limit_title",
137
+ "surface_close",
138
+ "surface_close_after",
139
+ "surface_close_others",
140
+ "surface_rename",
141
+ "surface_reset_title",
135
142
  "terminal_change_title",
136
143
  "terminal_close_pane",
137
144
  "terminal_copy",
@@ -139,6 +146,7 @@ export const I18N_KEYS = [
139
146
  "terminal_paste",
140
147
  "terminal_read_only",
141
148
  "terminal_reset",
149
+ "terminal_search",
142
150
  "terminal_split_down",
143
151
  "terminal_split_left",
144
152
  "terminal_split_right",