@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.
@@ -14,9 +14,10 @@ export interface PageInstance<TData extends Record<string, unknown> = Record<str
14
14
  data: TData;
15
15
  route: string;
16
16
  /**
17
- * Available when this page was opened as a surface via `lx.openSurface(...)`.
17
+ * Available when this page was opened as a surface via
18
+ * `lx.surface.openPage(...)`.
18
19
  */
19
- surface?: Surface;
20
+ surface?: PageSurface;
20
21
  /**
21
22
  * Available when this page was opened by `lx.navigateTo(...)`.
22
23
  */
@@ -93,11 +94,6 @@ export interface DownloadTask<TDownloadResult extends DownloadResult = DownloadR
93
94
  abort(): Promise<void>;
94
95
  wait(): Promise<TDownloadResult>;
95
96
  }
96
- export interface FileManager {
97
- readFile(options: ReadTextFileOptions): Promise<ReadTextFileResult>;
98
- readFile(options: ReadBinaryFileOptions): Promise<ReadBinaryFileResult>;
99
- readFile(options: ReadFileOptions): Promise<ReadFileResult>;
100
- }
101
97
  declare global {
102
98
  interface HostAppApi {
103
99
  /**
@@ -106,8 +102,9 @@ declare global {
106
102
  */
107
103
  readonly envVersion: HostAppEnvVersion;
108
104
  /**
109
- * Launch-at-startup control. Present only on macOS / Windows with the
110
- * capability declared; gate access with `lx.app.autostart?.…`.
105
+ * Launch-at-startup control. Absent where the host cannot register a
106
+ * startup item; its presence and `lx.supports({ capability: 'autostart' })` always
107
+ * agree, so `lx.app.autostart?.…` and the query are interchangeable.
111
108
  */
112
109
  autostart?: AutostartApi;
113
110
  }
@@ -116,27 +113,65 @@ declare global {
116
113
  }
117
114
  interface Lx {
118
115
  /**
119
- * Open a surface. Browser tabs resolve to `null`, declared surfaces to a
120
- * host-managed handle, and page surfaces to a full `Surface`. URL asides
121
- * return a `Surface` when docked and `null` in compact browser chrome.
122
- * `as: "window"` is desktop-only.
116
+ * Terminal product settings. Present only in the host-bundled Terminal
117
+ * Settings lxapp when the host declares `capabilities.terminal`; its
118
+ * presence and `lx.supports({ capability: 'terminal' })` always agree.
123
119
  */
124
- openSurface(spec: OpenUrlTabSpec): Promise<null>;
125
- openSurface(spec: OpenDeclaredSurfaceSpec | OpenLxappSurfaceSpec | OpenNativeSurfaceSpec): Promise<SurfaceHandle>;
126
- openSurface(spec: OpenPageSurfaceSpec): Promise<Surface>;
127
- openSurface(spec: OpenUrlAsideSpec): Promise<Surface | null>;
128
- openSurface(spec: OpenSurfaceSpec): Promise<Surface | SurfaceHandle | null>;
120
+ readonly terminal?: TerminalApi;
129
121
  /** Download to the downloads directory. */
130
122
  downloadFile(options: DownloadsDownloadOptions): DownloadTask<DownloadsDownloadResult>;
131
123
  /** Download to the lxapp-managed app directory. */
132
124
  downloadFile(options: AppDownloadOptions): DownloadTask<AppDownloadResult>;
133
125
  /** Download with a destination-correlated result type. */
134
126
  downloadFile<TDestination extends DownloadDestination = "app">(options: DownloadOptions<TDestination>): DownloadTask<DownloadResultForDestination<TDestination>>;
127
+ /**
128
+ * Open this lxapp's store with every key's shape pinned on the handle.
129
+ * `get` / `set` / `delete` then share that schema instead of
130
+ * repeating `get<T>()` at each call site.
131
+ */
132
+ getStorage<S extends StorageSchema>(): TypedStorage<S>;
135
133
  }
136
134
  }
137
- export type ActionSheetResult = {
138
- tapIndex: number;
135
+ /**
136
+ * A map of storage keys to stored value shapes.
137
+ *
138
+ * `object` deliberately accepts both type aliases and interfaces. Requiring a
139
+ * string index signature would reject ordinary interface-based schemas.
140
+ */
141
+ export type StorageSchema = object;
142
+ type StorageKey<S extends object> = Extract<keyof S, string>;
143
+ type StorageEntry<S extends object> = {
144
+ [K in StorageKey<S>]: [key: K, value: S[K]];
145
+ }[StorageKey<S>];
146
+ /**
147
+ * Schema-typed view of the same store `lx.getStorage()` returns.
148
+ * Runtime is identical; only the key/value types are pinned.
149
+ *
150
+ * The schema constrains what this handle writes and reads, not what the store
151
+ * contains: a previous app version, or another code path holding the untyped
152
+ * handle, can have written keys outside it. That is why `list` still resolves
153
+ * plain strings — narrowing it to the schema's keys would be the same
154
+ * unchecked assertion this type exists to remove from `get<T>()`.
155
+ */
156
+ export type TypedStorage<S extends object> = {
157
+ get<K extends StorageKey<S>>(key: K): Promise<S[K] | undefined>;
158
+ set(...entry: StorageEntry<S>): Promise<void>;
159
+ delete(key: StorageKey<S>): Promise<void>;
160
+ clear(): Promise<void>;
161
+ list(prefix?: string): Promise<string[]>;
162
+ info(): Promise<StorageInfo>;
139
163
  };
164
+ /**
165
+ * Result of `lx.showActionSheet`. Branch on `canceled` before reading
166
+ * the selected item index.
167
+ */
168
+ export type ActionSheetResult = {
169
+ canceled: false;
170
+ /** Index of the tapped item in `itemList`. */
171
+ index: number;
172
+ } | CanceledResult;
173
+ /** Every surface handle, narrowable by `kind`. */
174
+ export type AnySurface = PageSurface | DeclaredSurface | AppSurface | TabSurface | BuiltinSurface;
140
175
  export type AppConfig = {
141
176
  globalData?: Record<string, unknown>;
142
177
  onLaunch?: (options?: AppLaunchOptions) => void | Promise<void>;
@@ -214,13 +249,20 @@ export type AppScreenshotResult = {
214
249
  /** Image height in pixels, when the runtime could read it from the PNG. */
215
250
  height?: number;
216
251
  };
252
+ /** Another lxapp composed into a shell slot. */
253
+ export type AppSurface = SurfaceBase & SurfaceShowable & {
254
+ readonly kind: 'app';
255
+ readonly realized: 'main' | 'aside';
256
+ };
257
+ export type AppearanceApi = globalThis.AppearanceApi;
258
+ export type AppearancePreference = 'auto' | 'light' | 'dark';
217
259
  /**
218
260
  * Launch-at-startup control for the host app.
219
- * **macOS 13+ / Windows only.** Everywhere else — other platforms, or a
220
- * macOS shell older than 13 — `lx.app.autostart` is absent (`undefined`);
221
- * presence is the support check, so portable code gates on the member itself:
261
+ * Absent (`undefined`) wherever the host cannot register a startup item.
262
+ * `lx.supports({ capability: 'autostart' })` and the member's presence always
263
+ * agree, so either gate works:
222
264
  * ```ts
223
- * if (lx.app.autostart) {
265
+ * if (lx.supports({ capability: 'autostart' })) {
224
266
  * // render the "Launch at startup" toggle
225
267
  * }
226
268
  * ```
@@ -250,24 +292,37 @@ export type AutostartApi = {
250
292
  setEnabled(on: boolean): Promise<void>;
251
293
  };
252
294
  export type BinaryFileData = ArrayBuffer | ArrayBufferView;
253
- export type CapsuleRect = {
254
- width?: number;
255
- height?: number;
256
- top?: number;
257
- right?: number;
258
- bottom?: number;
259
- left?: number;
295
+ /**
296
+ * Built-in browser product page. Opening one requires
297
+ * `capabilities.browser` and is restricted to the home lxapp.
298
+ */
299
+ export type BuiltinShellPage = 'settings' | 'downloads';
300
+ /**
301
+ * A host builtin page such as settings or downloads. The shell owns
302
+ * its lifetime and its visibility, so this handle reports identity:
303
+ * there is no `show` / `hide`, and the inherited `close()` rejects
304
+ * with `unsupported_placement`.
305
+ */
306
+ export type BuiltinSurface = SurfaceBase & {
307
+ readonly kind: 'builtin';
308
+ };
309
+ /** The user dismissed the operation. Never an error. */
310
+ export type CanceledResult = {
311
+ canceled: true;
260
312
  };
261
313
  export type ChooseDirectoryOptions = {
262
314
  /** Initial directory the dialog opens in. Platform default if omitted. */
263
315
  defaultPath?: string;
264
316
  };
317
+ /**
318
+ * Result of `lx.chooseDirectory`. Branch on `canceled` before reading
319
+ * the selected directory.
320
+ */
265
321
  export type ChooseDirectoryResult = {
266
- /** True if the user dismissed the dialog without selecting. */
267
- canceled: boolean;
268
- /** Native-consumable directory reference (path or URI). Undefined when canceled. */
269
- path?: string;
270
- };
322
+ canceled: false;
323
+ /** Native-consumable directory reference (path or URI). */
324
+ path: string;
325
+ } | CanceledResult;
271
326
  export type ChooseFileOptions = {
272
327
  /** Allow selecting multiple files. Default: false */
273
328
  multiple?: boolean;
@@ -281,16 +336,20 @@ export type ChooseFileOptions = {
281
336
  */
282
337
  defaultPath?: string;
283
338
  };
339
+ /**
340
+ * Result of `lx.chooseFile`. Branch on `canceled` before reading the
341
+ * selected paths.
342
+ */
284
343
  export type ChooseFileResult = {
285
- /** True if the user dismissed the dialog without selecting. */
286
- canceled: boolean;
344
+ canceled: false;
287
345
  /**
288
- * File paths returned by LingXia. Values may be app-local paths, `lx://...`
289
- * paths, or platform system-picker references. Treat them as opaque strings
290
- * and pass them back to LingXia APIs such as `lx.share`.
346
+ * File paths returned by LingXia; always at least one. Values may be
347
+ * app-local paths, `lx://...` paths, or platform system-picker references.
348
+ * Treat them as opaque strings and pass them back to LingXia APIs such as
349
+ * `lx.share`.
291
350
  */
292
- paths: string[];
293
- };
351
+ paths: [string, ...string[]];
352
+ } | CanceledResult;
294
353
  export type ChooseMediaOptions = {
295
354
  count?: number;
296
355
  mediaType?: ('image' | 'video')[];
@@ -298,6 +357,15 @@ export type ChooseMediaOptions = {
298
357
  camera?: 'back' | 'front';
299
358
  maxDuration?: number;
300
359
  };
360
+ /**
361
+ * Result of `lx.chooseMedia`. Branch on `canceled` before reading the
362
+ * selected entries.
363
+ */
364
+ export type ChooseMediaResult = {
365
+ canceled: false;
366
+ /** Picked media; always at least one entry. */
367
+ entries: [ChosenMediaEntry, ...ChosenMediaEntry[]];
368
+ } | CanceledResult;
301
369
  export type ChosenMediaEntry = {
302
370
  tempFilePath: string;
303
371
  fileType: 'image' | 'video';
@@ -388,11 +456,9 @@ export type ConnectWifiOptions = {
388
456
  SSID: string;
389
457
  password?: string;
390
458
  };
391
- export type CopyFileOptions = {
392
- srcPath: string;
393
- destPath: string;
394
- /** Defaults to false. */
395
- overwrite?: boolean;
459
+ /** A surface declared by the host in `lingxia.yaml`. */
460
+ export type DeclaredSurface = SurfaceBase & SurfaceShowable & {
461
+ readonly kind: 'declared';
396
462
  };
397
463
  /** Display and orientation APIs. */
398
464
  export type DeviceOrientation = "portrait" | "landscape";
@@ -417,22 +483,19 @@ export type DownloadResult = AppDownloadResult | DownloadsDownloadResult;
417
483
  export type DownloadsDownloadOptions = DownloadOptionsBase & {
418
484
  /**
419
485
  * Optional filename hint for the system Downloads destination.
420
- * This is not an app-owned FileManager path.
486
+ * This is not an app-owned `lx.fs` path.
421
487
  */
422
488
  filePath?: string;
423
489
  /** Save into the user's system Downloads directory. */
424
490
  destination: 'downloads';
425
491
  };
426
492
  export type DownloadsDownloadResult = {
427
- /** Native system Downloads path. Do not pass this to `FileManager`. */
493
+ /** Native system Downloads path. Do not pass this to `lx.fs`. */
428
494
  filePath: SystemDownloadsPath;
429
495
  tempFilePath?: never;
430
496
  mimeType?: string;
431
497
  size: number;
432
498
  };
433
- export type ExistsOptions = {
434
- path: string;
435
- };
436
499
  export type ExtractVideoThumbnailOptions = {
437
500
  /**
438
501
  * Source video path or `lx://` URI.
@@ -486,6 +549,30 @@ export type FileDialogFilter = {
486
549
  /** Allowed extensions without dots, e.g. ['pdf', 'txt']. */
487
550
  extensions: string[];
488
551
  };
552
+ export type FileSystemApi = globalThis.FileSystemApi;
553
+ export type FsCopyOptions = {
554
+ /** Defaults to false. */
555
+ overwrite?: boolean;
556
+ };
557
+ export type FsMkdirOptions = {
558
+ recursive?: boolean;
559
+ };
560
+ export type FsRemoveOptions = {
561
+ recursive?: boolean;
562
+ };
563
+ export type FsRenameOptions = {
564
+ /** Defaults to false. */
565
+ overwrite?: boolean;
566
+ };
567
+ export type FsWriteOptions = {
568
+ /**
569
+ * How string input is interpreted. Strings are UTF-8 by default; `base64`
570
+ * decodes the input into raw bytes before writing.
571
+ */
572
+ encoding?: 'utf8' | 'base64';
573
+ /** Defaults to false. */
574
+ overwrite?: boolean;
575
+ };
489
576
  /** Media picker, preview, scan, and file processing APIs. */
490
577
  export type GetImageInfoOptions = {
491
578
  path: string;
@@ -510,8 +597,8 @@ export type HostAppApi = globalThis.HostAppApi;
510
597
  * `crates/lingxia-update::ReleaseType` enum and the `envVersion` field in the
511
598
  * generated `app.json`. Pre-envVersion app artifacts are treated as `'release'`.
512
599
  * Note: this is *separate* from `LxAppEnvVersion` in the navigator module,
513
- * which encodes lxapp release channels (`'develop' | 'preview' | 'release'`)
514
- * for cross-app navigation URLs and uses the truncated `develop` form.
600
+ * which encodes lxapp release channels for cross-app navigation URLs —
601
+ * same three names, different axis.
515
602
  */
516
603
  export type HostAppEnvVersion = 'developer' | 'preview' | 'release';
517
604
  export type HostAppUpdateApplyStage = 'download' | 'install';
@@ -546,9 +633,9 @@ export type HostAppUpdateInfo = {
546
633
  * The returned task can be awaited directly when progress is not needed, or
547
634
  * consumed with `for await...of` to render progress.
548
635
  *
549
- * Direct package handoff is currently supported on Android and macOS. Other
550
- * platforms reject with an unsupported-operation error; use `version` and
551
- * `releaseNotes` to guide users to the appropriate app marketplace.
636
+ * Requires `lx.supports({ capability: 'selfUpdate' })`. Where the host cannot
637
+ * install its own update it rejects with an unsupported-operation error;
638
+ * use `version` and `releaseNotes` to guide users to the app marketplace.
552
639
  */
553
640
  apply(): HostAppUpdateTask;
554
641
  };
@@ -567,6 +654,12 @@ export type HostAppUpdateTask = PromiseLike<HostAppUpdateResult> & AsyncIterable
567
654
  finally(onfinally?: (() => void) | null): Promise<HostAppUpdateResult>;
568
655
  wait(): Promise<HostAppUpdateResult>;
569
656
  };
657
+ export type InstalledTerminalFont = {
658
+ family: string;
659
+ monospace: boolean;
660
+ ligatures: boolean;
661
+ nerdIcons: boolean;
662
+ };
570
663
  /**
571
664
  * Input event APIs.
572
665
  * Platform support: Android only
@@ -583,36 +676,100 @@ export type KeyEvent = {
583
676
  repeat?: boolean;
584
677
  };
585
678
  export type KeyEventCallback = (event: KeyEvent) => void;
586
- export type LxAppEnvVersion = 'release' | 'preview' | 'develop';
679
+ export type LxAppEnvVersion = 'release' | 'preview' | 'developer';
587
680
  /** LxApp metadata APIs. */
588
681
  export type LxAppReleaseType = 'release' | 'preview' | 'developer';
682
+ /** Boolean capability names accepted by `lx.supports`. */
683
+ export type LxCapabilityFlag = 'terminal' | 'autostart' | 'notifications' | 'browser' | 'proxy' | 'selfUpdate' | 'process' | 'appUse' | 'computerUse' | 'browserUse' | 'mediaCapture';
684
+ /**
685
+ * One capability question per call. The catalog is closed, so
686
+ * completion enumerates it and a typo is a type error. `capability`
687
+ * is the discriminant; only the `surface` branch accepts a `value`.
688
+ * Two surface answers describe an *affordance*, not whether the call
689
+ * succeeds: `tab` is "the host has an in-app browser" — without it a
690
+ * url still opens, in the OS browser instead — and `aside` is "a
691
+ * docked region exists right now", while a compact layout still opens
692
+ * the url through the in-app browser's own chrome. Ask them to decide
693
+ * what to render, not whether to call.
694
+ * `chrome` qualifies a window and only a window: it asks whether this
695
+ * host can produce that decoration, not merely a window.
696
+ */
697
+ export type LxCapabilityQuery = {
698
+ capability: 'surface';
699
+ value: 'window';
700
+ chrome?: WindowChrome;
701
+ } | {
702
+ capability: 'surface';
703
+ value: Exclude<LxSurfaceCapability, 'window'>;
704
+ } | {
705
+ capability: LxCapabilityFlag;
706
+ };
589
707
  export type LxEnv = globalThis.LxEnv;
708
+ /** Surface placements accepted by `lx.supports`. */
709
+ export type LxSurfaceCapability = 'main' | 'aside' | 'float' | 'window' | 'tab';
590
710
  /** Device action APIs. */
591
711
  export type MakePhoneCallOptions = {
592
712
  phoneNumber: string;
593
713
  };
594
714
  export type MediaObjectFit = 'cover' | 'contain' | 'fill' | 'fit';
595
715
  export type MediaRotation = 0 | 90 | 180 | 270;
596
- export type MkdirOptions = {
597
- path: string;
598
- recursive?: boolean;
599
- };
716
+ /**
717
+ * Result of `lx.showModal`. `canceled: false` means the user confirmed;
718
+ * there is no third resolved outcome. Presentation failures reject.
719
+ */
600
720
  export type ModalResult = {
601
- confirm: boolean;
602
- cancel: boolean;
721
+ canceled: false;
722
+ } | CanceledResult;
723
+ /**
724
+ * One app-declared action shown in the host-provided More affordance.
725
+ * Mobile hosts render these in the capsule sheet; desktop hosts render
726
+ * them in the lxapp context menu. The native menu is fully dismissed
727
+ * before `onClick` runs.
728
+ */
729
+ export type MoreAction = {
730
+ /** Bundled resource path or an app-accessible local `lx://` path. */
731
+ icon: string;
732
+ /** Visible action label. */
733
+ label: string;
734
+ onClick: () => void | Promise<void>;
603
735
  };
736
+ /**
737
+ * Options for `lx.navigateBack()`. Omit the object or `delta` to pop
738
+ * one page.
739
+ */
604
740
  export type NavigateBackOptions = {
605
- delta: number;
741
+ /** Number of pages to pop. Defaults to 1. */
742
+ delta?: number;
606
743
  };
607
- export type NavigateToLxAppOptions = {
744
+ /**
745
+ * Navigate to another lxapp inside the current App Surface. JavaScript
746
+ * callers address pages by their configured name; page routes are an
747
+ * internal runtime detail and are not accepted as input.
748
+ */
749
+ export type NavigateToAppOptions = {
608
750
  appId: string;
751
+ /**
752
+ * Configured page name from the target lxapp's `lxapp.json`. Omit it to
753
+ * open the target app's initial page. Full routes such as
754
+ * `/pages/home/index` are not supported.
755
+ */
609
756
  page?: string;
610
- path?: string;
611
757
  query?: PageQuery;
612
758
  envVersion?: LxAppEnvVersion;
613
759
  targetVersion?: string;
614
760
  };
615
761
  export type NavigateToOptions = PageTargetOptions;
762
+ export type NavigationBarApi = globalThis.NavigationBarApi;
763
+ export type NavigationBarPatch = {
764
+ title?: string | null;
765
+ homeButton?: VisibilityPreference;
766
+ style?: NavigationBarStylePatch | null;
767
+ };
768
+ export type NavigationBarStylePatch = {
769
+ backgroundColor?: string | null;
770
+ foregroundColor?: string | null;
771
+ dividerColor?: string | null;
772
+ };
616
773
  export type NetworkChangeCallback = (info: NetworkInfo) => void;
617
774
  export type NetworkInfo = {
618
775
  isConnected: boolean;
@@ -622,23 +779,6 @@ export type NetworkInfo = {
622
779
  };
623
780
  /** Network status APIs. */
624
781
  export type NetworkType = 'none' | 'unknown' | 'wifi' | '2g' | '3g' | '4g' | '5g' | 'ethernet';
625
- /**
626
- * Show a surface declared by id in the host's `lingxia.yaml`.
627
- * Available to any lxapp granted access to that declaration.
628
- */
629
- export type OpenDeclaredSurfaceSpec = {
630
- surface: string;
631
- /** Docking edge override for this open. */
632
- edge?: SurfaceEdge;
633
- page?: never;
634
- url?: never;
635
- lxapp?: never;
636
- native?: never;
637
- as?: never;
638
- position?: never;
639
- size?: never;
640
- query?: never;
641
- };
642
782
  /** File system APIs. */
643
783
  export type OpenFileOptions = {
644
784
  /** Local file path or runtime-managed temp path. */
@@ -655,134 +795,56 @@ export type OpenFileOptions = {
655
795
  showMenu?: boolean;
656
796
  };
657
797
  /**
658
- * Open another lxapp by appId (home lxapp only). A declared surface
659
- * toggles its shell presentation; an undeclared lxapp opens as a main
660
- * tab, or docks as an aside panel with `as: 'aside'`.
798
+ * `as` picks the shape. A float anchors and carries no decoration; a
799
+ * window is decorated and does not anchor. The runtime rejects the
800
+ * wrong pairing either way, so the type says it first — except with an
801
+ * ordered preference, where the realized placement is not known up
802
+ * front and both stay open.
661
803
  */
662
- export type OpenLxappSurfaceSpec = {
663
- lxapp: string;
664
- /** Defaults to the lingxia.yaml role, else 'main'. */
665
- as?: 'main' | 'aside' | 'float';
804
+ export type OpenPageOptions = (OpenPageShared & {
805
+ /** The default. Rejects when the host cannot float. */
806
+ as?: 'float';
807
+ /** Where the float anchors. */
808
+ position?: SurfaceFloatPosition;
809
+ chrome?: never;
810
+ }) | (OpenPageShared & {
811
+ /** A separate desktop window. Rejects when the host cannot make one. */
812
+ as: 'window';
813
+ /** Window decoration. */
814
+ chrome?: WindowChrome;
815
+ position?: never;
816
+ }) | (OpenPageShared & {
666
817
  /**
667
- * Docking edge override for this open. Without it the surface keeps its
668
- * current placement (initially the `lingxia.yaml` edge); with it the panel
669
- * opens there — or moves there if already visible.
818
+ * An ordered preference: the first placement the host can realize wins,
819
+ * and `realized` reports which.
670
820
  */
671
- edge?: SurfaceEdge;
672
- page?: never;
673
- url?: never;
674
- native?: never;
675
- position?: never;
676
- size?: never;
677
- query?: never;
678
- };
679
- /**
680
- * Open a host-registered native capability (home lxapp only), e.g.
681
- * the built-in terminal declared in `lingxia.yaml` surfaces.
682
- */
683
- export type OpenNativeSurfaceSpec = {
684
- native: string;
685
- /** Docking edge override for this open. */
686
- edge?: SurfaceEdge;
687
- page?: never;
688
- url?: never;
689
- lxapp?: never;
690
- as?: never;
691
- position?: never;
692
- size?: never;
693
- query?: never;
694
- };
695
- /**
696
- * Spec for {@link OpenSurfaceSpec}. A discriminated union keyed by source so a
697
- * page name and a declared surface id never collide (each is its own string
698
- * space, separately type-checkable).
699
- * - `{ page }` — one of this lxapp's own pages, by name, arranged as `as`
700
- * (`float` is a popup; `window` is a bare desktop window, which rejects on
701
- * mobile). `position` applies to `float`, and `size` is a Host-clamped hint.
702
- * They are fixed at open (re-open to change). Your own pages **cannot** be
703
- * docked as an `aside` — an aside is external content only (see `{ url }`).
704
- * For a side panel of your own, use a declared `surface`, an in-page split
705
- * layout, or `role: main` for a switchable destination.
706
- * `float` is a popup layered above the main at `position` (like a dialog); it
707
- * takes no layout space. `interaction` controls the native close button,
708
- * outside-click dismissal, and modality. Defaults are no button,
709
- * `tapOutside`, and non-modal.
710
- * - `{ surface }` — a surface declared in `lingxia.yaml` `surfaces:`, by id
711
- * (e.g. `'terminal'`, `'ai-assistant'`). Form, position, and startup data come
712
- * from the declaration.
713
- * - `{ url }` — external content in the in-app browser. Without `as` it opens as
714
- * a main browser tab (the **self** browser: full chrome **with an editable
715
- * address bar**, no handle). With `as: 'aside'` it opens in the **browser
716
- * aside** — a docked (large screen) / full-screen (phone) **multi-tab** browser
717
- * for external content only (`https://` or `file://`).
718
- * The aside is **API-only** and never permits address editing or a manual
719
- * "new tab" action. Desktop may show the current address read-only; compact
720
- * phone/Runner chrome omits the address row entirely.
721
- * Tabs are **deduped by URL** — reopening a URL focuses the existing tab and
722
- * preserves its current navigation. On `medium` / `expanded`, the returned
723
- * handle is **tab-scoped**: `close()` closes that tab. Compact browser chrome
724
- * owns the group and returns `null`. Closing the last tab closes the aside;
725
- * dismissing it only hides the group. The tab UI shows page **titles** (never
726
- * the URL), plus per-tab close, back/forward, refresh, and dismissal.
727
- * Presentation is the only large/small difference: on `medium` / `expanded`
728
- * the aside **docks** and splits beside the main at `edge` (default `'right'`)
729
- * with a horizontal title tab strip; on `compact` (phone / runner) it presents
730
- * **full-screen** with a single-row **bottom** browser toolbar (tabs reached
731
- * via an aside-only switcher). System/edge Back and the toolbar dismiss action
732
- * exit the whole aside even when page history exists; the explicit browser
733
- * Back button navigates history. `size` is a host-clamped preferred size
734
- * (large screen only).
735
- */
736
- export type OpenPageSurfaceSpec = {
737
- page: string;
738
- /** A popup above the main. */
739
- as: 'float';
821
+ as: readonly ('float' | 'window')[];
822
+ chrome?: WindowChrome;
740
823
  position?: SurfaceFloatPosition;
824
+ });
825
+ export type OpenPageShared = {
826
+ /**
827
+ * A float accepts a percentage; a window is in logical pixels and ignores
828
+ * one. Both live here rather than in two option types, because `as` may be
829
+ * an ordered preference and the realized placement is not known up front.
830
+ */
741
831
  size?: OverlaySurfaceSize;
742
832
  interaction?: SurfaceInteraction;
743
833
  query?: Record<string, unknown>;
744
- edge?: never;
745
- surface?: never;
746
- url?: never;
747
- } | {
748
- page: string;
749
- as: 'window';
750
- size?: WindowSurfaceSize;
751
- /** Windows use manual dismissal; `tapOutside` is invalid. */
752
- interaction?: SurfaceInteraction;
753
- query?: Record<string, unknown>;
754
- edge?: never;
755
- position?: never;
756
- surface?: never;
757
- url?: never;
834
+ /** Caller-owned identity, for `lx.surface.get(key)` later. */
835
+ key?: string;
758
836
  };
759
- export type OpenSurfaceSpec = OpenPageSurfaceSpec | OpenDeclaredSurfaceSpec | OpenLxappSurfaceSpec | OpenNativeSurfaceSpec | OpenUrlTabSpec | OpenUrlAsideSpec;
760
- /**
761
- * Open `url` in the multi-tab browser aside. `url` must be `https://` or
762
- * `file://` (external content only). Repeated calls add/focus tabs (deduped by
763
- * URL) in the single aside per window. Medium/expanded returns a tab-scoped
764
- * handle; compact returns `null` because browser chrome owns the group. See
765
- * {@link OpenSurfaceSpec} for the full aside contract.
766
- */
767
- export type OpenUrlAsideSpec = {
768
- url: string;
769
- as: 'aside';
837
+ export type OpenUrlOptions = {
838
+ /**
839
+ * `tab` opens a browser tab; `aside` docks the browser beside the main.
840
+ * Defaults to `'tab'`.
841
+ */
842
+ as?: 'tab' | 'aside' | readonly ('tab' | 'aside')[];
843
+ /** Preferred docking side when the realized placement is an aside. */
770
844
  edge?: SurfaceEdge;
771
845
  size?: OverlaySurfaceSize;
772
- page?: never;
773
- surface?: never;
774
- position?: never;
775
- query?: never;
776
- };
777
- export type OpenUrlTabSpec = {
778
- url: string;
779
- as?: never;
780
- page?: never;
781
- surface?: never;
782
- edge?: never;
783
- position?: never;
784
- size?: never;
785
- query?: never;
846
+ /** Stable identity for `lx.surface.get(key)`. */
847
+ key?: string;
786
848
  };
787
849
  export type OverlaySurfaceSize = {
788
850
  /** Width hint. */
@@ -805,20 +867,19 @@ export type PageMessagePort = {
805
867
  };
806
868
  export type PageQuery = Record<string, PageQueryValue>;
807
869
  export type PageQueryValue = string | number | boolean | null | undefined;
870
+ /** One of this lxapp's own pages, opened as a float or a window. */
871
+ export type PageSurface = SurfaceBase & SurfaceShowable & SurfaceMessaging & {
872
+ readonly kind: 'page';
873
+ readonly realized: 'float' | 'window';
874
+ };
808
875
  /**
809
876
  * Target page for `navigateTo`, `redirectTo`, `switchTab`, and `reLaunch`.
810
- * Pass exactly one of `page` or `path`; there is no `url` field. Page
811
- * names and routes are discoverable with `lxdev lxapp pages`.
877
+ * JavaScript navigation accepts only the configured page name; full routes
878
+ * are internal runtime details. Discover names with `lxdev lxapp pages`.
812
879
  */
813
880
  export type PageTargetOptions = {
814
881
  /** Configured page name from `lingxia.yaml` / `lxapp.json`. */
815
882
  page: string;
816
- path?: never;
817
- query?: PageQuery;
818
- } | {
819
- /** Full page route, for example `/pages/home/index`. */
820
- path: string;
821
- page?: never;
822
883
  query?: PageQuery;
823
884
  };
824
885
  export type PreviewMediaAdvance = 'manual' | 'next' | 'loop';
@@ -968,36 +1029,8 @@ export type PreviewMediaSource = {
968
1029
  durationMs?: number;
969
1030
  };
970
1031
  export type ReLaunchOptions = PageTargetOptions;
971
- export type ReadBinaryFileOptions = {
972
- filePath: string;
973
- encoding?: undefined;
974
- };
975
- export type ReadBinaryFileResult = {
976
- data: ArrayBuffer;
977
- };
978
- export type ReadDirOptions = {
979
- path: string;
980
- };
981
- export type ReadFileOptions = ReadTextFileOptions | ReadBinaryFileOptions;
982
- export type ReadFileResult = ReadTextFileResult | ReadBinaryFileResult;
983
- export type ReadTextFileOptions = {
984
- filePath: string;
985
- encoding: 'utf8' | 'base64';
986
- };
987
- export type ReadTextFileResult = {
988
- data: string;
989
- };
990
1032
  export type RedirectToOptions = PageTargetOptions;
991
- export type RemoveOptions = {
992
- path: string;
993
- recursive?: boolean;
994
- };
995
- export type RenameOptions = {
996
- oldPath: string;
997
- newPath: string;
998
- /** Defaults to false. */
999
- overwrite?: boolean;
1000
- };
1033
+ export type ResolvedAppearance = 'light' | 'dark';
1001
1034
  export type SaveMediaOptions = {
1002
1035
  filePath: string;
1003
1036
  };
@@ -1005,10 +1038,15 @@ export type ScanCodeOptions = {
1005
1038
  onlyFromCamera?: boolean;
1006
1039
  scanType?: ('barCode' | 'qrCode' | 'datamatrix' | 'pdf417')[];
1007
1040
  };
1041
+ /**
1042
+ * Result of `lx.scanCode`. Branch on `canceled` before reading the scan
1043
+ * payload.
1044
+ */
1008
1045
  export type ScanCodeResult = {
1046
+ canceled: false;
1009
1047
  scanResult: string;
1010
1048
  scanType: string;
1011
- };
1049
+ } | CanceledResult;
1012
1050
  /** Share images, PDFs, or other files. */
1013
1051
  export type ShareFilesOptions = ShareTitleOptions & {
1014
1052
  /**
@@ -1064,10 +1102,12 @@ export type SharePageOptions = ShareTextBaseOptions & {
1064
1102
  export type ShareQuery = Record<string, string | number | boolean>;
1065
1103
  export type ShareResult = {
1066
1104
  /**
1067
- * Best-effort completion flag. Some platforms can only confirm that the
1068
- * system share UI was opened or closed.
1105
+ * What the share sheet reported. Not part of the `canceled` family: some
1106
+ * platforms only observe that the system UI opened and closed, so the
1107
+ * unknown case is stated rather than hidden in a missing boolean that
1108
+ * every call site would read as "not shared".
1069
1109
  */
1070
- completed?: boolean;
1110
+ outcome: 'completed' | 'dismissed' | 'unknown';
1071
1111
  };
1072
1112
  export type ShareTextBaseOptions = ShareTitleOptions & {
1073
1113
  /**
@@ -1090,26 +1130,143 @@ export type ShareTitleOptions = {
1090
1130
  title?: string;
1091
1131
  };
1092
1132
  /**
1093
- * One app-declared shell activator. Its `id` remains stable across
1094
- * updates and activation. The shell only routes activation to the
1095
- * callback; the app owns every resulting action.
1133
+ * App-owned host-shell chrome. Mutations are available only to the home
1134
+ * lxapp's Logic context; other lxapps receive a permission error.
1135
+ */
1136
+ export type ShellApi = {
1137
+ /**
1138
+ * Declares runtime actions in the desktop shell's sidebar header or footer.
1139
+ * The shell controls layout and only dispatches activation; callbacks own
1140
+ * navigation and all other behavior.
1141
+ */
1142
+ sidebarActions: ShellSidebarActionsApi;
1143
+ /** Compose another lxapp into a shell slot. */
1144
+ openApp(appId: string, options: ShellOpenAppOptions): Promise<AppSurface>;
1145
+ /** Open a host builtin page such as settings or downloads. */
1146
+ openBuiltin(page: BuiltinShellPage): Promise<BuiltinSurface>;
1147
+ /**
1148
+ * Open a declared surface with shell privileges — the same declaration
1149
+ * `lx.surface.openDeclared` opens, plus the keyed multi-instance form and
1150
+ * placement overrides.
1151
+ */
1152
+ openDeclared(id: string, options?: ShellOpenDeclaredOptions): Promise<DeclaredSurface>;
1153
+ /** Re-place a live declared surface: change its role or its edge. */
1154
+ reconfigure(id: string, patch: ShellSurfacePatch): Promise<void>;
1155
+ };
1156
+ export type ShellOpenAppOptions = {
1157
+ /** `main` occupies the primary content area; `aside` a companion region. */
1158
+ as: 'main' | 'aside';
1159
+ /** Preferred docking side. Only meaningful with `as: 'aside'`. */
1160
+ edge?: SurfaceEdge;
1161
+ /**
1162
+ * Configured page name from the target lxapp's `lxapp.json`. Omit it to
1163
+ * open that app's initial page. Full page routes are not supported.
1164
+ */
1165
+ page?: string;
1166
+ query?: PageQuery;
1167
+ /** Defaults to 'release'. */
1168
+ envVersion?: LxAppEnvVersion;
1169
+ targetVersion?: string;
1170
+ /** Stable identity for `lx.surface.get(key)`. */
1171
+ key?: string;
1172
+ };
1173
+ /**
1174
+ * The declared-surface options only the home lxapp may use.
1175
+ * Creating an extra instance and overriding a placement both mutate
1176
+ * shared shell composition, so they live here and not on
1177
+ * `lx.surface.openDeclared` — which consumes a declaration exactly as
1178
+ * the host authored it, and therefore takes no options at all.
1096
1179
  */
1097
- export type ShellActivator = {
1180
+ export type ShellOpenDeclaredOptions = {
1181
+ /**
1182
+ * Caller-owned identity, for `lx.surface.get(key)` later — the same key
1183
+ * every opener takes. It carries one extra power here: a declaration can
1184
+ * be opened more than once, and the key is which instance you mean, so a
1185
+ * new key creates one. 1 to 128 UTF-8 bytes. Declarations without
1186
+ * instantiable native providers reject it with `capability_missing`.
1187
+ */
1188
+ key?: string;
1189
+ /**
1190
+ * Open with a role other than the declaration's. Must be realizable by the
1191
+ * declared provider; a stable root rejects anything but `main`. Prefer this
1192
+ * over opening and then calling `reconfigure`, which would present the
1193
+ * wrong role first.
1194
+ */
1195
+ as?: 'main' | 'aside' | 'float';
1196
+ /** Preferred docking side when the effective role is `aside`. */
1197
+ edge?: SurfaceEdge;
1198
+ };
1199
+ /**
1200
+ * One app-declared shell sidebar action. It is a stateless command, not a
1201
+ * selectable navigation item: the shell invokes `onActivate` once and
1202
+ * does not infer a target or active state.
1203
+ */
1204
+ export type ShellSidebarAction = {
1205
+ /** Stable, non-empty id; unique across both header and footer actions. */
1098
1206
  id: string;
1207
+ /**
1208
+ * Initial host-owned region. Use `replace` to move an action. The header
1209
+ * takes at most two; everything else belongs in the footer.
1210
+ */
1211
+ placement: ShellSidebarActionPlacement;
1212
+ /**
1213
+ * Local lxapp-accessible icon. Use a bundled relative path such as
1214
+ * `public/settings.svg`, or an `lx://temp`, `lx://usercache`, or
1215
+ * `lx://userdata` path returned by LingXia file APIs. Native absolute paths,
1216
+ * parent traversal, `file:` URLs, and network URLs are rejected; download a
1217
+ * remote icon before registration. For portable rendering, prefer a square,
1218
+ * transparent, monochrome SVG or PNG designed for a 16-point visual.
1219
+ */
1099
1220
  icon: string;
1221
+ /**
1222
+ * Visible footer title and the tooltip/accessibility text for every
1223
+ * placement. Long footer labels are kept on one line and tail-truncated.
1224
+ */
1100
1225
  label: string;
1226
+ /** Visible but non-activatable when true. Defaults to false. */
1101
1227
  disabled?: boolean;
1228
+ /**
1229
+ * Called once for each enabled mouse, keyboard, accessibility, shortcut, or
1230
+ * automation activation. Explicitly open or navigate to the desired content.
1231
+ */
1102
1232
  onActivate: () => void;
1103
1233
  };
1104
- /** Mutable presentation fields for an existing activator. */
1105
- export type ShellActivatorUpdate = {
1234
+ /**
1235
+ * Where the host renders a sidebar action on desktop.
1236
+ * - `header`: icon-only, at most two actions; `label` supplies tooltip
1237
+ * and accessibility text. Hidden in the compact/collapsed shell.
1238
+ * - `footer`: icon and label in the expanded sidebar, icon-only in the
1239
+ * compact rail. The host wraps cells and scrolls after five visible
1240
+ * rows.
1241
+ * Apps cannot configure cell size, row, weight, color, or selected state.
1242
+ * Where an action lives in the sidebar.
1243
+ * `header` is the caption row beside the window controls: at most two
1244
+ * actions, for the ones a person reaches for constantly. Declaring a
1245
+ * third rejects the whole `replace` call rather than hiding one.
1246
+ * `footer` is unbounded and scrolls, and every entry stays visible at
1247
+ * any window size. Anything that must be findable belongs here.
1248
+ */
1249
+ export type ShellSidebarActionPlacement = 'header' | 'footer';
1250
+ /**
1251
+ * Mutable presentation fields for an existing sidebar action. The patch
1252
+ * must contain at least one field. Use `replace` to change `placement` or
1253
+ * `onActivate`.
1254
+ */
1255
+ export type ShellSidebarActionUpdate = {
1256
+ /** Replacement local icon, with the same path rules as registration. */
1106
1257
  icon?: string;
1258
+ /** Replacement non-empty visible/accessibility label. */
1107
1259
  label?: string;
1260
+ /** Whether the action remains visible but rejects activation. */
1108
1261
  disabled?: boolean;
1109
1262
  };
1110
- /** Shell chrome writer API (home lxapp only). */
1111
- export type ShellApi = {
1112
- activators: ShellActivatorsApi;
1263
+ /**
1264
+ * Role and edge overrides the home lxapp may apply to a live declared
1265
+ * surface. A stable root rejects non-main roles.
1266
+ */
1267
+ export type ShellSurfacePatch = {
1268
+ as?: 'main' | 'aside' | 'float';
1269
+ edge?: SurfaceEdge;
1113
1270
  };
1114
1271
  export type ShowActionSheetOptions = {
1115
1272
  itemList: string[];
@@ -1133,16 +1290,25 @@ export type ShowToastOptions = {
1133
1290
  mask?: boolean;
1134
1291
  position?: 'top' | 'center' | 'bottom';
1135
1292
  };
1136
- export type StatOptions = {
1137
- path: string;
1138
- };
1139
- /** Persistent key-value storage backed by the lxapp database. */
1293
+ /**
1294
+ * Asynchronous persistent key-value storage backed by the lxapp
1295
+ * database. Use `lx.fs` for path-based data.
1296
+ * `get<T>()` is an unchecked assertion at the call site. Pin every
1297
+ * key's shape once with `lx.getStorage<Schema>()` — that returns a
1298
+ * `TypedStorage<Schema>` instead of this untyped handle.
1299
+ */
1140
1300
  export type Storage = {
1141
- get(key: string): Promise<unknown>;
1301
+ /**
1302
+ * Reads a stored value. `T` is an unchecked assertion about the stored
1303
+ * shape, exactly like a `JSON.parse` boundary; a missing key resolves
1304
+ * `undefined`, which a stored `null` never does.
1305
+ */
1306
+ get<T = unknown>(key: string): Promise<T | undefined>;
1142
1307
  set(key: string, value: unknown): Promise<void>;
1143
1308
  delete(key: string): Promise<void>;
1144
1309
  clear(): Promise<void>;
1145
- list(prefix?: string): Promise<IterableIterator<string>>;
1310
+ /** Resolves every key, optionally filtered by prefix. */
1311
+ list(prefix?: string): Promise<string[]>;
1146
1312
  info(): Promise<StorageInfo>;
1147
1313
  };
1148
1314
  /** Current persistent-storage usage and configured limits. */
@@ -1157,61 +1323,65 @@ export type StreamSourceOptions = {
1157
1323
  duration?: number;
1158
1324
  params?: Record<string, unknown>;
1159
1325
  };
1160
- export type Surface = SurfaceHandle & {
1161
- readonly kind: 'overlay' | 'window';
1326
+ /**
1327
+ * Content-keyed surface composition, callable by any lxapp. Privileged
1328
+ * composition lives on `lx.shell`, so the namespace is the privilege.
1329
+ */
1330
+ export type SurfaceApi = {
1331
+ /** Open one of this lxapp's own pages as a float or a window. */
1332
+ openPage(page: string, options?: OpenPageOptions): Promise<PageSurface>;
1333
+ /** Open external content in the in-app browser. */
1334
+ openUrl(url: string, options?: OpenUrlOptions): Promise<TabSurface>;
1162
1335
  /**
1163
- * Last-known visibility, kept in sync with the native side via show/hide
1164
- * events. False once the surface has been closed. Safe to bind into
1165
- * declarative UI; for event-driven updates subscribe via `onShow`/`onHide`.
1336
+ * Open a surface the host declared in `lingxia.yaml`, with the placement
1337
+ * the declaration chose. Instance keys and placement overrides are shell
1338
+ * composition; they live on `lx.shell.openDeclared`.
1166
1339
  */
1167
- readonly visible: boolean;
1340
+ openDeclared(id: string): Promise<DeclaredSurface>;
1168
1341
  /**
1169
- * True until `close()` fires. After close the surface is detached and the
1170
- * page instance is being torn down; further `show()` / `hide()` calls will
1171
- * reject.
1342
+ * The live handle for a surface this lxapp opened **with a `key`**, found
1343
+ * by that key or by its `id`. Removes the need to cache handles in order
1344
+ * to reuse or close them. A surface opened without a `key` is not
1345
+ * addressable — nothing else refers to a runtime-assigned id, so nothing
1346
+ * registers it. A key you chose wins over an id it happens to spell.
1172
1347
  */
1173
- readonly alive: boolean;
1174
- /**
1175
- * Sends a message to the other side of a page surface.
1176
- *
1177
- * For the opener this targets the opened page. For the opened page this
1178
- * targets the opener. URL surfaces have no page-side receiver.
1179
- */
1180
- postMessage(message: unknown): void;
1181
- onMessage(handler: (message: unknown) => void): () => void;
1182
- onClose(handler: (event: SurfaceClosedEvent) => void): () => void;
1348
+ get(keyOrId: string): AnySurface | undefined;
1183
1349
  /**
1184
- * Fires when the surface transitions to visible, regardless of whether
1185
- * `show()` was called on this side or on the peer. Returns an unsubscribe
1186
- * function. Only fires on real state changes — calling `show()` on an
1187
- * already-visible surface is a no-op for listeners.
1350
+ * Observe this presentation's viewport. Invoked immediately with the
1351
+ * current context, then again whenever it changes. Returns an unsubscribe
1352
+ * function.
1188
1353
  */
1189
- onShow(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1354
+ onContext(handler: (context: SurfaceContext) => void): () => void;
1355
+ };
1356
+ /** What every surface handle carries, whatever opened it. */
1357
+ export type SurfaceBase = {
1358
+ readonly kind: SurfaceKind;
1359
+ readonly id: string;
1360
+ /** The caller-supplied identity, when this surface was opened with one. */
1361
+ readonly key?: string;
1190
1362
  /**
1191
- * Fires when the surface transitions to hidden, regardless of which side
1192
- * triggered it. Returns an unsubscribe function. Only fires on real state
1193
- * changes.
1363
+ * The placement the host produced, which an ordered preference may narrow.
1364
+ * Live rather than a snapshot: `lx.shell.reconfigure` updates it on the
1365
+ * handle you already hold.
1194
1366
  */
1195
- onHide(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1196
- close(): Promise<void>;
1367
+ readonly realized: SurfacePlacement;
1368
+ /** True until `close()` fires; afterwards the page instance is torn down. */
1369
+ readonly alive: boolean;
1197
1370
  /**
1198
- * Toggle the surface to visible without tearing it down. The page instance
1199
- * and its state survive a hide / show round-trip — only close() actually
1200
- * destroys the surface and fires the onClose listener. Idempotent: calling
1201
- * on an already-visible surface resolves without firing `onShow`.
1371
+ * Last-known visibility, kept in sync with the native side. Safe to bind
1372
+ * into declarative UI; for event-driven updates use `onShow` / `onHide`.
1202
1373
  */
1203
- show(): Promise<void>;
1374
+ readonly visible: boolean;
1204
1375
  /**
1205
- * Hide the surface without destroying it. The page instance stays mounted,
1206
- * so a subsequent show() restores the same scroll position, form input,
1207
- * and JS state. Hidden surfaces still receive postMessage but are not
1208
- * visible to the user. Idempotent.
1376
+ * Destroy the surface. The stable root main cannot be closed. Repeated
1377
+ * calls after a successful close are idempotent.
1209
1378
  */
1210
- hide(): Promise<void>;
1379
+ close(): Promise<void>;
1380
+ onClose(handler: (event: SurfaceClosedEvent) => void): () => void;
1211
1381
  };
1212
1382
  /**
1213
1383
  * Surfaces (docked asides, floats, windows, browser tabs, declared surfaces)
1214
- * and the desktop tray — the types behind `lx.openSurface`, `lx.onSurfaceContext`,
1384
+ * and the desktop tray — the types behind `lx.surface`, `lx.shell`,
1215
1385
  * and `lx.tray`.
1216
1386
  */
1217
1387
  export type SurfaceCloseReason = 'user' | 'programmatic' | 'owner_closed' | 'app_closed' | 'failed'
@@ -1223,11 +1393,10 @@ export type SurfaceCloseReason = 'user' | 'programmatic' | 'owner_closed' | 'app
1223
1393
  | 'reclaimed' | 'unknown';
1224
1394
  export type SurfaceClosedEvent = {
1225
1395
  id: string;
1226
- kind: 'overlay' | 'window';
1227
1396
  reason: SurfaceCloseReason;
1228
1397
  };
1229
1398
  /**
1230
- * The current surface viewport context, delivered to `lx.onSurfaceContext()`
1399
+ * The current surface viewport context, delivered to `lx.surface.onContext()`
1231
1400
  * so an lxapp can self-adapt (e.g. switch column count by `sizeClass`).
1232
1401
  */
1233
1402
  export type SurfaceContext = {
@@ -1238,34 +1407,51 @@ export type SurfaceContext = {
1238
1407
  /** Actual surface viewport height in logical pixels. */
1239
1408
  height: number;
1240
1409
  };
1241
- /** Edge an aside docks to; the Host decides the realized form by screen size. */
1410
+ /**
1411
+ * Preferred docking side for an aside when the Host has room for a docked
1412
+ * layout. `aside` selects the companion region; `edge` selects a side within
1413
+ * it. Compact Hosts may reproject the same aside as a full-screen overlay.
1414
+ */
1242
1415
  export type SurfaceEdge = 'left' | 'right' | 'top' | 'bottom';
1416
+ /**
1417
+ * A surface rejection. The runtime carries the surface code on
1418
+ * `data.code` — `code` itself is the transport-level host code, shared
1419
+ * with every other `lx` rejection — so read it with
1420
+ * `surfaceErrorCode(error)` and never parse the message.
1421
+ * ```ts
1422
+ * import { surfaceErrorCode } from 'lingxia-types/error';
1423
+ * catch (error) {
1424
+ * if (surfaceErrorCode(error) === 'unsupported_placement') { … }
1425
+ * }
1426
+ * ```
1427
+ */
1428
+ export type SurfaceError = Error & {
1429
+ readonly data?: {
1430
+ readonly code?: SurfaceErrorCode;
1431
+ };
1432
+ };
1433
+ /**
1434
+ * Why a surface operation was refused. Carried as `code` on every
1435
+ * `SurfaceError`, so no caller has to match on message text.
1436
+ */
1437
+ export type SurfaceErrorCode = /** The placement cannot be realized by this host build. */ 'unsupported_placement'
1438
+ /** A privileged operation was called by an lxapp other than the home lxapp. */
1439
+ | 'denied'
1440
+ /** No such declared surface, lxapp, or builtin page. */
1441
+ | 'not_declared'
1442
+ /** The arguments are malformed or combine options that cannot apply together. */
1443
+ | 'invalid_arg'
1444
+ /** The target is already open in a role this call cannot change. */
1445
+ | 'already_open_other_role'
1446
+ /** The surface has been closed; the handle is detached. */
1447
+ | 'closed'
1448
+ /** The host lacks a capability the request needs, such as an instantiable
1449
+ * native provider for a keyed surface. */
1450
+ | 'capability_missing'
1451
+ /** The operation reached the host and failed there. */
1452
+ | 'failed';
1243
1453
  /** Where a float popup anchors (default `center`). */
1244
1454
  export type SurfaceFloatPosition = 'center' | 'top' | 'bottom' | 'left' | 'right';
1245
- export type SurfaceHandle = {
1246
- readonly id: string;
1247
- /** Standalone windows have no role in the primary shell graph. */
1248
- readonly role?: SurfaceRole;
1249
- readonly presentation: SurfacePresentation;
1250
- readonly visible: boolean;
1251
- readonly alive: boolean;
1252
- /**
1253
- * Show a host-managed surface. Dynamic page/url surfaces return a Promise;
1254
- * host-declared surfaces may complete synchronously.
1255
- */
1256
- show(): void | Promise<void>;
1257
- /**
1258
- * Hide without destroying user-visible state when the platform supports it.
1259
- */
1260
- hide(): void | Promise<void>;
1261
- /**
1262
- * Destroy the live surface. Repeated close calls are idempotent.
1263
- */
1264
- close(): void | Promise<void>;
1265
- onShow(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1266
- onHide(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1267
- onClose(handler: (event: SurfaceClosedEvent) => void): () => void;
1268
- };
1269
1455
  /** Native interaction supplied by the host around page content. */
1270
1456
  export type SurfaceInteraction = {
1271
1457
  /** Show the standard circular close button. Default `false`. */
@@ -1275,27 +1461,212 @@ export type SurfaceInteraction = {
1275
1461
  /** Block interaction with content below. Default `false`. */
1276
1462
  modal?: boolean;
1277
1463
  };
1464
+ /**
1465
+ * Where the content came from. The discriminant on every surface
1466
+ * handle, so `AnySurface` narrows without a runtime `typeof` check.
1467
+ */
1468
+ export type SurfaceKind = 'page' | 'declared' | 'app' | 'tab' | 'builtin';
1469
+ /** Two-way messaging, available when both sides are lxapp pages. */
1470
+ export type SurfaceMessaging = {
1471
+ /**
1472
+ * Send to the other side. For the opener this targets the opened page;
1473
+ * for the opened page it targets the opener.
1474
+ */
1475
+ postMessage(message: unknown): void;
1476
+ onMessage(handler: (message: unknown) => void): () => void;
1477
+ };
1478
+ /**
1479
+ * What the host actually produced. Reported by `realized`, which is
1480
+ * how a caller reads the outcome of an ordered placement preference.
1481
+ */
1482
+ export type SurfacePlacement = 'main' | 'aside' | 'float' | 'window' | 'tab';
1278
1483
  export type SurfacePresentation = 'main' | 'dock' | 'overlay' | 'popover' | 'sheet' | 'window';
1279
1484
  export type SurfaceRole = 'main' | 'aside' | 'float';
1485
+ /** Surfaces the host can hide and restore without losing page state. */
1486
+ export type SurfaceShowable = {
1487
+ /**
1488
+ * Restore a hidden surface. The page instance survived, so scroll
1489
+ * position, form input, and JS state come back with it. Idempotent.
1490
+ */
1491
+ show(): Promise<void>;
1492
+ /**
1493
+ * Hide without destroying. Main surfaces cannot be hidden and reject.
1494
+ * Idempotent.
1495
+ */
1496
+ hide(): Promise<void>;
1497
+ /** Fires on a real transition to visible, whichever side drove it. */
1498
+ onShow(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1499
+ /** Fires on a real transition to hidden, whichever side drove it. */
1500
+ onHide(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1501
+ };
1280
1502
  /**
1281
1503
  * Detail payload for `onShow` / `onHide` events. `source` identifies which
1282
1504
  * Surface object initiated the visibility change so observers can
1283
1505
  * distinguish self-driven transitions from peer-driven ones (e.g. an opener
1284
1506
  * UI that wants to update its own button state only when the page side
1285
- * toggled visibility).
1507
+ * toggled visibility). `shell` identifies a host-driven main switch.
1286
1508
  */
1287
1509
  export type SurfaceVisibilityEvent = {
1288
1510
  id: string;
1289
- kind: 'overlay' | 'window';
1290
- source: 'opener' | 'page';
1511
+ source: 'opener' | 'page' | 'shell';
1291
1512
  };
1292
1513
  export type SwitchTabOptions = PageTargetOptions;
1293
- /** Native system Downloads path. Do not pass this to `FileManager`. */
1514
+ /** Native system Downloads path. Do not pass this to `lx.fs`. */
1294
1515
  export type SystemDownloadsPath = string & {
1295
1516
  readonly [systemDownloadsPathBrand]: 'system-downloads-path';
1296
1517
  };
1297
- export type TabBarRedDotOptions = {
1518
+ export type TabBarApi = globalThis.TabBarApi;
1519
+ export type TabBarItemPatch = {
1298
1520
  index: number;
1521
+ text?: string | null;
1522
+ iconPath?: string | null;
1523
+ selectedIconPath?: string | null;
1524
+ badge?: string | null;
1525
+ redDot?: boolean;
1526
+ };
1527
+ export type TabBarPatch = {
1528
+ visibility?: TabBarVisibilityPreference;
1529
+ style?: TabBarStylePatch | null;
1530
+ items?: readonly TabBarItemPatch[];
1531
+ };
1532
+ export type TabBarStylePatch = {
1533
+ foregroundColor?: string | null;
1534
+ selectedForegroundColor?: string | null;
1535
+ };
1536
+ export type TabBarVisibilityPreference = 'auto' | 'visible' | 'hidden';
1537
+ /** External content in the in-app browser. */
1538
+ export type TabSurface = SurfaceBase & {
1539
+ readonly kind: 'tab';
1540
+ readonly realized: 'tab' | 'aside';
1541
+ /**
1542
+ * `tab` when this handle owns exactly the tab it opened, and `close()` /
1543
+ * `activate()` act on it. `group` when the browser chrome owns the tab
1544
+ * strip: the content is open, but control belongs to that chrome, so both
1545
+ * methods reject with `unsupported_placement`. Branch on this rather than
1546
+ * on the old platform-dependent `null`.
1547
+ */
1548
+ readonly scope: 'tab' | 'group';
1549
+ /** Bring this tab to the front of its browser. `scope: 'group'` rejects. */
1550
+ activate(): Promise<void>;
1551
+ };
1552
+ export type TerminalApi = {
1553
+ /** Saved terminal settings, revision-checked on write. */
1554
+ readonly settings: TerminalSettingsApi;
1555
+ /** Installed color schemes, plus import and live preview. */
1556
+ readonly colorSchemes: TerminalColorSchemesApi;
1557
+ /** Terminal fonts installed on this machine. */
1558
+ readonly fonts: TerminalFontsApi;
1559
+ /** Windows-only optional inline-image compatibility runtime. */
1560
+ readonly windows?: WindowsTerminalApi;
1561
+ };
1562
+ export type TerminalColorScheme = {
1563
+ name?: string;
1564
+ background: string;
1565
+ foreground: string;
1566
+ cursorColor?: string;
1567
+ selectionBackground?: string;
1568
+ selectionForeground?: string;
1569
+ black: string;
1570
+ red: string;
1571
+ green: string;
1572
+ yellow: string;
1573
+ blue: string;
1574
+ purple: string;
1575
+ cyan: string;
1576
+ white: string;
1577
+ brightBlack: string;
1578
+ brightRed: string;
1579
+ brightGreen: string;
1580
+ brightYellow: string;
1581
+ brightBlue: string;
1582
+ brightPurple: string;
1583
+ brightCyan: string;
1584
+ brightWhite: string;
1585
+ };
1586
+ export type TerminalColorSchemeDetails = {
1587
+ name: string;
1588
+ source: 'builtIn' | 'imported';
1589
+ scheme: TerminalColorScheme;
1590
+ };
1591
+ export type TerminalColorSchemesApi = {
1592
+ list(): Promise<TerminalColorSchemeDetails[]>;
1593
+ import(options: {
1594
+ text: string;
1595
+ name?: string;
1596
+ /** Existing names are rejected unless overwrite is explicit. */
1597
+ overwrite?: boolean;
1598
+ }): Promise<TerminalColorSchemeDetails>;
1599
+ createPreview(): TerminalPreviewController;
1600
+ };
1601
+ export type TerminalFontSettings = {
1602
+ /** Ordered candidates; the first installed monospaced family wins. */
1603
+ family: string[];
1604
+ size: number;
1605
+ lineHeight: number;
1606
+ ligatures: boolean;
1607
+ };
1608
+ export type TerminalFontsApi = {
1609
+ list(): Promise<InstalledTerminalFont[]>;
1610
+ };
1611
+ export type TerminalPreviewController = {
1612
+ /** Preview a stored name or an unpersisted scheme. Last request wins. */
1613
+ show(scheme: string | TerminalColorScheme): Promise<void>;
1614
+ /** Restore saved settings only when this controller owns the preview. */
1615
+ clear(): Promise<void>;
1616
+ /** Idempotently clear and retire this controller. */
1617
+ close(): Promise<void>;
1618
+ };
1619
+ export type TerminalSettingsApi = {
1620
+ get(): Promise<TerminalSettingsSnapshot>;
1621
+ update(patch: TerminalSettingsPatch, options: {
1622
+ ifRevision: number;
1623
+ }): Promise<TerminalSettingsSnapshot>;
1624
+ reset(options: {
1625
+ ifRevision: number;
1626
+ scope?: 'font' | 'theme';
1627
+ }): Promise<TerminalSettingsSnapshot>;
1628
+ /** Fires after saved settings, effective appearance, or fonts change. */
1629
+ onChange(listener: (snapshot: TerminalSettingsSnapshot) => void): () => void;
1630
+ };
1631
+ export type TerminalSettingsPatch = {
1632
+ font?: Partial<TerminalFontSettings>;
1633
+ theme?: Partial<TerminalThemeSettings>;
1634
+ };
1635
+ export type TerminalSettingsSnapshot = {
1636
+ /** Monotonic process revision used by update/reset compare-and-swap. */
1637
+ revision: number;
1638
+ /** Framework defaults. */
1639
+ defaults: TerminalSettingsValue;
1640
+ /** User-authored fields only. */
1641
+ overrides: TerminalSettingsPatch;
1642
+ /** Resolved configuration after all valid layers. */
1643
+ value: TerminalSettingsValue;
1644
+ effective: {
1645
+ /** Host appearance before applying terminal.theme.mode. */
1646
+ systemAppearance: 'light' | 'dark';
1647
+ appearance: 'light' | 'dark';
1648
+ colorScheme: string | null;
1649
+ font: {
1650
+ family: string;
1651
+ missing: string[];
1652
+ fellBack: boolean;
1653
+ };
1654
+ };
1655
+ warnings: TerminalSettingsWarning[];
1656
+ };
1657
+ export type TerminalSettingsValue = {
1658
+ font: TerminalFontSettings;
1659
+ theme: TerminalThemeSettings;
1660
+ };
1661
+ export type TerminalSettingsWarning = {
1662
+ code: 'invalidUserFile' | 'missingColorScheme';
1663
+ message: string;
1664
+ };
1665
+ export type TerminalThemeMode = 'system' | 'light' | 'dark';
1666
+ export type TerminalThemeSettings = {
1667
+ mode: TerminalThemeMode;
1668
+ light: string;
1669
+ dark: string;
1299
1670
  };
1300
1671
  export type TrayApi = globalThis.TrayApi;
1301
1672
  /**
@@ -1320,11 +1691,17 @@ export type TrayMenuSeparator = {
1320
1691
  export type UpdateFailedInfo = UpdateReadyInfo & {
1321
1692
  error?: string;
1322
1693
  };
1323
- /** Runtime update APIs. */
1694
+ /**
1695
+ * Callback-based updates for this lxapp's bundle. Available to every
1696
+ * lxapp. To update the native host app, the home lxapp uses the
1697
+ * task-based `lx.app.checkUpdate()` API instead.
1698
+ */
1324
1699
  export type UpdateManager = {
1325
1700
  applyUpdate(): void;
1326
- onUpdateReady(callback: (info: UpdateReadyInfo) => void): void;
1327
- onUpdateFailed(callback: (info: UpdateFailedInfo) => void): void;
1701
+ /** Subscribes to a ready update and returns the unsubscribe fn. */
1702
+ onUpdateReady(callback: (info: UpdateReadyInfo) => void): () => void;
1703
+ /** Subscribes to a failed update and returns the unsubscribe fn. */
1704
+ onUpdateFailed(callback: (info: UpdateFailedInfo) => void): () => void;
1328
1705
  };
1329
1706
  export type UpdateReadyInfo = {
1330
1707
  version?: string;
@@ -1335,34 +1712,90 @@ export type UploadIteratorResult = {
1335
1712
  done: boolean;
1336
1713
  value?: UploadProgressEvent;
1337
1714
  };
1715
+ /**
1716
+ * Upload options. The file streams from disk, so the size ceiling is
1717
+ * the remote's, not memory.
1718
+ * `bodyMode` picks the body shape, and with it which of the other
1719
+ * fields apply:
1720
+ * - `multipart` (default) wraps the file in a `multipart/form-data`
1721
+ * envelope beside the `formData` text fields — what an ordinary form
1722
+ * endpoint parses. `name`, `fileName`, and `formData` describe that
1723
+ * envelope.
1724
+ * - `raw` sends the file bytes as the entire body. Presigned
1725
+ * object-storage URLs (S3, OSS, Azure Blob) need this: a multipart
1726
+ * envelope would be stored verbatim as the object's contents,
1727
+ * boundary lines and all. `name` and `formData` are then rejected
1728
+ * rather than silently dropped, and `fileName` is ignored.
1729
+ * @example
1730
+ * ```ts
1731
+ * // A presigned URL is signed for one method and one Content-Type,
1732
+ * // so both have to match whatever the signer used.
1733
+ * const task = lx.uploadFile({
1734
+ * url: presignedUrl,
1735
+ * filePath: 'lx://media/clip.mp4',
1736
+ * method: 'PUT',
1737
+ * bodyMode: 'raw',
1738
+ * mimeType: 'video/mp4',
1739
+ * });
1740
+ * for await (const event of task) render(event.progress);
1741
+ * const { statusCode } = await task;
1742
+ * ```
1743
+ */
1338
1744
  export type UploadOptions = {
1339
1745
  /** HTTP(S) destination URL. */
1340
1746
  url: string;
1341
1747
  /** Local file path or runtime-managed URI to upload. */
1342
1748
  filePath: string;
1343
- /** Multipart field name. Default: `file`. */
1749
+ /**
1750
+ * HTTP method. Default: `POST`.
1751
+ * A presigned URL is signed for exactly one method, usually `PUT`.
1752
+ */
1753
+ method?: 'POST' | 'PUT' | 'PATCH';
1754
+ /**
1755
+ * How the file bytes are framed. Default: `multipart`.
1756
+ * `raw` sends them as the whole body under a `Content-Length` taken from
1757
+ * the file itself, which is what presigned endpoints require.
1758
+ */
1759
+ bodyMode?: 'multipart' | 'raw';
1760
+ /** Name of the multipart part carrying the file. Default: `file`. Multipart only. */
1344
1761
  name?: string;
1345
1762
  /**
1346
1763
  * Optional request headers.
1347
1764
  * Restricted headers such as `Referer` are ignored by the runtime.
1765
+ * `Content-Type` is yours to set only under `bodyMode: 'raw'`, where it
1766
+ * wins over `mimeType`; a multipart body owns the header, because it
1767
+ * carries the part boundary.
1348
1768
  */
1349
1769
  headers?: Record<string, string>;
1350
- /** Optional extra `multipart/form-data` text fields. */
1770
+ /** Text fields sent alongside the file in the envelope. Multipart only. */
1351
1771
  formData?: Record<string, string>;
1352
1772
  /** Request timeout in milliseconds. */
1353
1773
  timeout?: number;
1354
- /** Override multipart filename. */
1774
+ /** Filename announced for the file part. Defaults to the file's own name. Multipart only. */
1355
1775
  fileName?: string;
1356
- /** Override file MIME type. */
1776
+ /**
1777
+ * File MIME type. Types the file part under `multipart`; becomes the
1778
+ * request `Content-Type` under `raw`, where it defaults to
1779
+ * `application/octet-stream`.
1780
+ */
1357
1781
  mimeType?: string;
1358
1782
  /** Optional abort signal. */
1359
1783
  signal?: AbortSignal;
1360
1784
  };
1361
1785
  export type UploadProgressEvent = {
1786
+ /** `completed` and `canceled` are terminal; iteration ends after either. */
1362
1787
  kind: 'progress' | 'canceled' | 'completed';
1788
+ /** Bytes handed to the socket so far, envelope included under `multipart`. */
1363
1789
  uploadedBytes?: number;
1790
+ /**
1791
+ * Bytes the whole request body will carry. Equals the file size under
1792
+ * `bodyMode: 'raw'`; under `multipart` it also covers the envelope, so it
1793
+ * runs slightly above the file size.
1794
+ */
1364
1795
  totalBytes?: number;
1796
+ /** `uploadedBytes / totalBytes`, absent while the total is unknown or zero. */
1365
1797
  progress?: number;
1798
+ /** Present on `completed` only. */
1366
1799
  result?: UploadResult;
1367
1800
  };
1368
1801
  export type UploadResult = {
@@ -1452,31 +1885,47 @@ export type VideoInfo = {
1452
1885
  */
1453
1886
  path: string;
1454
1887
  };
1888
+ export type VisibilityPreference = 'auto' | 'hidden';
1455
1889
  export type WifiConnectedCallback = (info: WifiConnectedInfo) => void;
1456
1890
  export type WifiConnectedInfo = WifiInfo & {
1457
1891
  connected: boolean;
1458
1892
  state: string;
1459
1893
  };
1894
+ /**
1895
+ * Window decoration. `system` is the standard title bar. `full`
1896
+ * extends the page to the window edge while keeping the system
1897
+ * minimize, maximize, resize, and drag affordances — the runtime owns
1898
+ * a native drag strip across the top and publishes its height as
1899
+ * `topInset` on the page-chrome snapshot, so a page that does nothing
1900
+ * to opt in still cannot trap the user.
1901
+ */
1902
+ export type WindowChrome = 'system' | 'full';
1460
1903
  export type WindowSurfaceSize = {
1461
1904
  /** Initial window width in logical pixels. */
1462
1905
  width?: number;
1463
1906
  /** Initial window height in logical pixels. */
1464
1907
  height?: number;
1465
1908
  };
1466
- export type WriteBinaryFileOptions = {
1467
- filePath: string;
1468
- data: BinaryFileData;
1469
- encoding?: never;
1470
- /** Defaults to false. */
1471
- overwrite?: boolean;
1472
- };
1473
- export type WriteFileOptions = WriteTextFileOptions | WriteBinaryFileOptions;
1474
- export type WriteTextFileOptions = {
1475
- filePath: string;
1476
- data: string;
1477
- encoding?: 'utf8' | 'base64';
1478
- /** Defaults to false. */
1479
- overwrite?: boolean;
1909
+ export type WindowsTerminalApi = {
1910
+ status(): Promise<WindowsTerminalInlineImageStatus>;
1911
+ /** Verify and install the fixed Microsoft ConPTY package from lxapp temp storage. */
1912
+ install(options: {
1913
+ path: string;
1914
+ }): Promise<WindowsTerminalInlineImageStatus>;
1915
+ /** Select the installed runtime for new terminal sessions. */
1916
+ setEnabled(options: {
1917
+ enabled: boolean;
1918
+ }): Promise<WindowsTerminalInlineImageStatus>;
1919
+ };
1920
+ export type WindowsTerminalInlineImageStatus = {
1921
+ enabled: boolean;
1922
+ installed: boolean;
1923
+ package: {
1924
+ version: string;
1925
+ url: string;
1926
+ sha256: string;
1927
+ bytes: number;
1928
+ };
1480
1929
  };
1481
1930
  /** Host app base information. */
1482
1931
  export interface AppBaseInfo {
@@ -1500,6 +1949,10 @@ export interface AppBaseInfo {
1500
1949
  version: string;
1501
1950
  SDKVersion: string;
1502
1951
  }
1952
+ export interface AppearanceState {
1953
+ preference: AppearancePreference;
1954
+ resolved: ResolvedAppearance;
1955
+ }
1503
1956
  /** Device info APIs. */
1504
1957
  export interface DeviceInfo {
1505
1958
  brand: string;
@@ -1546,43 +1999,11 @@ export interface LxAppInfo {
1546
1999
  version: string;
1547
2000
  releaseType: LxAppReleaseType;
1548
2001
  }
1549
- /** Options for removing TabBar badge */
1550
- export interface RemoveTabBarBadgeOptions {
1551
- index: number;
1552
- }
1553
2002
  export interface ScreenInfo {
1554
2003
  width: number;
1555
2004
  height: number;
1556
2005
  scale: number;
1557
2006
  }
1558
- /** Options for setNavigationBarColor */
1559
- export interface SetNavigationBarColorOptions {
1560
- frontColor: string;
1561
- backgroundColor: string;
1562
- }
1563
- /** Options for setNavigationBarTitle */
1564
- export interface SetNavigationBarTitleOptions {
1565
- title: string;
1566
- }
1567
- /** Options for setting TabBar badge */
1568
- export interface SetTabBarBadgeOptions {
1569
- index: number;
1570
- text: string;
1571
- }
1572
- /** Options for setting TabBar item */
1573
- export interface SetTabBarItemOptions {
1574
- index: number;
1575
- text?: string;
1576
- iconPath?: string;
1577
- selectedIconPath?: string;
1578
- }
1579
- /** Options for setting TabBar style */
1580
- export interface SetTabBarStyleOptions {
1581
- color?: string;
1582
- selectedColor?: string;
1583
- backgroundColor?: string;
1584
- borderStyle?: string;
1585
- }
1586
2007
  /** System setting status */
1587
2008
  export interface SystemSettingInfo {
1588
2009
  bluetoothEnabled: boolean;
@@ -1609,18 +2030,6 @@ export declare class DirEntry {
1609
2030
  readonly isDirectory: boolean;
1610
2031
  readonly isSymlink: boolean;
1611
2032
  }
1612
- export declare class FileManager {
1613
- private constructor();
1614
- exists(options: ExistsOptions): Promise<boolean>;
1615
- stat(options: StatOptions): Promise<FileStats>;
1616
- readDir(options: ReadDirOptions): Promise<AsyncIterableIterator<DirEntry>>;
1617
- mkdir(options: MkdirOptions): Promise<void>;
1618
- readFile(options: never): Promise<never>;
1619
- writeFile(options: WriteFileOptions): Promise<void>;
1620
- copyFile(options: CopyFileOptions): Promise<void>;
1621
- rename(options: RenameOptions): Promise<void>;
1622
- remove(options: RemoveOptions): Promise<void>;
1623
- }
1624
2033
  export declare class JSMessagePort {
1625
2034
  constructor();
1626
2035
  static postMessage(payload: any): void;
@@ -1637,8 +2046,10 @@ export declare class JSUpdateManager {
1637
2046
  constructor();
1638
2047
  /** Apply update by restarting the app */
1639
2048
  applyUpdate(): void;
1640
- onUpdateReady(cb: (...args: any[]) => any): void;
1641
- onUpdateFailed(cb: (...args: any[]) => any): void;
2049
+ /** Subscribes to a ready update and returns the unsubscribe fn. */
2050
+ onUpdateReady(cb: (...args: any[]) => any): (...args: any[]) => any;
2051
+ /** Subscribes to a failed update and returns the unsubscribe fn. */
2052
+ onUpdateFailed(cb: (...args: any[]) => any): (...args: any[]) => any;
1642
2053
  }
1643
2054
  export declare class JSVideoContext {
1644
2055
  constructor();
@@ -1650,6 +2061,64 @@ export declare class JSVideoContext {
1650
2061
  exitFullScreen(): void;
1651
2062
  setStreamSource(options: StreamSourceOptions): void;
1652
2063
  }
2064
+ export declare class LxFile {
2065
+ private constructor();
2066
+ /** The path supplied to `lx.fs.file`. */
2067
+ readonly path: string;
2068
+ /** Read the complete file as strict UTF-8 text. */
2069
+ text(): Promise<string>;
2070
+ /**
2071
+ * Read and parse the complete file as JSON. Stays `unknown`: a class
2072
+ * method cannot carry a type parameter through the binding, so unlike
2073
+ * `lx.getStorage().get<T>()` the assertion is spelled `as` at the call
2074
+ * site rather than passed in.
2075
+ */
2076
+ json(): Promise<unknown>;
2077
+ /** Read the complete file as a Base64 string. */
2078
+ base64(): Promise<string>;
2079
+ /** Read the complete file as bytes. */
2080
+ bytes(): Promise<Uint8Array>;
2081
+ /** Read the complete file as an ArrayBuffer. */
2082
+ arrayBuffer(): Promise<ArrayBuffer>;
2083
+ /** Test whether this managed path currently exists. */
2084
+ exists(): Promise<boolean>;
2085
+ /** Read metadata for this managed path. */
2086
+ stat(): Promise<FileStats>;
2087
+ }
2088
+ declare global {
2089
+ interface AppearanceApi {
2090
+ /** Read the appearance preference and the light/dark value it resolves to. */
2091
+ get(): AppearanceState;
2092
+ /** Set the appearance preference to `auto`, `light`, or `dark`. */
2093
+ set(preference: AppearancePreference): Promise<void>;
2094
+ }
2095
+ }
2096
+ declare global {
2097
+ interface FileSystemApi {
2098
+ /**
2099
+ * Create a lazy reference to a LingXia-managed path.
2100
+ * Relative paths resolve under `lx.env.USER_DATA_PATH`. Creating a reference
2101
+ * does not require the path to exist.
2102
+ */
2103
+ file(path: string): LxFile;
2104
+ /** Test whether a managed path currently exists. */
2105
+ exists(path: string): Promise<boolean>;
2106
+ /** Read metadata for a managed path. */
2107
+ stat(path: string): Promise<FileStats>;
2108
+ /** The direct children of a managed directory. */
2109
+ readDir(path: string): Promise<DirEntry[]>;
2110
+ /** Create a managed directory. */
2111
+ mkdir(path: string, options?: FsMkdirOptions): Promise<void>;
2112
+ /** Write UTF-8 text or bytes to a managed file. */
2113
+ write(path: string, data: string, options?: FsWriteOptions): Promise<void>;
2114
+ /** Copy a managed file. */
2115
+ copy(source: string, destination: string, options?: FsCopyOptions): Promise<void>;
2116
+ /** Rename or move a managed file or directory. */
2117
+ rename(source: string, destination: string, options?: FsRenameOptions): Promise<void>;
2118
+ /** Remove a managed file or directory. */
2119
+ remove(path: string, options?: FsRemoveOptions): Promise<void>;
2120
+ }
2121
+ }
1653
2122
  declare global {
1654
2123
  interface HostAppApi {
1655
2124
  /**
@@ -1670,7 +2139,19 @@ declare global {
1670
2139
  */
1671
2140
  checkUpdate(): Promise<HostAppUpdateCheckResult>;
1672
2141
  readonly envVersion: HostAppEnvVersion;
2142
+ /**
2143
+ * Read the host app's identity: locale, display language, OS, product name,
2144
+ * product version, and SDK runtime version.
2145
+ */
1673
2146
  getBaseInfo(): AppBaseInfo;
2147
+ /**
2148
+ * Follow the host's effective display language.
2149
+ * `getBaseInfo().displayLanguage` answers what it is now; this answers when it
2150
+ * changes. Logic needs both because the strings it hands to native chrome —
2151
+ * navigation bar titles, tab bar labels, modal and action-sheet text — are the
2152
+ * app's own, and nothing re-renders them on its behalf.
2153
+ */
2154
+ onDisplayLanguageChange(callback: (language: string) => void): () => void;
1674
2155
  /**
1675
2156
  * Exit the host app immediately without a confirmation dialog.
1676
2157
  * If the user should confirm first, call `lx.showModal(...)` and invoke this
@@ -1689,14 +2170,31 @@ declare global {
1689
2170
  declare global {
1690
2171
  interface Lx {
1691
2172
  readonly app: HostAppApi;
2173
+ /**
2174
+ * Whether this host exposes a capability to this Logic context, right now.
2175
+ * Synchronous, because it is meant to be called from render paths. The answer
2176
+ * is live and may be stale by the time you act on it — it is an affordance for
2177
+ * deciding what to render, not a replacement for handling a rejection.
2178
+ * `{ capability: 'surface', value: 'aside' }` in particular changes when a
2179
+ * desktop window crosses the compact breakpoint; pair it with
2180
+ * `lx.surface.onContext` instead of polling. The answer is per runtime context:
2181
+ * a context that does not expose an API reports false for it.
2182
+ */
2183
+ supports(query: LxCapabilityQuery): boolean;
2184
+ /** Vibrate briefly, where the device has a vibrator. */
1692
2185
  vibrateShort(): boolean;
2186
+ /** Vibrate for a longer pulse, where the device has a vibrator. */
1693
2187
  vibrateLong(): boolean;
2188
+ /** Hand a number to the system dialer; the user still places the call. */
1694
2189
  makePhoneCall(options: MakePhoneCallOptions): boolean;
2190
+ /** Read the device and OS facts this host reports. */
1695
2191
  getDeviceInfo(): DeviceInfo;
2192
+ /** Read the screen geometry and pixel ratio this host reports. */
1696
2193
  getScreenInfo(): ScreenInfo;
2194
+ /** Read connectivity right now: whether it is connected, its type, and addresses. */
1697
2195
  getNetworkInfo(): Promise<NetworkInfo>;
1698
- onNetworkChange(callback: NetworkChangeCallback): void;
1699
- offNetworkChange(callback?: NetworkChangeCallback): void;
2196
+ /** Subscribes to network changes and returns the unsubscribe fn. */
2197
+ onNetworkChange(callback: NetworkChangeCallback): () => void;
1700
2198
  /** Initialize WiFi module */
1701
2199
  startWifi(): Promise<void>;
1702
2200
  /** Stop WiFi module */
@@ -1711,13 +2209,27 @@ declare global {
1711
2209
  getWifiList(): Promise<WifiInfo[]>;
1712
2210
  /** Get connected WiFi info */
1713
2211
  getConnectedWifi(): Promise<WifiInfo>;
1714
- onWifiConnected(callback: WifiConnectedCallback): void;
1715
- offWifiConnected(callback?: WifiConnectedCallback): void;
2212
+ /** Subscribes to WiFi connection events and returns the unsubscribe fn. */
2213
+ onWifiConnected(callback: WifiConnectedCallback): () => void;
2214
+ /**
2215
+ * Lock this lxapp to `portrait` or `landscape`.
2216
+ * Any other value rejects. Where the host does not report the change back,
2217
+ * the runtime emits the orientation event itself so JS state stays in sync.
2218
+ */
1716
2219
  setDeviceOrientation(orientation: DeviceOrientation): boolean;
1717
- onDeviceOrientationChange(callback: (event: DeviceOrientationChangeEvent) => void): void;
1718
- offDeviceOrientationChange(callback?: (event: DeviceOrientationChangeEvent) => void): void;
2220
+ /** Subscribes to orientation changes and returns the unsubscribe fn. */
2221
+ onDeviceOrientationChange(callback: (event: DeviceOrientationChangeEvent) => void): () => void;
1719
2222
  readonly env: LxEnv;
1720
2223
  downloadFile(options: never): never;
2224
+ /**
2225
+ * Upload a file over HTTP, streamed from disk.
2226
+ * Defaults to a `POST` with a `multipart/form-data` body. Set
2227
+ * `method: 'PUT'` with `bodyMode: 'raw'` to send the file bytes as the whole
2228
+ * body instead, which is what presigned object-storage URLs expect.
2229
+ * Returns the task handle synchronously, before the transfer starts, so
2230
+ * progress and cancellation can be wired up without racing it: the handle is
2231
+ * awaitable for the final result, async-iterable for progress, and cancelable.
2232
+ */
1721
2233
  uploadFile(options: UploadOptions): UploadTask;
1722
2234
  /**
1723
2235
  * Open a local file with the requested strategy.
@@ -1725,19 +2237,41 @@ declare global {
1725
2237
  * `mode: "auto"`.
1726
2238
  */
1727
2239
  openFile(options: OpenFileOptions): Promise<void>;
2240
+ /**
2241
+ * Opens a file picker.
2242
+ * Resolves `{ canceled: true }` only when the user dismisses the picker. A
2243
+ * completed selection resolves `{ canceled: false, paths }` with at least one
2244
+ * path. Rejects when the picker fails or returns an invalid payload.
2245
+ */
1728
2246
  chooseFile(options?: ChooseFileOptions): Promise<ChooseFileResult>;
2247
+ /**
2248
+ * Opens a directory picker.
2249
+ * Resolves `{ canceled: true }` only when the user dismisses the picker. A
2250
+ * completed selection resolves `{ canceled: false, path }`. Rejects when the
2251
+ * picker fails or returns an invalid payload.
2252
+ */
1729
2253
  chooseDirectory(options?: ChooseDirectoryOptions): Promise<ChooseDirectoryResult>;
1730
- getFileManager(): FileManager;
1731
- onKeyDown(callback: KeyEventCallback): void;
1732
- offKeyDown(callback?: KeyEventCallback): void;
1733
- onKeyUp(callback: KeyEventCallback): void;
1734
- offKeyUp(callback?: KeyEventCallback): void;
2254
+ readonly fs: FileSystemApi;
2255
+ /** Subscribes to key-down events and returns the unsubscribe fn. */
2256
+ onKeyDown(callback: KeyEventCallback): () => void;
2257
+ /** Subscribes to key-up events and returns the unsubscribe fn. */
2258
+ onKeyUp(callback: KeyEventCallback): () => void;
1735
2259
  /** Get location function */
1736
2260
  getLocation(options?: GetLocationOptions): Promise<LocationInfo>;
2261
+ /** Identify the running lxapp: its id, display name, version, and release type. */
1737
2262
  getLxAppInfo(): LxAppInfo;
2263
+ /** Read an image's dimensions, type, and orientation without decoding it into a view. */
1738
2264
  getImageInfo(options: GetImageInfoOptions): Promise<ImageInfo>;
2265
+ /** Re-encode an image at a lower quality or size, writing a new managed file. */
1739
2266
  compressImage(options: CompressImageOptions): Promise<CompressImageResult>;
1740
- chooseMedia(options?: ChooseMediaOptions): Promise<ChosenMediaEntry[]>;
2267
+ /**
2268
+ * Opens the media picker or camera.
2269
+ * Resolves `{ canceled: true }` only when the user dismisses the picker. A
2270
+ * completed selection resolves `{ canceled: false, entries }` with at least one
2271
+ * entry. Rejects when capture or selection fails, or the host returns an invalid
2272
+ * payload.
2273
+ */
2274
+ chooseMedia(options?: ChooseMediaOptions): Promise<ChooseMediaResult>;
1741
2275
  /**
1742
2276
  * Synchronously returns a JS handle so listeners can be attached before the
1743
2277
  * first event fires:
@@ -1756,9 +2290,18 @@ declare global {
1756
2290
  * caller never re-indexes their own array.
1757
2291
  */
1758
2292
  previewMedia(options: PreviewMediaOptions): PreviewMediaHandle;
2293
+ /** Save an image into the system photo library. */
1759
2294
  saveImageToPhotosAlbum(options: SaveMediaOptions): Promise<void>;
2295
+ /** Save a video into the system photo library. */
1760
2296
  saveVideoToPhotosAlbum(options: SaveMediaOptions): Promise<void>;
2297
+ /**
2298
+ * Opens the scanner.
2299
+ * Resolves `{ canceled: true }` only when the user dismisses the scanner. A
2300
+ * completed scan resolves `{ canceled: false, scanResult, scanType }`. Rejects
2301
+ * when scanning fails or the host returns an invalid payload.
2302
+ */
1761
2303
  scanCode(options?: ScanCodeOptions): Promise<ScanCodeResult>;
2304
+ /** Take a control handle for the `<lx-video>` component with this id. */
1762
2305
  createVideoContext(componentId: string): VideoContext;
1763
2306
  /**
1764
2307
  * Reads local video metadata for upload preflight and presentation.
@@ -1768,47 +2311,63 @@ declare global {
1768
2311
  * still validate the uploaded bytes.
1769
2312
  */
1770
2313
  getVideoInfo(options: GetVideoInfoOptions): Promise<VideoInfo>;
2314
+ /** Write one frame of a video out as an image file. */
1771
2315
  extractVideoThumbnail(options: ExtractVideoThumbnailOptions): Promise<ExtractVideoThumbnailResult>;
2316
+ /**
2317
+ * Transcode a video to a smaller file.
2318
+ * Returns a task handle synchronously, so progress and cancellation can be
2319
+ * wired up before transcoding starts.
2320
+ */
1772
2321
  compressVideo(options: CompressVideoOptions): CompressVideoTask;
1773
- navigateToLxApp(options: NavigateToLxAppOptions): Promise<void>;
1774
- navigateBackLxApp(): Promise<void>;
2322
+ /**
2323
+ * Open another lxapp, optionally at one of its pages.
2324
+ * Navigating to the lxapp already running is a no-op. Rejects with
2325
+ * `E_SURFACE_CONFLICT` when the target is currently docked as an aside —
2326
+ * close that aside before opening it as a main.
2327
+ */
2328
+ navigateToApp(options: NavigateToAppOptions): Promise<void>;
2329
+ /** Leave this lxapp and reveal the one that opened it. */
2330
+ navigateBackApp(): Promise<void>;
2331
+ /**
2332
+ * Hand content to the system share sheet.
2333
+ * Share text, files, or a page link — files cannot be combined with a page
2334
+ * target or with text; share those separately.
2335
+ */
1775
2336
  share(options: ShareOptions): Promise<ShareResult>;
1776
- getStorage(): Storage;
1777
2337
  /**
1778
- * `lx.openSurface(spec)` — unified surface entry point. The spec is a
1779
- * discriminated union keyed by exactly one of `page`, `surface`, or `url`:
1780
- * - `{ page, as, position?, size?, query? }` opens one of this lxapp's own
1781
- * pages as a `float` (overlay popup) or a `window` (bare standalone desktop
1782
- * window). Pages cannot be docked as an `aside` — an aside shows external
1783
- * content only.
1784
- * - `{ surface, edge?, query? }` shows a host-declared surface by its `ui` id.
1785
- * - `{ url }` opens an authorized HTTPS/file URL in the in-app chromed browser.
2338
+ * Open this lxapp's asynchronous persistent key-value store. `get` asserts the
2339
+ * value shape at the call site and resolves `undefined` for a missing key. Use
2340
+ * `lx.fs` instead for path-based data.
1786
2341
  */
1787
- openSurface(spec: never): Promise<never>;
2342
+ getStorage(): Storage;
1788
2343
  /** `lx.openExternal(url)` — hand the url off to the OS default browser. */
1789
2344
  openExternal(url: string): void;
2345
+ readonly surface: SurfaceApi;
2346
+ /** Read system switches the lxapp may branch on, such as location and WiFi. */
2347
+ getSystemSetting(): SystemSettingInfo;
1790
2348
  /**
1791
- * `lx.onSurfaceContext(handler)` — register a JS callback (scoped to this
1792
- * lxapp's JS context), invoke it immediately, then again whenever that
1793
- * presentation's actual viewport changes. Returns an unsubscribe fn.
2349
+ * Shows a list of actions.
2350
+ * Resolves `{ canceled: false, index }` when the user selects an item; `index`
2351
+ * points into `options.itemList`. Resolves `{ canceled: true }` only when the
2352
+ * user dismisses the sheet. Rejects when presentation fails or the host returns
2353
+ * an invalid selection.
1794
2354
  */
1795
- onSurfaceContext(handler: (context: SurfaceContext) => void): () => void;
1796
- getSystemSetting(): SystemSettingInfo;
1797
- /** Show action sheet function for JavaScript */
1798
2355
  showActionSheet(options: ShowActionSheetOptions): Promise<ActionSheetResult>;
2356
+ readonly appearance: AppearanceApi;
1799
2357
  /**
1800
- * Get capsule button bounding client rect (async)
1801
- * Returns Promise<{width, height, top, right, bottom, left}>
2358
+ * Shows a confirmation modal.
2359
+ * Resolves `{ canceled: false }` when the user confirms and `{ canceled: true }`
2360
+ * only when the user dismisses or cancels the modal. Rejects when presentation
2361
+ * fails or the host returns an invalid payload.
1802
2362
  */
1803
- getCapsuleRect(): Promise<CapsuleRect>;
1804
- /** Show modal function (async) */
1805
2363
  showModal(options: ShowModalOptions): Promise<ModalResult>;
1806
- /** Set navigation bar title */
1807
- setNavigationBarTitle(options: SetNavigationBarTitleOptions): boolean;
1808
- /** Set navigation bar color */
1809
- setNavigationBarColor(options: SetNavigationBarColorOptions): boolean;
1810
- /** Hide home button */
1811
- hideHomeButton(): boolean;
2364
+ /**
2365
+ * Replace the current lxapp's complete app-declared More action list (seven
2366
+ * entries maximum). Pass an empty array to clear it. Native hosts append these
2367
+ * entries after their own lifecycle actions.
2368
+ */
2369
+ setMoreActions(items: MoreAction[]): void;
2370
+ readonly navigationBar: NavigationBarApi;
1812
2371
  /**
1813
2372
  * lx.startPullDownRefresh()
1814
2373
  * Programmatically start the pull-to-refresh animation.
@@ -1821,38 +2380,47 @@ declare global {
1821
2380
  * This should be called after the refresh operation is complete.
1822
2381
  */
1823
2382
  stopPullDownRefresh(): void;
1824
- /** Navigate to a new page (forward navigation) */
2383
+ /**
2384
+ * Push a configured page onto the stack.
2385
+ * A route can appear on the stack only once. The promise rejects with
2386
+ * `data.reason === "duplicate_route"` when the target is already present,
2387
+ * or `data.reason === "stack_full"` when the ten-page limit is reached.
2388
+ */
1825
2389
  navigateTo(options: NavigateToOptions): Promise<PageMessagePort>;
1826
- /** Navigate back to previous page */
1827
- navigateBack(options: NavigateBackOptions): void;
1828
- /** Redirect to a new page (replace current page) */
2390
+ /**
2391
+ * Pop one or more pages and reveal the destination page.
2392
+ * `options` and `options.delta` are optional; both default to one page. The
2393
+ * promise resolves once the destination WebView is ready, so callers can
2394
+ * safely continue with work that targets the revealed page.
2395
+ */
2396
+ navigateBack(options?: NavigateBackOptions): Promise<void>;
2397
+ /**
2398
+ * Replace the current stack entry with a configured page.
2399
+ * Redirecting to the current route keeps its page instance and runs `onLoad`
2400
+ * again with the new query. Redirecting to a route lower in the stack rejects
2401
+ * with `data.reason === "duplicate_route"`.
2402
+ */
1829
2403
  redirectTo(options: RedirectToOptions): Promise<void>;
1830
- /** Switch to a tab page */
2404
+ /**
2405
+ * Switch to a configured tab page.
2406
+ * The tab page being left is hidden and retained. Non-tab pages pushed above
2407
+ * a tab leave the stack and receive `onUnload`.
2408
+ */
1831
2409
  switchTab(options: SwitchTabOptions): Promise<void>;
1832
- /** Relaunch to a new page (clear page stack) */
2410
+ /** Clear the page stack and launch a configured page as the new root. */
1833
2411
  reLaunch(options: ReLaunchOptions): Promise<void>;
1834
2412
  readonly shell: ShellApi;
1835
- /** Show TabBar red dot */
1836
- showTabBarRedDot(options: TabBarRedDotOptions): boolean;
1837
- /** Hide TabBar red dot */
1838
- hideTabBarRedDot(options: TabBarRedDotOptions): boolean;
1839
- /** Set TabBar badge */
1840
- setTabBarBadge(options: SetTabBarBadgeOptions): boolean;
1841
- /** Remove TabBar badge */
1842
- removeTabBarBadge(options: RemoveTabBarBadgeOptions): boolean;
1843
- /** Show TabBar */
1844
- showTabBar(): Promise<boolean>;
1845
- /** Hide TabBar */
1846
- hideTabBar(): Promise<boolean>;
1847
- /** Set TabBar style */
1848
- setTabBarStyle(options: SetTabBarStyleOptions): boolean;
1849
- /** Set TabBar item */
1850
- setTabBarItem(options: SetTabBarItemOptions): boolean;
2413
+ readonly tabBar: TabBarApi;
1851
2414
  /** Show toast function */
1852
2415
  showToast(options: ShowToastOptions): Promise<void>;
1853
2416
  /** Hide toast function */
1854
2417
  hideToast(): Promise<void>;
1855
2418
  readonly tray: TrayApi;
2419
+ /**
2420
+ * Return the callback-based update manager for this lxapp's bundle. This is
2421
+ * available to every lxapp and is distinct from the home-only
2422
+ * `lx.app.checkUpdate()`, which updates the native host app.
2423
+ */
1856
2424
  getUpdateManager(): UpdateManager;
1857
2425
  }
1858
2426
  }
@@ -1863,21 +2431,106 @@ declare global {
1863
2431
  }
1864
2432
  }
1865
2433
  declare global {
1866
- interface ShellActivatorsApi {
2434
+ interface NavigationBarApi {
2435
+ /** Patch the navigation bar of the active page; unset fields stay as they are. */
2436
+ update(patch: NavigationBarPatch): Promise<void>;
2437
+ }
2438
+ }
2439
+ declare global {
2440
+ interface ShellApi {
2441
+ /**
2442
+ * `lx.shell.openApp(appId, options)` — compose another lxapp into a shell
2443
+ * slot. Home-lxapp only; the namespace is the privilege.
2444
+ */
2445
+ openApp(appId: string, options: ShellOpenAppOptions): Promise<AppSurface>;
2446
+ /** `lx.shell.openBuiltin(page)` — a host builtin page. Home-lxapp only. */
2447
+ openBuiltin(page: BuiltinShellPage): Promise<BuiltinSurface>;
2448
+ /**
2449
+ * `lx.shell.openDeclared(id, options?)` — the declared surface, plus the
2450
+ * keyed multi-instance form and placement overrides. Home-lxapp only.
2451
+ */
2452
+ openDeclared(id: string, options?: ShellOpenDeclaredOptions): Promise<DeclaredSurface>;
2453
+ /** `lx.shell.reconfigure(id, patch)` — re-place a live declared surface. */
2454
+ reconfigure(id: string, patch: ShellSurfacePatch): Promise<void>;
2455
+ }
2456
+ }
2457
+ declare global {
2458
+ interface ShellSidebarActionsApi {
1867
2459
  /**
1868
- * Atomically replaces the complete desktop activator declaration. Home lxapp
1869
- * only. Relative icons resolve from the home app bundle. Every entry is bound
1870
- * to its generation-scoped callback; `replace([])` explicitly clears chrome.
2460
+ * Atomically replaces the complete desktop sidebar action declaration. Only the
2461
+ * home lxapp may call this API. Ids must be non-empty and unique across both
2462
+ * placements; header accepts at most two entries. Icons must be bundled relative
2463
+ * paths or runtime-managed `lx://` paths accessible to the home lxapp.
2464
+ * Every entry is bound to its generation-scoped callback. The shell invokes that
2465
+ * callback but never infers navigation or selected state. Validation or host
2466
+ * projection failure leaves the previous generation active. `replace([])` clears
2467
+ * the chrome explicitly. Declarations are process-local, so call `replace` again
2468
+ * on every Logic launch.
2469
+ */
2470
+ replace(items: ShellSidebarAction[]): void;
2471
+ /**
2472
+ * Atomically updates the icon, label, and/or disabled state of one stable id.
2473
+ * Only the home lxapp may call this API. The patch must be non-empty; unknown
2474
+ * fields are rejected. The callback and placement stay unchanged. Throws
2475
+ * `E_NOT_FOUND` when `id` is not in the current declaration.
2476
+ */
2477
+ update(id: string, patch: ShellSidebarActionUpdate): void;
2478
+ /**
2479
+ * Atomically removes one stable id and its generation-scoped callback. Only the
2480
+ * home lxapp may call this API. Throws `E_NOT_FOUND` when `id` is not in the
2481
+ * current declaration.
1871
2482
  */
1872
- replace(items: ShellActivator[]): void;
1873
- /** Updates presentation fields for one stable id. Home lxapp only. */
1874
- update(id: string, patch: ShellActivatorUpdate): void;
1875
- /** Removes one stable id from the declaration. Home lxapp only. */
1876
2483
  remove(id: string): void;
1877
- /** Clears the current runtime declaration. Home lxapp only. */
2484
+ /**
2485
+ * Atomically clears every runtime sidebar action and callback. Only the home
2486
+ * lxapp may call this API. Equivalent to `replace([])` and safe when already
2487
+ * empty; the home lxapp must still redeclare actions after the next Logic launch.
2488
+ */
1878
2489
  clear(): void;
1879
2490
  }
1880
2491
  }
2492
+ declare global {
2493
+ interface SurfaceApi {
2494
+ /**
2495
+ * `lx.surface.openPage(page, options?)` — one of this lxapp's own pages as a
2496
+ * float or a window. A page can never be an aside: asides carry external
2497
+ * content only, which is why that member does not exist on this signature.
2498
+ */
2499
+ openPage(page: string, options?: OpenPageOptions): Promise<PageSurface>;
2500
+ /**
2501
+ * `lx.surface.openUrl(url, options?)` — external content in the in-app
2502
+ * browser, as a tab or docked as an aside.
2503
+ */
2504
+ openUrl(url: string, options?: OpenUrlOptions): Promise<TabSurface>;
2505
+ /**
2506
+ * `lx.surface.openDeclared(id, options?)` — a surface the host declared in
2507
+ * `lingxia.yaml`, opened with the declaration's own presentation.
2508
+ */
2509
+ openDeclared(id: string): Promise<DeclaredSurface>;
2510
+ /**
2511
+ * `lx.surface.get(keyOrId)` — the live handle for a surface this lxapp opened
2512
+ * **with a `key`**, so no caller has to cache one in order to reuse or close
2513
+ * it. An unkeyed surface is not addressable: nothing registers it, because
2514
+ * holding one for the session costs its closures and its message port and
2515
+ * nobody can look up a uuid they never chose.
2516
+ * A `key` you chose wins over a runtime-assigned `id`, so a key that happens
2517
+ * to spell another surface's id still finds yours.
2518
+ */
2519
+ get(keyOrId: string): AnySurface | undefined;
2520
+ /**
2521
+ * `lx.surface.onContext(handler)` — register a JS callback (scoped to this
2522
+ * lxapp's JS context), invoke it immediately, then again whenever that
2523
+ * presentation's actual viewport changes. Returns an unsubscribe fn.
2524
+ */
2525
+ onContext(handler: (context: SurfaceContext) => void): () => void;
2526
+ }
2527
+ }
2528
+ declare global {
2529
+ interface TabBarApi {
2530
+ /** Patch this lxapp's tab bar; unset fields stay as they are. */
2531
+ update(patch: TabBarPatch): Promise<void>;
2532
+ }
2533
+ }
1881
2534
  declare global {
1882
2535
  interface TrayApi {
1883
2536
  /** lx.tray.setBadge(value) — the menu-bar / system-tray badge. Null/empty clears it. */