@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.
@@ -21,9 +21,10 @@ export interface PageInstance<TData extends Record<string, unknown> = Record<str
21
21
  data: TData;
22
22
  route: string;
23
23
  /**
24
- * Available when this page was opened as a surface via `lx.openSurface(...)`.
24
+ * Available when this page was opened as a surface via
25
+ * `lx.surface.openPage(...)`.
25
26
  */
26
- surface?: Surface;
27
+ surface?: PageSurface;
27
28
  /**
28
29
  * Available when this page was opened by `lx.navigateTo(...)`.
29
30
  */
@@ -111,12 +112,6 @@ export interface DownloadTask<TDownloadResult extends DownloadResult = DownloadR
111
112
  wait(): Promise<TDownloadResult>;
112
113
  }
113
114
 
114
- export interface FileManager {
115
- readFile(options: ReadTextFileOptions): Promise<ReadTextFileResult>;
116
- readFile(options: ReadBinaryFileOptions): Promise<ReadBinaryFileResult>;
117
- readFile(options: ReadFileOptions): Promise<ReadFileResult>;
118
- }
119
-
120
115
  declare global {
121
116
  // HostAppApi/LxEnv members are emitted from the Rust js_api metadata; these
122
117
  // merges only add what Rong cannot express — the cfg-gated autostart member
@@ -130,8 +125,9 @@ declare global {
130
125
  readonly envVersion: HostAppEnvVersion;
131
126
 
132
127
  /**
133
- * Launch-at-startup control. Present only on macOS / Windows with the
134
- * capability declared; gate access with `lx.app.autostart?.…`.
128
+ * Launch-at-startup control. Absent where the host cannot register a
129
+ * startup item; its presence and `lx.supports({ capability: 'autostart' })` always
130
+ * agree, so `lx.app.autostart?.…` and the query are interchangeable.
135
131
  */
136
132
  autostart?: AutostartApi;
137
133
  }
@@ -141,16 +137,11 @@ declare global {
141
137
 
142
138
  interface Lx {
143
139
  /**
144
- * Open a surface. Browser tabs resolve to `null`, declared surfaces to a
145
- * host-managed handle, and page surfaces to a full `Surface`. URL asides
146
- * return a `Surface` when docked and `null` in compact browser chrome.
147
- * `as: "window"` is desktop-only.
140
+ * Terminal product settings. Present only in the host-bundled Terminal
141
+ * Settings lxapp when the host declares `capabilities.terminal`; its
142
+ * presence and `lx.supports({ capability: 'terminal' })` always agree.
148
143
  */
149
- openSurface(spec: OpenUrlTabSpec): Promise<null>;
150
- openSurface(spec: OpenDeclaredSurfaceSpec | OpenLxappSurfaceSpec | OpenNativeSurfaceSpec): Promise<SurfaceHandle>;
151
- openSurface(spec: OpenPageSurfaceSpec): Promise<Surface>;
152
- openSurface(spec: OpenUrlAsideSpec): Promise<Surface | null>;
153
- openSurface(spec: OpenSurfaceSpec): Promise<Surface | SurfaceHandle | null>;
144
+ readonly terminal?: TerminalApi;
154
145
 
155
146
  /** Download to the downloads directory. */
156
147
  downloadFile(options: DownloadsDownloadOptions): DownloadTask<DownloadsDownloadResult>;
@@ -160,13 +151,61 @@ declare global {
160
151
  downloadFile<TDestination extends DownloadDestination = "app">(
161
152
  options: DownloadOptions<TDestination>,
162
153
  ): DownloadTask<DownloadResultForDestination<TDestination>>;
154
+
155
+ /**
156
+ * Open this lxapp's store with every key's shape pinned on the handle.
157
+ * `get` / `set` / `delete` then share that schema instead of
158
+ * repeating `get<T>()` at each call site.
159
+ */
160
+ getStorage<S extends StorageSchema>(): TypedStorage<S>;
163
161
  }
164
162
  }
165
163
 
166
- export type ActionSheetResult = {
167
- tapIndex: number;
164
+ /**
165
+ * A map of storage keys to stored value shapes.
166
+ *
167
+ * `object` deliberately accepts both type aliases and interfaces. Requiring a
168
+ * string index signature would reject ordinary interface-based schemas.
169
+ */
170
+ export type StorageSchema = object;
171
+
172
+ type StorageKey<S extends object> = Extract<keyof S, string>;
173
+ type StorageEntry<S extends object> = {
174
+ [K in StorageKey<S>]: [key: K, value: S[K]];
175
+ }[StorageKey<S>];
176
+
177
+ /**
178
+ * Schema-typed view of the same store `lx.getStorage()` returns.
179
+ * Runtime is identical; only the key/value types are pinned.
180
+ *
181
+ * The schema constrains what this handle writes and reads, not what the store
182
+ * contains: a previous app version, or another code path holding the untyped
183
+ * handle, can have written keys outside it. That is why `list` still resolves
184
+ * plain strings — narrowing it to the schema's keys would be the same
185
+ * unchecked assertion this type exists to remove from `get<T>()`.
186
+ */
187
+ export type TypedStorage<S extends object> = {
188
+ get<K extends StorageKey<S>>(key: K): Promise<S[K] | undefined>;
189
+ set(...entry: StorageEntry<S>): Promise<void>;
190
+ delete(key: StorageKey<S>): Promise<void>;
191
+ clear(): Promise<void>;
192
+ list(prefix?: string): Promise<string[]>;
193
+ info(): Promise<StorageInfo>;
168
194
  };
169
195
 
196
+ /**
197
+ * Result of `lx.showActionSheet`. Branch on `canceled` before reading
198
+ * the selected item index.
199
+ */
200
+ export type ActionSheetResult = {
201
+ canceled: false;
202
+ /** Index of the tapped item in `itemList`. */
203
+ index: number;
204
+ } | CanceledResult;
205
+
206
+ /** Every surface handle, narrowable by `kind`. */
207
+ export type AnySurface = PageSurface | DeclaredSurface | AppSurface | TabSurface | BuiltinSurface;
208
+
170
209
  export type AppConfig = {
171
210
  globalData?: Record<string, unknown>;
172
211
  onLaunch?: (options?: AppLaunchOptions) => void | Promise<void>;
@@ -253,13 +292,23 @@ export type AppScreenshotResult = {
253
292
  height?: number;
254
293
  };
255
294
 
295
+ /** Another lxapp composed into a shell slot. */
296
+ export type AppSurface = SurfaceBase & SurfaceShowable & {
297
+ readonly kind: 'app';
298
+ readonly realized: 'main' | 'aside';
299
+ };
300
+
301
+ export type AppearanceApi = globalThis.AppearanceApi;
302
+
303
+ export type AppearancePreference = 'auto' | 'light' | 'dark';
304
+
256
305
  /**
257
306
  * Launch-at-startup control for the host app.
258
- * **macOS 13+ / Windows only.** Everywhere else — other platforms, or a
259
- * macOS shell older than 13 — `lx.app.autostart` is absent (`undefined`);
260
- * presence is the support check, so portable code gates on the member itself:
307
+ * Absent (`undefined`) wherever the host cannot register a startup item.
308
+ * `lx.supports({ capability: 'autostart' })` and the member's presence always
309
+ * agree, so either gate works:
261
310
  * ```ts
262
- * if (lx.app.autostart) {
311
+ * if (lx.supports({ capability: 'autostart' })) {
263
312
  * // render the "Launch at startup" toggle
264
313
  * }
265
314
  * ```
@@ -291,13 +340,25 @@ export type AutostartApi = {
291
340
 
292
341
  export type BinaryFileData = ArrayBuffer | ArrayBufferView;
293
342
 
294
- export type CapsuleRect = {
295
- width?: number;
296
- height?: number;
297
- top?: number;
298
- right?: number;
299
- bottom?: number;
300
- left?: number;
343
+ /**
344
+ * Built-in browser product page. Opening one requires
345
+ * `capabilities.browser` and is restricted to the home lxapp.
346
+ */
347
+ export type BuiltinShellPage = 'settings' | 'downloads';
348
+
349
+ /**
350
+ * A host builtin page such as settings or downloads. The shell owns
351
+ * its lifetime and its visibility, so this handle reports identity:
352
+ * there is no `show` / `hide`, and the inherited `close()` rejects
353
+ * with `unsupported_placement`.
354
+ */
355
+ export type BuiltinSurface = SurfaceBase & {
356
+ readonly kind: 'builtin';
357
+ };
358
+
359
+ /** The user dismissed the operation. Never an error. */
360
+ export type CanceledResult = {
361
+ canceled: true;
301
362
  };
302
363
 
303
364
  export type ChooseDirectoryOptions = {
@@ -305,12 +366,15 @@ export type ChooseDirectoryOptions = {
305
366
  defaultPath?: string;
306
367
  };
307
368
 
369
+ /**
370
+ * Result of `lx.chooseDirectory`. Branch on `canceled` before reading
371
+ * the selected directory.
372
+ */
308
373
  export type ChooseDirectoryResult = {
309
- /** True if the user dismissed the dialog without selecting. */
310
- canceled: boolean;
311
- /** Native-consumable directory reference (path or URI). Undefined when canceled. */
312
- path?: string;
313
- };
374
+ canceled: false;
375
+ /** Native-consumable directory reference (path or URI). */
376
+ path: string;
377
+ } | CanceledResult;
314
378
 
315
379
  export type ChooseFileOptions = {
316
380
  /** Allow selecting multiple files. Default: false */
@@ -326,16 +390,20 @@ export type ChooseFileOptions = {
326
390
  defaultPath?: string;
327
391
  };
328
392
 
393
+ /**
394
+ * Result of `lx.chooseFile`. Branch on `canceled` before reading the
395
+ * selected paths.
396
+ */
329
397
  export type ChooseFileResult = {
330
- /** True if the user dismissed the dialog without selecting. */
331
- canceled: boolean;
398
+ canceled: false;
332
399
  /**
333
- * File paths returned by LingXia. Values may be app-local paths, `lx://...`
334
- * paths, or platform system-picker references. Treat them as opaque strings
335
- * and pass them back to LingXia APIs such as `lx.share`.
400
+ * File paths returned by LingXia; always at least one. Values may be
401
+ * app-local paths, `lx://...` paths, or platform system-picker references.
402
+ * Treat them as opaque strings and pass them back to LingXia APIs such as
403
+ * `lx.share`.
336
404
  */
337
- paths: string[];
338
- };
405
+ paths: [string, ...string[]];
406
+ } | CanceledResult;
339
407
 
340
408
  export type ChooseMediaOptions = {
341
409
  count?: number;
@@ -345,6 +413,16 @@ export type ChooseMediaOptions = {
345
413
  maxDuration?: number;
346
414
  };
347
415
 
416
+ /**
417
+ * Result of `lx.chooseMedia`. Branch on `canceled` before reading the
418
+ * selected entries.
419
+ */
420
+ export type ChooseMediaResult = {
421
+ canceled: false;
422
+ /** Picked media; always at least one entry. */
423
+ entries: [ChosenMediaEntry, ...ChosenMediaEntry[]];
424
+ } | CanceledResult;
425
+
348
426
  export type ChosenMediaEntry = {
349
427
  tempFilePath: string;
350
428
  fileType: 'image' | 'video';
@@ -444,11 +522,9 @@ export type ConnectWifiOptions = {
444
522
  password?: string;
445
523
  };
446
524
 
447
- export type CopyFileOptions = {
448
- srcPath: string;
449
- destPath: string;
450
- /** Defaults to false. */
451
- overwrite?: boolean;
525
+ /** A surface declared by the host in `lingxia.yaml`. */
526
+ export type DeclaredSurface = SurfaceBase & SurfaceShowable & {
527
+ readonly kind: 'declared';
452
528
  };
453
529
 
454
530
  /** Display and orientation APIs. */
@@ -479,7 +555,7 @@ export type DownloadResult = AppDownloadResult | DownloadsDownloadResult;
479
555
  export type DownloadsDownloadOptions = DownloadOptionsBase & {
480
556
  /**
481
557
  * Optional filename hint for the system Downloads destination.
482
- * This is not an app-owned FileManager path.
558
+ * This is not an app-owned `lx.fs` path.
483
559
  */
484
560
  filePath?: string;
485
561
  /** Save into the user's system Downloads directory. */
@@ -487,17 +563,13 @@ export type DownloadsDownloadOptions = DownloadOptionsBase & {
487
563
  };
488
564
 
489
565
  export type DownloadsDownloadResult = {
490
- /** Native system Downloads path. Do not pass this to `FileManager`. */
566
+ /** Native system Downloads path. Do not pass this to `lx.fs`. */
491
567
  filePath: SystemDownloadsPath;
492
568
  tempFilePath?: never;
493
569
  mimeType?: string;
494
570
  size: number;
495
571
  };
496
572
 
497
- export type ExistsOptions = {
498
- path: string;
499
- };
500
-
501
573
  export type ExtractVideoThumbnailOptions = {
502
574
  /**
503
575
  * Source video path or `lx://` URI.
@@ -554,6 +626,36 @@ export type FileDialogFilter = {
554
626
  extensions: string[];
555
627
  };
556
628
 
629
+ export type FileSystemApi = globalThis.FileSystemApi;
630
+
631
+ export type FsCopyOptions = {
632
+ /** Defaults to false. */
633
+ overwrite?: boolean;
634
+ };
635
+
636
+ export type FsMkdirOptions = {
637
+ recursive?: boolean;
638
+ };
639
+
640
+ export type FsRemoveOptions = {
641
+ recursive?: boolean;
642
+ };
643
+
644
+ export type FsRenameOptions = {
645
+ /** Defaults to false. */
646
+ overwrite?: boolean;
647
+ };
648
+
649
+ export type FsWriteOptions = {
650
+ /**
651
+ * How string input is interpreted. Strings are UTF-8 by default; `base64`
652
+ * decodes the input into raw bytes before writing.
653
+ */
654
+ encoding?: 'utf8' | 'base64';
655
+ /** Defaults to false. */
656
+ overwrite?: boolean;
657
+ };
658
+
557
659
  /** Media picker, preview, scan, and file processing APIs. */
558
660
  export type GetImageInfoOptions = {
559
661
  path: string;
@@ -582,8 +684,8 @@ export type HostAppApi = globalThis.HostAppApi;
582
684
  * `crates/lingxia-update::ReleaseType` enum and the `envVersion` field in the
583
685
  * generated `app.json`. Pre-envVersion app artifacts are treated as `'release'`.
584
686
  * Note: this is *separate* from `LxAppEnvVersion` in the navigator module,
585
- * which encodes lxapp release channels (`'develop' | 'preview' | 'release'`)
586
- * for cross-app navigation URLs and uses the truncated `develop` form.
687
+ * which encodes lxapp release channels for cross-app navigation URLs —
688
+ * same three names, different axis.
587
689
  */
588
690
  export type HostAppEnvVersion = 'developer' | 'preview' | 'release';
589
691
 
@@ -622,9 +724,9 @@ export type HostAppUpdateInfo = {
622
724
  * The returned task can be awaited directly when progress is not needed, or
623
725
  * consumed with `for await...of` to render progress.
624
726
  *
625
- * Direct package handoff is currently supported on Android and macOS. Other
626
- * platforms reject with an unsupported-operation error; use `version` and
627
- * `releaseNotes` to guide users to the appropriate app marketplace.
727
+ * Requires `lx.supports({ capability: 'selfUpdate' })`. Where the host cannot
728
+ * install its own update it rejects with an unsupported-operation error;
729
+ * use `version` and `releaseNotes` to guide users to the app marketplace.
628
730
  */
629
731
  apply(): HostAppUpdateTask;
630
732
  };
@@ -647,6 +749,13 @@ export type HostAppUpdateTask = PromiseLike<HostAppUpdateResult> & AsyncIterable
647
749
  wait(): Promise<HostAppUpdateResult>;
648
750
  };
649
751
 
752
+ export type InstalledTerminalFont = {
753
+ family: string;
754
+ monospace: boolean;
755
+ ligatures: boolean;
756
+ nerdIcons: boolean;
757
+ };
758
+
650
759
  /**
651
760
  * Input event APIs.
652
761
  * Platform support: Android only
@@ -665,13 +774,43 @@ export type KeyEvent = {
665
774
 
666
775
  export type KeyEventCallback = (event: KeyEvent) => void;
667
776
 
668
- export type LxAppEnvVersion = 'release' | 'preview' | 'develop';
777
+ export type LxAppEnvVersion = 'release' | 'preview' | 'developer';
669
778
 
670
779
  /** LxApp metadata APIs. */
671
780
  export type LxAppReleaseType = 'release' | 'preview' | 'developer';
672
781
 
782
+ /** Boolean capability names accepted by `lx.supports`. */
783
+ export type LxCapabilityFlag = 'terminal' | 'autostart' | 'notifications' | 'browser' | 'proxy' | 'selfUpdate' | 'process' | 'appUse' | 'computerUse' | 'browserUse' | 'mediaCapture';
784
+
785
+ /**
786
+ * One capability question per call. The catalog is closed, so
787
+ * completion enumerates it and a typo is a type error. `capability`
788
+ * is the discriminant; only the `surface` branch accepts a `value`.
789
+ * Two surface answers describe an *affordance*, not whether the call
790
+ * succeeds: `tab` is "the host has an in-app browser" — without it a
791
+ * url still opens, in the OS browser instead — and `aside` is "a
792
+ * docked region exists right now", while a compact layout still opens
793
+ * the url through the in-app browser's own chrome. Ask them to decide
794
+ * what to render, not whether to call.
795
+ * `chrome` qualifies a window and only a window: it asks whether this
796
+ * host can produce that decoration, not merely a window.
797
+ */
798
+ export type LxCapabilityQuery = {
799
+ capability: 'surface';
800
+ value: 'window';
801
+ chrome?: WindowChrome;
802
+ } | {
803
+ capability: 'surface';
804
+ value: Exclude<LxSurfaceCapability, 'window'>;
805
+ } | {
806
+ capability: LxCapabilityFlag;
807
+ };
808
+
673
809
  export type LxEnv = globalThis.LxEnv;
674
810
 
811
+ /** Surface placements accepted by `lx.supports`. */
812
+ export type LxSurfaceCapability = 'main' | 'aside' | 'float' | 'window' | 'tab';
813
+
675
814
  /** Device action APIs. */
676
815
  export type MakePhoneCallOptions = {
677
816
  phoneNumber: string;
@@ -681,24 +820,50 @@ export type MediaObjectFit = 'cover' | 'contain' | 'fill' | 'fit';
681
820
 
682
821
  export type MediaRotation = 0 | 90 | 180 | 270;
683
822
 
684
- export type MkdirOptions = {
685
- path: string;
686
- recursive?: boolean;
687
- };
688
-
823
+ /**
824
+ * Result of `lx.showModal`. `canceled: false` means the user confirmed;
825
+ * there is no third resolved outcome. Presentation failures reject.
826
+ */
689
827
  export type ModalResult = {
690
- confirm: boolean;
691
- cancel: boolean;
828
+ canceled: false;
829
+ } | CanceledResult;
830
+
831
+ /**
832
+ * One app-declared action shown in the host-provided More affordance.
833
+ * Mobile hosts render these in the capsule sheet; desktop hosts render
834
+ * them in the lxapp context menu. The native menu is fully dismissed
835
+ * before `onClick` runs.
836
+ */
837
+ export type MoreAction = {
838
+ /** Bundled resource path or an app-accessible local `lx://` path. */
839
+ icon: string;
840
+ /** Visible action label. */
841
+ label: string;
842
+ onClick: () => void | Promise<void>;
692
843
  };
693
844
 
845
+ /**
846
+ * Options for `lx.navigateBack()`. Omit the object or `delta` to pop
847
+ * one page.
848
+ */
694
849
  export type NavigateBackOptions = {
695
- delta: number;
850
+ /** Number of pages to pop. Defaults to 1. */
851
+ delta?: number;
696
852
  };
697
853
 
698
- export type NavigateToLxAppOptions = {
854
+ /**
855
+ * Navigate to another lxapp inside the current App Surface. JavaScript
856
+ * callers address pages by their configured name; page routes are an
857
+ * internal runtime detail and are not accepted as input.
858
+ */
859
+ export type NavigateToAppOptions = {
699
860
  appId: string;
861
+ /**
862
+ * Configured page name from the target lxapp's `lxapp.json`. Omit it to
863
+ * open the target app's initial page. Full routes such as
864
+ * `/pages/home/index` are not supported.
865
+ */
700
866
  page?: string;
701
- path?: string;
702
867
  query?: PageQuery;
703
868
  envVersion?: LxAppEnvVersion;
704
869
  targetVersion?: string;
@@ -706,6 +871,20 @@ export type NavigateToLxAppOptions = {
706
871
 
707
872
  export type NavigateToOptions = PageTargetOptions;
708
873
 
874
+ export type NavigationBarApi = globalThis.NavigationBarApi;
875
+
876
+ export type NavigationBarPatch = {
877
+ title?: string | null;
878
+ homeButton?: VisibilityPreference;
879
+ style?: NavigationBarStylePatch | null;
880
+ };
881
+
882
+ export type NavigationBarStylePatch = {
883
+ backgroundColor?: string | null;
884
+ foregroundColor?: string | null;
885
+ dividerColor?: string | null;
886
+ };
887
+
709
888
  export type NetworkChangeCallback = (info: NetworkInfo) => void;
710
889
 
711
890
  export type NetworkInfo = {
@@ -718,24 +897,6 @@ export type NetworkInfo = {
718
897
  /** Network status APIs. */
719
898
  export type NetworkType = 'none' | 'unknown' | 'wifi' | '2g' | '3g' | '4g' | '5g' | 'ethernet';
720
899
 
721
- /**
722
- * Show a surface declared by id in the host's `lingxia.yaml`.
723
- * Available to any lxapp granted access to that declaration.
724
- */
725
- export type OpenDeclaredSurfaceSpec = {
726
- surface: string;
727
- /** Docking edge override for this open. */
728
- edge?: SurfaceEdge;
729
- page?: never;
730
- url?: never;
731
- lxapp?: never;
732
- native?: never;
733
- as?: never;
734
- position?: never;
735
- size?: never;
736
- query?: never;
737
- };
738
-
739
900
  /** File system APIs. */
740
901
  export type OpenFileOptions = {
741
902
  /** Local file path or runtime-managed temp path. */
@@ -753,139 +914,58 @@ export type OpenFileOptions = {
753
914
  };
754
915
 
755
916
  /**
756
- * Open another lxapp by appId (home lxapp only). A declared surface
757
- * toggles its shell presentation; an undeclared lxapp opens as a main
758
- * tab, or docks as an aside panel with `as: 'aside'`.
917
+ * `as` picks the shape. A float anchors and carries no decoration; a
918
+ * window is decorated and does not anchor. The runtime rejects the
919
+ * wrong pairing either way, so the type says it first — except with an
920
+ * ordered preference, where the realized placement is not known up
921
+ * front and both stay open.
759
922
  */
760
- export type OpenLxappSurfaceSpec = {
761
- lxapp: string;
762
- /** Defaults to the lingxia.yaml role, else 'main'. */
763
- as?: 'main' | 'aside' | 'float';
923
+ export type OpenPageOptions = (OpenPageShared & {
924
+ /** The default. Rejects when the host cannot float. */
925
+ as?: 'float';
926
+ /** Where the float anchors. */
927
+ position?: SurfaceFloatPosition;
928
+ chrome?: never;
929
+ }) | (OpenPageShared & {
930
+ /** A separate desktop window. Rejects when the host cannot make one. */
931
+ as: 'window';
932
+ /** Window decoration. */
933
+ chrome?: WindowChrome;
934
+ position?: never;
935
+ }) | (OpenPageShared & {
764
936
  /**
765
- * Docking edge override for this open. Without it the surface keeps its
766
- * current placement (initially the `lingxia.yaml` edge); with it the panel
767
- * opens there — or moves there if already visible.
937
+ * An ordered preference: the first placement the host can realize wins,
938
+ * and `realized` reports which.
768
939
  */
769
- edge?: SurfaceEdge;
770
- page?: never;
771
- url?: never;
772
- native?: never;
773
- position?: never;
774
- size?: never;
775
- query?: never;
776
- };
777
-
778
- /**
779
- * Open a host-registered native capability (home lxapp only), e.g.
780
- * the built-in terminal declared in `lingxia.yaml` surfaces.
781
- */
782
- export type OpenNativeSurfaceSpec = {
783
- native: string;
784
- /** Docking edge override for this open. */
785
- edge?: SurfaceEdge;
786
- page?: never;
787
- url?: never;
788
- lxapp?: never;
789
- as?: never;
790
- position?: never;
791
- size?: never;
792
- query?: never;
793
- };
794
-
795
- /**
796
- * Spec for {@link OpenSurfaceSpec}. A discriminated union keyed by source so a
797
- * page name and a declared surface id never collide (each is its own string
798
- * space, separately type-checkable).
799
- * - `{ page }` — one of this lxapp's own pages, by name, arranged as `as`
800
- * (`float` is a popup; `window` is a bare desktop window, which rejects on
801
- * mobile). `position` applies to `float`, and `size` is a Host-clamped hint.
802
- * They are fixed at open (re-open to change). Your own pages **cannot** be
803
- * docked as an `aside` — an aside is external content only (see `{ url }`).
804
- * For a side panel of your own, use a declared `surface`, an in-page split
805
- * layout, or `role: main` for a switchable destination.
806
- * `float` is a popup layered above the main at `position` (like a dialog); it
807
- * takes no layout space. `interaction` controls the native close button,
808
- * outside-click dismissal, and modality. Defaults are no button,
809
- * `tapOutside`, and non-modal.
810
- * - `{ surface }` — a surface declared in `lingxia.yaml` `surfaces:`, by id
811
- * (e.g. `'terminal'`, `'ai-assistant'`). Form, position, and startup data come
812
- * from the declaration.
813
- * - `{ url }` — external content in the in-app browser. Without `as` it opens as
814
- * a main browser tab (the **self** browser: full chrome **with an editable
815
- * address bar**, no handle). With `as: 'aside'` it opens in the **browser
816
- * aside** — a docked (large screen) / full-screen (phone) **multi-tab** browser
817
- * for external content only (`https://` or `file://`).
818
- * The aside is **API-only** and never permits address editing or a manual
819
- * "new tab" action. Desktop may show the current address read-only; compact
820
- * phone/Runner chrome omits the address row entirely.
821
- * Tabs are **deduped by URL** — reopening a URL focuses the existing tab and
822
- * preserves its current navigation. On `medium` / `expanded`, the returned
823
- * handle is **tab-scoped**: `close()` closes that tab. Compact browser chrome
824
- * owns the group and returns `null`. Closing the last tab closes the aside;
825
- * dismissing it only hides the group. The tab UI shows page **titles** (never
826
- * the URL), plus per-tab close, back/forward, refresh, and dismissal.
827
- * Presentation is the only large/small difference: on `medium` / `expanded`
828
- * the aside **docks** and splits beside the main at `edge` (default `'right'`)
829
- * with a horizontal title tab strip; on `compact` (phone / runner) it presents
830
- * **full-screen** with a single-row **bottom** browser toolbar (tabs reached
831
- * via an aside-only switcher). System/edge Back and the toolbar dismiss action
832
- * exit the whole aside even when page history exists; the explicit browser
833
- * Back button navigates history. `size` is a host-clamped preferred size
834
- * (large screen only).
835
- */
836
- export type OpenPageSurfaceSpec = {
837
- page: string;
838
- /** A popup above the main. */
839
- as: 'float';
940
+ as: readonly ('float' | 'window')[];
941
+ chrome?: WindowChrome;
840
942
  position?: SurfaceFloatPosition;
943
+ });
944
+
945
+ export type OpenPageShared = {
946
+ /**
947
+ * A float accepts a percentage; a window is in logical pixels and ignores
948
+ * one. Both live here rather than in two option types, because `as` may be
949
+ * an ordered preference and the realized placement is not known up front.
950
+ */
841
951
  size?: OverlaySurfaceSize;
842
952
  interaction?: SurfaceInteraction;
843
953
  query?: Record<string, unknown>;
844
- edge?: never;
845
- surface?: never;
846
- url?: never;
847
- } | {
848
- page: string;
849
- as: 'window';
850
- size?: WindowSurfaceSize;
851
- /** Windows use manual dismissal; `tapOutside` is invalid. */
852
- interaction?: SurfaceInteraction;
853
- query?: Record<string, unknown>;
854
- edge?: never;
855
- position?: never;
856
- surface?: never;
857
- url?: never;
954
+ /** Caller-owned identity, for `lx.surface.get(key)` later. */
955
+ key?: string;
858
956
  };
859
957
 
860
- export type OpenSurfaceSpec = OpenPageSurfaceSpec | OpenDeclaredSurfaceSpec | OpenLxappSurfaceSpec | OpenNativeSurfaceSpec | OpenUrlTabSpec | OpenUrlAsideSpec;
861
-
862
- /**
863
- * Open `url` in the multi-tab browser aside. `url` must be `https://` or
864
- * `file://` (external content only). Repeated calls add/focus tabs (deduped by
865
- * URL) in the single aside per window. Medium/expanded returns a tab-scoped
866
- * handle; compact returns `null` because browser chrome owns the group. See
867
- * {@link OpenSurfaceSpec} for the full aside contract.
868
- */
869
- export type OpenUrlAsideSpec = {
870
- url: string;
871
- as: 'aside';
958
+ export type OpenUrlOptions = {
959
+ /**
960
+ * `tab` opens a browser tab; `aside` docks the browser beside the main.
961
+ * Defaults to `'tab'`.
962
+ */
963
+ as?: 'tab' | 'aside' | readonly ('tab' | 'aside')[];
964
+ /** Preferred docking side when the realized placement is an aside. */
872
965
  edge?: SurfaceEdge;
873
966
  size?: OverlaySurfaceSize;
874
- page?: never;
875
- surface?: never;
876
- position?: never;
877
- query?: never;
878
- };
879
-
880
- export type OpenUrlTabSpec = {
881
- url: string;
882
- as?: never;
883
- page?: never;
884
- surface?: never;
885
- edge?: never;
886
- position?: never;
887
- size?: never;
888
- query?: never;
967
+ /** Stable identity for `lx.surface.get(key)`. */
968
+ key?: string;
889
969
  };
890
970
 
891
971
  export type OverlaySurfaceSize = {
@@ -915,20 +995,20 @@ export type PageQuery = Record<string, PageQueryValue>;
915
995
 
916
996
  export type PageQueryValue = string | number | boolean | null | undefined;
917
997
 
998
+ /** One of this lxapp's own pages, opened as a float or a window. */
999
+ export type PageSurface = SurfaceBase & SurfaceShowable & SurfaceMessaging & {
1000
+ readonly kind: 'page';
1001
+ readonly realized: 'float' | 'window';
1002
+ };
1003
+
918
1004
  /**
919
1005
  * Target page for `navigateTo`, `redirectTo`, `switchTab`, and `reLaunch`.
920
- * Pass exactly one of `page` or `path`; there is no `url` field. Page
921
- * names and routes are discoverable with `lxdev lxapp pages`.
1006
+ * JavaScript navigation accepts only the configured page name; full routes
1007
+ * are internal runtime details. Discover names with `lxdev lxapp pages`.
922
1008
  */
923
1009
  export type PageTargetOptions = {
924
1010
  /** Configured page name from `lingxia.yaml` / `lxapp.json`. */
925
1011
  page: string;
926
- path?: never;
927
- query?: PageQuery;
928
- } | {
929
- /** Full page route, for example `/pages/home/index`. */
930
- path: string;
931
- page?: never;
932
1012
  query?: PageQuery;
933
1013
  };
934
1014
 
@@ -1090,45 +1170,9 @@ export type PreviewMediaSource = {
1090
1170
 
1091
1171
  export type ReLaunchOptions = PageTargetOptions;
1092
1172
 
1093
- export type ReadBinaryFileOptions = {
1094
- filePath: string;
1095
- encoding?: undefined;
1096
- };
1097
-
1098
- export type ReadBinaryFileResult = {
1099
- data: ArrayBuffer;
1100
- };
1101
-
1102
- export type ReadDirOptions = {
1103
- path: string;
1104
- };
1105
-
1106
- export type ReadFileOptions = ReadTextFileOptions | ReadBinaryFileOptions;
1107
-
1108
- export type ReadFileResult = ReadTextFileResult | ReadBinaryFileResult;
1109
-
1110
- export type ReadTextFileOptions = {
1111
- filePath: string;
1112
- encoding: 'utf8' | 'base64';
1113
- };
1114
-
1115
- export type ReadTextFileResult = {
1116
- data: string;
1117
- };
1118
-
1119
1173
  export type RedirectToOptions = PageTargetOptions;
1120
1174
 
1121
- export type RemoveOptions = {
1122
- path: string;
1123
- recursive?: boolean;
1124
- };
1125
-
1126
- export type RenameOptions = {
1127
- oldPath: string;
1128
- newPath: string;
1129
- /** Defaults to false. */
1130
- overwrite?: boolean;
1131
- };
1175
+ export type ResolvedAppearance = 'light' | 'dark';
1132
1176
 
1133
1177
  export type SaveMediaOptions = {
1134
1178
  filePath: string;
@@ -1139,10 +1183,15 @@ export type ScanCodeOptions = {
1139
1183
  scanType?: ('barCode' | 'qrCode' | 'datamatrix' | 'pdf417')[];
1140
1184
  };
1141
1185
 
1186
+ /**
1187
+ * Result of `lx.scanCode`. Branch on `canceled` before reading the scan
1188
+ * payload.
1189
+ */
1142
1190
  export type ScanCodeResult = {
1191
+ canceled: false;
1143
1192
  scanResult: string;
1144
1193
  scanType: string;
1145
- };
1194
+ } | CanceledResult;
1146
1195
 
1147
1196
  /** Share images, PDFs, or other files. */
1148
1197
  export type ShareFilesOptions = ShareTitleOptions & {
@@ -1205,10 +1254,12 @@ export type ShareQuery = Record<string, string | number | boolean>;
1205
1254
 
1206
1255
  export type ShareResult = {
1207
1256
  /**
1208
- * Best-effort completion flag. Some platforms can only confirm that the
1209
- * system share UI was opened or closed.
1257
+ * What the share sheet reported. Not part of the `canceled` family: some
1258
+ * platforms only observe that the system UI opened and closed, so the
1259
+ * unknown case is stated rather than hidden in a missing boolean that
1260
+ * every call site would read as "not shared".
1210
1261
  */
1211
- completed?: boolean;
1262
+ outcome: 'completed' | 'dismissed' | 'unknown';
1212
1263
  };
1213
1264
 
1214
1265
  export type ShareTextBaseOptions = ShareTitleOptions & {
@@ -1235,28 +1286,149 @@ export type ShareTitleOptions = {
1235
1286
  };
1236
1287
 
1237
1288
  /**
1238
- * One app-declared shell activator. Its `id` remains stable across
1239
- * updates and activation. The shell only routes activation to the
1240
- * callback; the app owns every resulting action.
1289
+ * App-owned host-shell chrome. Mutations are available only to the home
1290
+ * lxapp's Logic context; other lxapps receive a permission error.
1291
+ */
1292
+ export type ShellApi = {
1293
+ /**
1294
+ * Declares runtime actions in the desktop shell's sidebar header or footer.
1295
+ * The shell controls layout and only dispatches activation; callbacks own
1296
+ * navigation and all other behavior.
1297
+ */
1298
+ sidebarActions: ShellSidebarActionsApi;
1299
+ /** Compose another lxapp into a shell slot. */
1300
+ openApp(appId: string, options: ShellOpenAppOptions): Promise<AppSurface>;
1301
+ /** Open a host builtin page such as settings or downloads. */
1302
+ openBuiltin(page: BuiltinShellPage): Promise<BuiltinSurface>;
1303
+ /**
1304
+ * Open a declared surface with shell privileges — the same declaration
1305
+ * `lx.surface.openDeclared` opens, plus the keyed multi-instance form and
1306
+ * placement overrides.
1307
+ */
1308
+ openDeclared(id: string, options?: ShellOpenDeclaredOptions): Promise<DeclaredSurface>;
1309
+ /** Re-place a live declared surface: change its role or its edge. */
1310
+ reconfigure(id: string, patch: ShellSurfacePatch): Promise<void>;
1311
+ };
1312
+
1313
+ export type ShellOpenAppOptions = {
1314
+ /** `main` occupies the primary content area; `aside` a companion region. */
1315
+ as: 'main' | 'aside';
1316
+ /** Preferred docking side. Only meaningful with `as: 'aside'`. */
1317
+ edge?: SurfaceEdge;
1318
+ /**
1319
+ * Configured page name from the target lxapp's `lxapp.json`. Omit it to
1320
+ * open that app's initial page. Full page routes are not supported.
1321
+ */
1322
+ page?: string;
1323
+ query?: PageQuery;
1324
+ /** Defaults to 'release'. */
1325
+ envVersion?: LxAppEnvVersion;
1326
+ targetVersion?: string;
1327
+ /** Stable identity for `lx.surface.get(key)`. */
1328
+ key?: string;
1329
+ };
1330
+
1331
+ /**
1332
+ * The declared-surface options only the home lxapp may use.
1333
+ * Creating an extra instance and overriding a placement both mutate
1334
+ * shared shell composition, so they live here and not on
1335
+ * `lx.surface.openDeclared` — which consumes a declaration exactly as
1336
+ * the host authored it, and therefore takes no options at all.
1241
1337
  */
1242
- export type ShellActivator = {
1338
+ export type ShellOpenDeclaredOptions = {
1339
+ /**
1340
+ * Caller-owned identity, for `lx.surface.get(key)` later — the same key
1341
+ * every opener takes. It carries one extra power here: a declaration can
1342
+ * be opened more than once, and the key is which instance you mean, so a
1343
+ * new key creates one. 1 to 128 UTF-8 bytes. Declarations without
1344
+ * instantiable native providers reject it with `capability_missing`.
1345
+ */
1346
+ key?: string;
1347
+ /**
1348
+ * Open with a role other than the declaration's. Must be realizable by the
1349
+ * declared provider; a stable root rejects anything but `main`. Prefer this
1350
+ * over opening and then calling `reconfigure`, which would present the
1351
+ * wrong role first.
1352
+ */
1353
+ as?: 'main' | 'aside' | 'float';
1354
+ /** Preferred docking side when the effective role is `aside`. */
1355
+ edge?: SurfaceEdge;
1356
+ };
1357
+
1358
+ /**
1359
+ * One app-declared shell sidebar action. It is a stateless command, not a
1360
+ * selectable navigation item: the shell invokes `onActivate` once and
1361
+ * does not infer a target or active state.
1362
+ */
1363
+ export type ShellSidebarAction = {
1364
+ /** Stable, non-empty id; unique across both header and footer actions. */
1243
1365
  id: string;
1366
+ /**
1367
+ * Initial host-owned region. Use `replace` to move an action. The header
1368
+ * takes at most two; everything else belongs in the footer.
1369
+ */
1370
+ placement: ShellSidebarActionPlacement;
1371
+ /**
1372
+ * Local lxapp-accessible icon. Use a bundled relative path such as
1373
+ * `public/settings.svg`, or an `lx://temp`, `lx://usercache`, or
1374
+ * `lx://userdata` path returned by LingXia file APIs. Native absolute paths,
1375
+ * parent traversal, `file:` URLs, and network URLs are rejected; download a
1376
+ * remote icon before registration. For portable rendering, prefer a square,
1377
+ * transparent, monochrome SVG or PNG designed for a 16-point visual.
1378
+ */
1244
1379
  icon: string;
1380
+ /**
1381
+ * Visible footer title and the tooltip/accessibility text for every
1382
+ * placement. Long footer labels are kept on one line and tail-truncated.
1383
+ */
1245
1384
  label: string;
1385
+ /** Visible but non-activatable when true. Defaults to false. */
1246
1386
  disabled?: boolean;
1387
+ /**
1388
+ * Called once for each enabled mouse, keyboard, accessibility, shortcut, or
1389
+ * automation activation. Explicitly open or navigate to the desired content.
1390
+ */
1247
1391
  onActivate: () => void;
1248
1392
  };
1249
1393
 
1250
- /** Mutable presentation fields for an existing activator. */
1251
- export type ShellActivatorUpdate = {
1394
+ /**
1395
+ * Where the host renders a sidebar action on desktop.
1396
+ * - `header`: icon-only, at most two actions; `label` supplies tooltip
1397
+ * and accessibility text. Hidden in the compact/collapsed shell.
1398
+ * - `footer`: icon and label in the expanded sidebar, icon-only in the
1399
+ * compact rail. The host wraps cells and scrolls after five visible
1400
+ * rows.
1401
+ * Apps cannot configure cell size, row, weight, color, or selected state.
1402
+ * Where an action lives in the sidebar.
1403
+ * `header` is the caption row beside the window controls: at most two
1404
+ * actions, for the ones a person reaches for constantly. Declaring a
1405
+ * third rejects the whole `replace` call rather than hiding one.
1406
+ * `footer` is unbounded and scrolls, and every entry stays visible at
1407
+ * any window size. Anything that must be findable belongs here.
1408
+ */
1409
+ export type ShellSidebarActionPlacement = 'header' | 'footer';
1410
+
1411
+ /**
1412
+ * Mutable presentation fields for an existing sidebar action. The patch
1413
+ * must contain at least one field. Use `replace` to change `placement` or
1414
+ * `onActivate`.
1415
+ */
1416
+ export type ShellSidebarActionUpdate = {
1417
+ /** Replacement local icon, with the same path rules as registration. */
1252
1418
  icon?: string;
1419
+ /** Replacement non-empty visible/accessibility label. */
1253
1420
  label?: string;
1421
+ /** Whether the action remains visible but rejects activation. */
1254
1422
  disabled?: boolean;
1255
1423
  };
1256
1424
 
1257
- /** Shell chrome writer API (home lxapp only). */
1258
- export type ShellApi = {
1259
- activators: ShellActivatorsApi;
1425
+ /**
1426
+ * Role and edge overrides the home lxapp may apply to a live declared
1427
+ * surface. A stable root rejects non-main roles.
1428
+ */
1429
+ export type ShellSurfacePatch = {
1430
+ as?: 'main' | 'aside' | 'float';
1431
+ edge?: SurfaceEdge;
1260
1432
  };
1261
1433
 
1262
1434
  export type ShowActionSheetOptions = {
@@ -1284,17 +1456,25 @@ export type ShowToastOptions = {
1284
1456
  position?: 'top' | 'center' | 'bottom';
1285
1457
  };
1286
1458
 
1287
- export type StatOptions = {
1288
- path: string;
1289
- };
1290
-
1291
- /** Persistent key-value storage backed by the lxapp database. */
1459
+ /**
1460
+ * Asynchronous persistent key-value storage backed by the lxapp
1461
+ * database. Use `lx.fs` for path-based data.
1462
+ * `get<T>()` is an unchecked assertion at the call site. Pin every
1463
+ * key's shape once with `lx.getStorage<Schema>()` — that returns a
1464
+ * `TypedStorage<Schema>` instead of this untyped handle.
1465
+ */
1292
1466
  export type Storage = {
1293
- get(key: string): Promise<unknown>;
1467
+ /**
1468
+ * Reads a stored value. `T` is an unchecked assertion about the stored
1469
+ * shape, exactly like a `JSON.parse` boundary; a missing key resolves
1470
+ * `undefined`, which a stored `null` never does.
1471
+ */
1472
+ get<T = unknown>(key: string): Promise<T | undefined>;
1294
1473
  set(key: string, value: unknown): Promise<void>;
1295
1474
  delete(key: string): Promise<void>;
1296
1475
  clear(): Promise<void>;
1297
- list(prefix?: string): Promise<IterableIterator<string>>;
1476
+ /** Resolves every key, optionally filtered by prefix. */
1477
+ list(prefix?: string): Promise<string[]>;
1298
1478
  info(): Promise<StorageInfo>;
1299
1479
  };
1300
1480
 
@@ -1312,62 +1492,67 @@ export type StreamSourceOptions = {
1312
1492
  params?: Record<string, unknown>;
1313
1493
  };
1314
1494
 
1315
- export type Surface = SurfaceHandle & {
1316
- readonly kind: 'overlay' | 'window';
1495
+ /**
1496
+ * Content-keyed surface composition, callable by any lxapp. Privileged
1497
+ * composition lives on `lx.shell`, so the namespace is the privilege.
1498
+ */
1499
+ export type SurfaceApi = {
1500
+ /** Open one of this lxapp's own pages as a float or a window. */
1501
+ openPage(page: string, options?: OpenPageOptions): Promise<PageSurface>;
1502
+ /** Open external content in the in-app browser. */
1503
+ openUrl(url: string, options?: OpenUrlOptions): Promise<TabSurface>;
1317
1504
  /**
1318
- * Last-known visibility, kept in sync with the native side via show/hide
1319
- * events. False once the surface has been closed. Safe to bind into
1320
- * declarative UI; for event-driven updates subscribe via `onShow`/`onHide`.
1505
+ * Open a surface the host declared in `lingxia.yaml`, with the placement
1506
+ * the declaration chose. Instance keys and placement overrides are shell
1507
+ * composition; they live on `lx.shell.openDeclared`.
1321
1508
  */
1322
- readonly visible: boolean;
1509
+ openDeclared(id: string): Promise<DeclaredSurface>;
1323
1510
  /**
1324
- * True until `close()` fires. After close the surface is detached and the
1325
- * page instance is being torn down; further `show()` / `hide()` calls will
1326
- * reject.
1511
+ * The live handle for a surface this lxapp opened **with a `key`**, found
1512
+ * by that key or by its `id`. Removes the need to cache handles in order
1513
+ * to reuse or close them. A surface opened without a `key` is not
1514
+ * addressable — nothing else refers to a runtime-assigned id, so nothing
1515
+ * registers it. A key you chose wins over an id it happens to spell.
1327
1516
  */
1328
- readonly alive: boolean;
1329
- /**
1330
- * Sends a message to the other side of a page surface.
1331
- *
1332
- * For the opener this targets the opened page. For the opened page this
1333
- * targets the opener. URL surfaces have no page-side receiver.
1334
- */
1335
- postMessage(message: unknown): void;
1336
- onMessage(handler: (message: unknown) => void): () => void;
1337
- onClose(handler: (event: SurfaceClosedEvent) => void): () => void;
1517
+ get(keyOrId: string): AnySurface | undefined;
1338
1518
  /**
1339
- * Fires when the surface transitions to visible, regardless of whether
1340
- * `show()` was called on this side or on the peer. Returns an unsubscribe
1341
- * function. Only fires on real state changes — calling `show()` on an
1342
- * already-visible surface is a no-op for listeners.
1519
+ * Observe this presentation's viewport. Invoked immediately with the
1520
+ * current context, then again whenever it changes. Returns an unsubscribe
1521
+ * function.
1343
1522
  */
1344
- onShow(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1523
+ onContext(handler: (context: SurfaceContext) => void): () => void;
1524
+ };
1525
+
1526
+ /** What every surface handle carries, whatever opened it. */
1527
+ export type SurfaceBase = {
1528
+ readonly kind: SurfaceKind;
1529
+ readonly id: string;
1530
+ /** The caller-supplied identity, when this surface was opened with one. */
1531
+ readonly key?: string;
1345
1532
  /**
1346
- * Fires when the surface transitions to hidden, regardless of which side
1347
- * triggered it. Returns an unsubscribe function. Only fires on real state
1348
- * changes.
1533
+ * The placement the host produced, which an ordered preference may narrow.
1534
+ * Live rather than a snapshot: `lx.shell.reconfigure` updates it on the
1535
+ * handle you already hold.
1349
1536
  */
1350
- onHide(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1351
- close(): Promise<void>;
1537
+ readonly realized: SurfacePlacement;
1538
+ /** True until `close()` fires; afterwards the page instance is torn down. */
1539
+ readonly alive: boolean;
1352
1540
  /**
1353
- * Toggle the surface to visible without tearing it down. The page instance
1354
- * and its state survive a hide / show round-trip — only close() actually
1355
- * destroys the surface and fires the onClose listener. Idempotent: calling
1356
- * on an already-visible surface resolves without firing `onShow`.
1541
+ * Last-known visibility, kept in sync with the native side. Safe to bind
1542
+ * into declarative UI; for event-driven updates use `onShow` / `onHide`.
1357
1543
  */
1358
- show(): Promise<void>;
1544
+ readonly visible: boolean;
1359
1545
  /**
1360
- * Hide the surface without destroying it. The page instance stays mounted,
1361
- * so a subsequent show() restores the same scroll position, form input,
1362
- * and JS state. Hidden surfaces still receive postMessage but are not
1363
- * visible to the user. Idempotent.
1546
+ * Destroy the surface. The stable root main cannot be closed. Repeated
1547
+ * calls after a successful close are idempotent.
1364
1548
  */
1365
- hide(): Promise<void>;
1549
+ close(): Promise<void>;
1550
+ onClose(handler: (event: SurfaceClosedEvent) => void): () => void;
1366
1551
  };
1367
1552
 
1368
1553
  /**
1369
1554
  * Surfaces (docked asides, floats, windows, browser tabs, declared surfaces)
1370
- * and the desktop tray — the types behind `lx.openSurface`, `lx.onSurfaceContext`,
1555
+ * and the desktop tray — the types behind `lx.surface`, `lx.shell`,
1371
1556
  * and `lx.tray`.
1372
1557
  */
1373
1558
  export type SurfaceCloseReason = 'user' | 'programmatic' | 'owner_closed' | 'app_closed' | 'failed'
@@ -1380,12 +1565,11 @@ export type SurfaceCloseReason = 'user' | 'programmatic' | 'owner_closed' | 'app
1380
1565
 
1381
1566
  export type SurfaceClosedEvent = {
1382
1567
  id: string;
1383
- kind: 'overlay' | 'window';
1384
1568
  reason: SurfaceCloseReason;
1385
1569
  };
1386
1570
 
1387
1571
  /**
1388
- * The current surface viewport context, delivered to `lx.onSurfaceContext()`
1572
+ * The current surface viewport context, delivered to `lx.surface.onContext()`
1389
1573
  * so an lxapp can self-adapt (e.g. switch column count by `sizeClass`).
1390
1574
  */
1391
1575
  export type SurfaceContext = {
@@ -1397,37 +1581,54 @@ export type SurfaceContext = {
1397
1581
  height: number;
1398
1582
  };
1399
1583
 
1400
- /** Edge an aside docks to; the Host decides the realized form by screen size. */
1584
+ /**
1585
+ * Preferred docking side for an aside when the Host has room for a docked
1586
+ * layout. `aside` selects the companion region; `edge` selects a side within
1587
+ * it. Compact Hosts may reproject the same aside as a full-screen overlay.
1588
+ */
1401
1589
  export type SurfaceEdge = 'left' | 'right' | 'top' | 'bottom';
1402
1590
 
1591
+ /**
1592
+ * A surface rejection. The runtime carries the surface code on
1593
+ * `data.code` — `code` itself is the transport-level host code, shared
1594
+ * with every other `lx` rejection — so read it with
1595
+ * `surfaceErrorCode(error)` and never parse the message.
1596
+ * ```ts
1597
+ * import { surfaceErrorCode } from 'lingxia-types/error';
1598
+ * catch (error) {
1599
+ * if (surfaceErrorCode(error) === 'unsupported_placement') { … }
1600
+ * }
1601
+ * ```
1602
+ */
1603
+ export type SurfaceError = Error & {
1604
+ readonly data?: { readonly code?: SurfaceErrorCode };
1605
+ };
1606
+
1607
+ /**
1608
+ * Why a surface operation was refused. Carried as `code` on every
1609
+ * `SurfaceError`, so no caller has to match on message text.
1610
+ */
1611
+ export type SurfaceErrorCode = /** The placement cannot be realized by this host build. */
1612
+ 'unsupported_placement'
1613
+ /** A privileged operation was called by an lxapp other than the home lxapp. */
1614
+ | 'denied'
1615
+ /** No such declared surface, lxapp, or builtin page. */
1616
+ | 'not_declared'
1617
+ /** The arguments are malformed or combine options that cannot apply together. */
1618
+ | 'invalid_arg'
1619
+ /** The target is already open in a role this call cannot change. */
1620
+ | 'already_open_other_role'
1621
+ /** The surface has been closed; the handle is detached. */
1622
+ | 'closed'
1623
+ /** The host lacks a capability the request needs, such as an instantiable
1624
+ * native provider for a keyed surface. */
1625
+ | 'capability_missing'
1626
+ /** The operation reached the host and failed there. */
1627
+ | 'failed';
1628
+
1403
1629
  /** Where a float popup anchors (default `center`). */
1404
1630
  export type SurfaceFloatPosition = 'center' | 'top' | 'bottom' | 'left' | 'right';
1405
1631
 
1406
- export type SurfaceHandle = {
1407
- readonly id: string;
1408
- /** Standalone windows have no role in the primary shell graph. */
1409
- readonly role?: SurfaceRole;
1410
- readonly presentation: SurfacePresentation;
1411
- readonly visible: boolean;
1412
- readonly alive: boolean;
1413
- /**
1414
- * Show a host-managed surface. Dynamic page/url surfaces return a Promise;
1415
- * host-declared surfaces may complete synchronously.
1416
- */
1417
- show(): void | Promise<void>;
1418
- /**
1419
- * Hide without destroying user-visible state when the platform supports it.
1420
- */
1421
- hide(): void | Promise<void>;
1422
- /**
1423
- * Destroy the live surface. Repeated close calls are idempotent.
1424
- */
1425
- close(): void | Promise<void>;
1426
- onShow(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1427
- onHide(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1428
- onClose(handler: (event: SurfaceClosedEvent) => void): () => void;
1429
- };
1430
-
1431
1632
  /** Native interaction supplied by the host around page content. */
1432
1633
  export type SurfaceInteraction = {
1433
1634
  /** Show the standard circular close button. Default `false`. */
@@ -1438,32 +1639,241 @@ export type SurfaceInteraction = {
1438
1639
  modal?: boolean;
1439
1640
  };
1440
1641
 
1642
+ /**
1643
+ * Where the content came from. The discriminant on every surface
1644
+ * handle, so `AnySurface` narrows without a runtime `typeof` check.
1645
+ */
1646
+ export type SurfaceKind = 'page' | 'declared' | 'app' | 'tab' | 'builtin';
1647
+
1648
+ /** Two-way messaging, available when both sides are lxapp pages. */
1649
+ export type SurfaceMessaging = {
1650
+ /**
1651
+ * Send to the other side. For the opener this targets the opened page;
1652
+ * for the opened page it targets the opener.
1653
+ */
1654
+ postMessage(message: unknown): void;
1655
+ onMessage(handler: (message: unknown) => void): () => void;
1656
+ };
1657
+
1658
+ /**
1659
+ * What the host actually produced. Reported by `realized`, which is
1660
+ * how a caller reads the outcome of an ordered placement preference.
1661
+ */
1662
+ export type SurfacePlacement = 'main' | 'aside' | 'float' | 'window' | 'tab';
1663
+
1441
1664
  export type SurfacePresentation = 'main' | 'dock' | 'overlay' | 'popover' | 'sheet' | 'window';
1442
1665
 
1443
1666
  export type SurfaceRole = 'main' | 'aside' | 'float';
1444
1667
 
1668
+ /** Surfaces the host can hide and restore without losing page state. */
1669
+ export type SurfaceShowable = {
1670
+ /**
1671
+ * Restore a hidden surface. The page instance survived, so scroll
1672
+ * position, form input, and JS state come back with it. Idempotent.
1673
+ */
1674
+ show(): Promise<void>;
1675
+ /**
1676
+ * Hide without destroying. Main surfaces cannot be hidden and reject.
1677
+ * Idempotent.
1678
+ */
1679
+ hide(): Promise<void>;
1680
+ /** Fires on a real transition to visible, whichever side drove it. */
1681
+ onShow(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1682
+ /** Fires on a real transition to hidden, whichever side drove it. */
1683
+ onHide(handler: (event: SurfaceVisibilityEvent) => void): () => void;
1684
+ };
1685
+
1445
1686
  /**
1446
1687
  * Detail payload for `onShow` / `onHide` events. `source` identifies which
1447
1688
  * Surface object initiated the visibility change so observers can
1448
1689
  * distinguish self-driven transitions from peer-driven ones (e.g. an opener
1449
1690
  * UI that wants to update its own button state only when the page side
1450
- * toggled visibility).
1691
+ * toggled visibility). `shell` identifies a host-driven main switch.
1451
1692
  */
1452
1693
  export type SurfaceVisibilityEvent = {
1453
1694
  id: string;
1454
- kind: 'overlay' | 'window';
1455
- source: 'opener' | 'page';
1695
+ source: 'opener' | 'page' | 'shell';
1456
1696
  };
1457
1697
 
1458
1698
  export type SwitchTabOptions = PageTargetOptions;
1459
1699
 
1460
- /** Native system Downloads path. Do not pass this to `FileManager`. */
1700
+ /** Native system Downloads path. Do not pass this to `lx.fs`. */
1461
1701
  export type SystemDownloadsPath = string & {
1462
1702
  readonly [systemDownloadsPathBrand]: 'system-downloads-path';
1463
1703
  };
1464
1704
 
1465
- export type TabBarRedDotOptions = {
1705
+ export type TabBarApi = globalThis.TabBarApi;
1706
+
1707
+ export type TabBarItemPatch = {
1466
1708
  index: number;
1709
+ text?: string | null;
1710
+ iconPath?: string | null;
1711
+ selectedIconPath?: string | null;
1712
+ badge?: string | null;
1713
+ redDot?: boolean;
1714
+ };
1715
+
1716
+ export type TabBarPatch = {
1717
+ visibility?: TabBarVisibilityPreference;
1718
+ style?: TabBarStylePatch | null;
1719
+ items?: readonly TabBarItemPatch[];
1720
+ };
1721
+
1722
+ export type TabBarStylePatch = {
1723
+ foregroundColor?: string | null;
1724
+ selectedForegroundColor?: string | null;
1725
+ };
1726
+
1727
+ export type TabBarVisibilityPreference = 'auto' | 'visible' | 'hidden';
1728
+
1729
+ /** External content in the in-app browser. */
1730
+ export type TabSurface = SurfaceBase & {
1731
+ readonly kind: 'tab';
1732
+ readonly realized: 'tab' | 'aside';
1733
+ /**
1734
+ * `tab` when this handle owns exactly the tab it opened, and `close()` /
1735
+ * `activate()` act on it. `group` when the browser chrome owns the tab
1736
+ * strip: the content is open, but control belongs to that chrome, so both
1737
+ * methods reject with `unsupported_placement`. Branch on this rather than
1738
+ * on the old platform-dependent `null`.
1739
+ */
1740
+ readonly scope: 'tab' | 'group';
1741
+ /** Bring this tab to the front of its browser. `scope: 'group'` rejects. */
1742
+ activate(): Promise<void>;
1743
+ };
1744
+
1745
+ export type TerminalApi = {
1746
+ /** Saved terminal settings, revision-checked on write. */
1747
+ readonly settings: TerminalSettingsApi;
1748
+ /** Installed color schemes, plus import and live preview. */
1749
+ readonly colorSchemes: TerminalColorSchemesApi;
1750
+ /** Terminal fonts installed on this machine. */
1751
+ readonly fonts: TerminalFontsApi;
1752
+ /** Windows-only optional inline-image compatibility runtime. */
1753
+ readonly windows?: WindowsTerminalApi;
1754
+ };
1755
+
1756
+ export type TerminalColorScheme = {
1757
+ name?: string;
1758
+ background: string;
1759
+ foreground: string;
1760
+ cursorColor?: string;
1761
+ selectionBackground?: string;
1762
+ selectionForeground?: string;
1763
+ black: string;
1764
+ red: string;
1765
+ green: string;
1766
+ yellow: string;
1767
+ blue: string;
1768
+ purple: string;
1769
+ cyan: string;
1770
+ white: string;
1771
+ brightBlack: string;
1772
+ brightRed: string;
1773
+ brightGreen: string;
1774
+ brightYellow: string;
1775
+ brightBlue: string;
1776
+ brightPurple: string;
1777
+ brightCyan: string;
1778
+ brightWhite: string;
1779
+ };
1780
+
1781
+ export type TerminalColorSchemeDetails = {
1782
+ name: string;
1783
+ source: 'builtIn' | 'imported';
1784
+ scheme: TerminalColorScheme;
1785
+ };
1786
+
1787
+ export type TerminalColorSchemesApi = {
1788
+ list(): Promise<TerminalColorSchemeDetails[]>;
1789
+ import(options: {
1790
+ text: string;
1791
+ name?: string;
1792
+ /** Existing names are rejected unless overwrite is explicit. */
1793
+ overwrite?: boolean;
1794
+ }): Promise<TerminalColorSchemeDetails>;
1795
+ createPreview(): TerminalPreviewController;
1796
+ };
1797
+
1798
+ export type TerminalFontSettings = {
1799
+ /** Ordered candidates; the first installed monospaced family wins. */
1800
+ family: string[];
1801
+ size: number;
1802
+ lineHeight: number;
1803
+ ligatures: boolean;
1804
+ };
1805
+
1806
+ export type TerminalFontsApi = {
1807
+ list(): Promise<InstalledTerminalFont[]>;
1808
+ };
1809
+
1810
+ export type TerminalPreviewController = {
1811
+ /** Preview a stored name or an unpersisted scheme. Last request wins. */
1812
+ show(scheme: string | TerminalColorScheme): Promise<void>;
1813
+ /** Restore saved settings only when this controller owns the preview. */
1814
+ clear(): Promise<void>;
1815
+ /** Idempotently clear and retire this controller. */
1816
+ close(): Promise<void>;
1817
+ };
1818
+
1819
+ export type TerminalSettingsApi = {
1820
+ get(): Promise<TerminalSettingsSnapshot>;
1821
+ update(
1822
+ patch: TerminalSettingsPatch,
1823
+ options: { ifRevision: number },
1824
+ ): Promise<TerminalSettingsSnapshot>;
1825
+ reset(options: {
1826
+ ifRevision: number;
1827
+ scope?: 'font' | 'theme';
1828
+ }): Promise<TerminalSettingsSnapshot>;
1829
+ /** Fires after saved settings, effective appearance, or fonts change. */
1830
+ onChange(listener: (snapshot: TerminalSettingsSnapshot) => void): () => void;
1831
+ };
1832
+
1833
+ export type TerminalSettingsPatch = {
1834
+ font?: Partial<TerminalFontSettings>;
1835
+ theme?: Partial<TerminalThemeSettings>;
1836
+ };
1837
+
1838
+ export type TerminalSettingsSnapshot = {
1839
+ /** Monotonic process revision used by update/reset compare-and-swap. */
1840
+ revision: number;
1841
+ /** Framework defaults. */
1842
+ defaults: TerminalSettingsValue;
1843
+ /** User-authored fields only. */
1844
+ overrides: TerminalSettingsPatch;
1845
+ /** Resolved configuration after all valid layers. */
1846
+ value: TerminalSettingsValue;
1847
+ effective: {
1848
+ /** Host appearance before applying terminal.theme.mode. */
1849
+ systemAppearance: 'light' | 'dark';
1850
+ appearance: 'light' | 'dark';
1851
+ colorScheme: string | null;
1852
+ font: {
1853
+ family: string;
1854
+ missing: string[];
1855
+ fellBack: boolean;
1856
+ };
1857
+ };
1858
+ warnings: TerminalSettingsWarning[];
1859
+ };
1860
+
1861
+ export type TerminalSettingsValue = {
1862
+ font: TerminalFontSettings;
1863
+ theme: TerminalThemeSettings;
1864
+ };
1865
+
1866
+ export type TerminalSettingsWarning = {
1867
+ code: 'invalidUserFile' | 'missingColorScheme';
1868
+ message: string;
1869
+ };
1870
+
1871
+ export type TerminalThemeMode = 'system' | 'light' | 'dark';
1872
+
1873
+ export type TerminalThemeSettings = {
1874
+ mode: TerminalThemeMode;
1875
+ light: string;
1876
+ dark: string;
1467
1877
  };
1468
1878
 
1469
1879
  export type TrayApi = globalThis.TrayApi;
@@ -1493,11 +1903,17 @@ export type UpdateFailedInfo = UpdateReadyInfo & {
1493
1903
  error?: string;
1494
1904
  };
1495
1905
 
1496
- /** Runtime update APIs. */
1906
+ /**
1907
+ * Callback-based updates for this lxapp's bundle. Available to every
1908
+ * lxapp. To update the native host app, the home lxapp uses the
1909
+ * task-based `lx.app.checkUpdate()` API instead.
1910
+ */
1497
1911
  export type UpdateManager = {
1498
1912
  applyUpdate(): void;
1499
- onUpdateReady(callback: (info: UpdateReadyInfo) => void): void;
1500
- onUpdateFailed(callback: (info: UpdateFailedInfo) => void): void;
1913
+ /** Subscribes to a ready update and returns the unsubscribe fn. */
1914
+ onUpdateReady(callback: (info: UpdateReadyInfo) => void): () => void;
1915
+ /** Subscribes to a failed update and returns the unsubscribe fn. */
1916
+ onUpdateFailed(callback: (info: UpdateFailedInfo) => void): () => void;
1501
1917
  };
1502
1918
 
1503
1919
  export type UpdateReadyInfo = {
@@ -1511,35 +1927,91 @@ export type UploadIteratorResult = {
1511
1927
  value?: UploadProgressEvent;
1512
1928
  };
1513
1929
 
1930
+ /**
1931
+ * Upload options. The file streams from disk, so the size ceiling is
1932
+ * the remote's, not memory.
1933
+ * `bodyMode` picks the body shape, and with it which of the other
1934
+ * fields apply:
1935
+ * - `multipart` (default) wraps the file in a `multipart/form-data`
1936
+ * envelope beside the `formData` text fields — what an ordinary form
1937
+ * endpoint parses. `name`, `fileName`, and `formData` describe that
1938
+ * envelope.
1939
+ * - `raw` sends the file bytes as the entire body. Presigned
1940
+ * object-storage URLs (S3, OSS, Azure Blob) need this: a multipart
1941
+ * envelope would be stored verbatim as the object's contents,
1942
+ * boundary lines and all. `name` and `formData` are then rejected
1943
+ * rather than silently dropped, and `fileName` is ignored.
1944
+ * @example
1945
+ * ```ts
1946
+ * // A presigned URL is signed for one method and one Content-Type,
1947
+ * // so both have to match whatever the signer used.
1948
+ * const task = lx.uploadFile({
1949
+ * url: presignedUrl,
1950
+ * filePath: 'lx://media/clip.mp4',
1951
+ * method: 'PUT',
1952
+ * bodyMode: 'raw',
1953
+ * mimeType: 'video/mp4',
1954
+ * });
1955
+ * for await (const event of task) render(event.progress);
1956
+ * const { statusCode } = await task;
1957
+ * ```
1958
+ */
1514
1959
  export type UploadOptions = {
1515
1960
  /** HTTP(S) destination URL. */
1516
1961
  url: string;
1517
1962
  /** Local file path or runtime-managed URI to upload. */
1518
1963
  filePath: string;
1519
- /** Multipart field name. Default: `file`. */
1964
+ /**
1965
+ * HTTP method. Default: `POST`.
1966
+ * A presigned URL is signed for exactly one method, usually `PUT`.
1967
+ */
1968
+ method?: 'POST' | 'PUT' | 'PATCH';
1969
+ /**
1970
+ * How the file bytes are framed. Default: `multipart`.
1971
+ * `raw` sends them as the whole body under a `Content-Length` taken from
1972
+ * the file itself, which is what presigned endpoints require.
1973
+ */
1974
+ bodyMode?: 'multipart' | 'raw';
1975
+ /** Name of the multipart part carrying the file. Default: `file`. Multipart only. */
1520
1976
  name?: string;
1521
1977
  /**
1522
1978
  * Optional request headers.
1523
1979
  * Restricted headers such as `Referer` are ignored by the runtime.
1980
+ * `Content-Type` is yours to set only under `bodyMode: 'raw'`, where it
1981
+ * wins over `mimeType`; a multipart body owns the header, because it
1982
+ * carries the part boundary.
1524
1983
  */
1525
1984
  headers?: Record<string, string>;
1526
- /** Optional extra `multipart/form-data` text fields. */
1985
+ /** Text fields sent alongside the file in the envelope. Multipart only. */
1527
1986
  formData?: Record<string, string>;
1528
1987
  /** Request timeout in milliseconds. */
1529
1988
  timeout?: number;
1530
- /** Override multipart filename. */
1989
+ /** Filename announced for the file part. Defaults to the file's own name. Multipart only. */
1531
1990
  fileName?: string;
1532
- /** Override file MIME type. */
1991
+ /**
1992
+ * File MIME type. Types the file part under `multipart`; becomes the
1993
+ * request `Content-Type` under `raw`, where it defaults to
1994
+ * `application/octet-stream`.
1995
+ */
1533
1996
  mimeType?: string;
1534
1997
  /** Optional abort signal. */
1535
1998
  signal?: AbortSignal;
1536
1999
  };
1537
2000
 
1538
2001
  export type UploadProgressEvent = {
2002
+ /** `completed` and `canceled` are terminal; iteration ends after either. */
1539
2003
  kind: 'progress' | 'canceled' | 'completed';
2004
+ /** Bytes handed to the socket so far, envelope included under `multipart`. */
1540
2005
  uploadedBytes?: number;
2006
+ /**
2007
+ * Bytes the whole request body will carry. Equals the file size under
2008
+ * `bodyMode: 'raw'`; under `multipart` it also covers the envelope, so it
2009
+ * runs slightly above the file size.
2010
+ */
1541
2011
  totalBytes?: number;
2012
+ /** `uploadedBytes / totalBytes`, absent while the total is unknown or zero. */
1542
2013
  progress?: number;
2014
+ /** Present on `completed` only. */
1543
2015
  result?: UploadResult;
1544
2016
  };
1545
2017
 
@@ -1635,6 +2107,8 @@ export type VideoInfo = {
1635
2107
  path: string;
1636
2108
  };
1637
2109
 
2110
+ export type VisibilityPreference = 'auto' | 'hidden';
2111
+
1638
2112
  export type WifiConnectedCallback = (info: WifiConnectedInfo) => void;
1639
2113
 
1640
2114
  export type WifiConnectedInfo = WifiInfo & {
@@ -1642,6 +2116,16 @@ export type WifiConnectedInfo = WifiInfo & {
1642
2116
  state: string;
1643
2117
  };
1644
2118
 
2119
+ /**
2120
+ * Window decoration. `system` is the standard title bar. `full`
2121
+ * extends the page to the window edge while keeping the system
2122
+ * minimize, maximize, resize, and drag affordances — the runtime owns
2123
+ * a native drag strip across the top and publishes its height as
2124
+ * `topInset` on the page-chrome snapshot, so a page that does nothing
2125
+ * to opt in still cannot trap the user.
2126
+ */
2127
+ export type WindowChrome = 'system' | 'full';
2128
+
1645
2129
  export type WindowSurfaceSize = {
1646
2130
  /** Initial window width in logical pixels. */
1647
2131
  width?: number;
@@ -1649,22 +2133,23 @@ export type WindowSurfaceSize = {
1649
2133
  height?: number;
1650
2134
  };
1651
2135
 
1652
- export type WriteBinaryFileOptions = {
1653
- filePath: string;
1654
- data: BinaryFileData;
1655
- encoding?: never;
1656
- /** Defaults to false. */
1657
- overwrite?: boolean;
2136
+ export type WindowsTerminalApi = {
2137
+ status(): Promise<WindowsTerminalInlineImageStatus>;
2138
+ /** Verify and install the fixed Microsoft ConPTY package from lxapp temp storage. */
2139
+ install(options: { path: string }): Promise<WindowsTerminalInlineImageStatus>;
2140
+ /** Select the installed runtime for new terminal sessions. */
2141
+ setEnabled(options: { enabled: boolean }): Promise<WindowsTerminalInlineImageStatus>;
1658
2142
  };
1659
2143
 
1660
- export type WriteFileOptions = WriteTextFileOptions | WriteBinaryFileOptions;
1661
-
1662
- export type WriteTextFileOptions = {
1663
- filePath: string;
1664
- data: string;
1665
- encoding?: 'utf8' | 'base64';
1666
- /** Defaults to false. */
1667
- overwrite?: boolean;
2144
+ export type WindowsTerminalInlineImageStatus = {
2145
+ enabled: boolean;
2146
+ installed: boolean;
2147
+ package: {
2148
+ version: string;
2149
+ url: string;
2150
+ sha256: string;
2151
+ bytes: number;
2152
+ };
1668
2153
  };
1669
2154
 
1670
2155
  /** Host app base information. */
@@ -1690,6 +2175,11 @@ export interface AppBaseInfo {
1690
2175
  SDKVersion: string;
1691
2176
  }
1692
2177
 
2178
+ export interface AppearanceState {
2179
+ preference: AppearancePreference;
2180
+ resolved: ResolvedAppearance;
2181
+ }
2182
+
1693
2183
  /** Device info APIs. */
1694
2184
  export interface DeviceInfo {
1695
2185
  brand: string;
@@ -1741,50 +2231,12 @@ export interface LxAppInfo {
1741
2231
  releaseType: LxAppReleaseType;
1742
2232
  }
1743
2233
 
1744
- /** Options for removing TabBar badge */
1745
- export interface RemoveTabBarBadgeOptions {
1746
- index: number;
1747
- }
1748
-
1749
2234
  export interface ScreenInfo {
1750
2235
  width: number;
1751
2236
  height: number;
1752
2237
  scale: number;
1753
2238
  }
1754
2239
 
1755
- /** Options for setNavigationBarColor */
1756
- export interface SetNavigationBarColorOptions {
1757
- frontColor: string;
1758
- backgroundColor: string;
1759
- }
1760
-
1761
- /** Options for setNavigationBarTitle */
1762
- export interface SetNavigationBarTitleOptions {
1763
- title: string;
1764
- }
1765
-
1766
- /** Options for setting TabBar badge */
1767
- export interface SetTabBarBadgeOptions {
1768
- index: number;
1769
- text: string;
1770
- }
1771
-
1772
- /** Options for setting TabBar item */
1773
- export interface SetTabBarItemOptions {
1774
- index: number;
1775
- text?: string;
1776
- iconPath?: string;
1777
- selectedIconPath?: string;
1778
- }
1779
-
1780
- /** Options for setting TabBar style */
1781
- export interface SetTabBarStyleOptions {
1782
- color?: string;
1783
- selectedColor?: string;
1784
- backgroundColor?: string;
1785
- borderStyle?: string;
1786
- }
1787
-
1788
2240
  /** System setting status */
1789
2241
  export interface SystemSettingInfo {
1790
2242
  bluetoothEnabled: boolean;
@@ -1814,19 +2266,6 @@ export declare class DirEntry {
1814
2266
  readonly isSymlink: boolean;
1815
2267
  }
1816
2268
 
1817
- export declare class FileManager {
1818
- private constructor();
1819
- exists(options: ExistsOptions): Promise<boolean>;
1820
- stat(options: StatOptions): Promise<FileStats>;
1821
- readDir(options: ReadDirOptions): Promise<AsyncIterableIterator<DirEntry>>;
1822
- mkdir(options: MkdirOptions): Promise<void>;
1823
- readFile(options: never): Promise<never>;
1824
- writeFile(options: WriteFileOptions): Promise<void>;
1825
- copyFile(options: CopyFileOptions): Promise<void>;
1826
- rename(options: RenameOptions): Promise<void>;
1827
- remove(options: RemoveOptions): Promise<void>;
1828
- }
1829
-
1830
2269
  export declare class JSMessagePort {
1831
2270
  constructor();
1832
2271
  static postMessage(payload: any): void;
@@ -1845,8 +2284,10 @@ export declare class JSUpdateManager {
1845
2284
  constructor();
1846
2285
  /** Apply update by restarting the app */
1847
2286
  applyUpdate(): void;
1848
- onUpdateReady(cb: (...args: any[]) => any): void;
1849
- onUpdateFailed(cb: (...args: any[]) => any): void;
2287
+ /** Subscribes to a ready update and returns the unsubscribe fn. */
2288
+ onUpdateReady(cb: (...args: any[]) => any): (...args: any[]) => any;
2289
+ /** Subscribes to a failed update and returns the unsubscribe fn. */
2290
+ onUpdateFailed(cb: (...args: any[]) => any): (...args: any[]) => any;
1850
2291
  }
1851
2292
 
1852
2293
  export declare class JSVideoContext {
@@ -1860,6 +2301,67 @@ export declare class JSVideoContext {
1860
2301
  setStreamSource(options: StreamSourceOptions): void;
1861
2302
  }
1862
2303
 
2304
+ export declare class LxFile {
2305
+ private constructor();
2306
+ /** The path supplied to `lx.fs.file`. */
2307
+ readonly path: string;
2308
+ /** Read the complete file as strict UTF-8 text. */
2309
+ text(): Promise<string>;
2310
+ /**
2311
+ * Read and parse the complete file as JSON. Stays `unknown`: a class
2312
+ * method cannot carry a type parameter through the binding, so unlike
2313
+ * `lx.getStorage().get<T>()` the assertion is spelled `as` at the call
2314
+ * site rather than passed in.
2315
+ */
2316
+ json(): Promise<unknown>;
2317
+ /** Read the complete file as a Base64 string. */
2318
+ base64(): Promise<string>;
2319
+ /** Read the complete file as bytes. */
2320
+ bytes(): Promise<Uint8Array>;
2321
+ /** Read the complete file as an ArrayBuffer. */
2322
+ arrayBuffer(): Promise<ArrayBuffer>;
2323
+ /** Test whether this managed path currently exists. */
2324
+ exists(): Promise<boolean>;
2325
+ /** Read metadata for this managed path. */
2326
+ stat(): Promise<FileStats>;
2327
+ }
2328
+
2329
+ declare global {
2330
+ interface AppearanceApi {
2331
+ /** Read the appearance preference and the light/dark value it resolves to. */
2332
+ get(): AppearanceState;
2333
+ /** Set the appearance preference to `auto`, `light`, or `dark`. */
2334
+ set(preference: AppearancePreference): Promise<void>;
2335
+ }
2336
+ }
2337
+
2338
+ declare global {
2339
+ interface FileSystemApi {
2340
+ /**
2341
+ * Create a lazy reference to a LingXia-managed path.
2342
+ * Relative paths resolve under `lx.env.USER_DATA_PATH`. Creating a reference
2343
+ * does not require the path to exist.
2344
+ */
2345
+ file(path: string): LxFile;
2346
+ /** Test whether a managed path currently exists. */
2347
+ exists(path: string): Promise<boolean>;
2348
+ /** Read metadata for a managed path. */
2349
+ stat(path: string): Promise<FileStats>;
2350
+ /** The direct children of a managed directory. */
2351
+ readDir(path: string): Promise<DirEntry[]>;
2352
+ /** Create a managed directory. */
2353
+ mkdir(path: string, options?: FsMkdirOptions): Promise<void>;
2354
+ /** Write UTF-8 text or bytes to a managed file. */
2355
+ write(path: string, data: string, options?: FsWriteOptions): Promise<void>;
2356
+ /** Copy a managed file. */
2357
+ copy(source: string, destination: string, options?: FsCopyOptions): Promise<void>;
2358
+ /** Rename or move a managed file or directory. */
2359
+ rename(source: string, destination: string, options?: FsRenameOptions): Promise<void>;
2360
+ /** Remove a managed file or directory. */
2361
+ remove(path: string, options?: FsRemoveOptions): Promise<void>;
2362
+ }
2363
+ }
2364
+
1863
2365
  declare global {
1864
2366
  interface HostAppApi {
1865
2367
  /**
@@ -1880,7 +2382,19 @@ declare global {
1880
2382
  */
1881
2383
  checkUpdate(): Promise<HostAppUpdateCheckResult>;
1882
2384
  readonly envVersion: HostAppEnvVersion;
2385
+ /**
2386
+ * Read the host app's identity: locale, display language, OS, product name,
2387
+ * product version, and SDK runtime version.
2388
+ */
1883
2389
  getBaseInfo(): AppBaseInfo;
2390
+ /**
2391
+ * Follow the host's effective display language.
2392
+ * `getBaseInfo().displayLanguage` answers what it is now; this answers when it
2393
+ * changes. Logic needs both because the strings it hands to native chrome —
2394
+ * navigation bar titles, tab bar labels, modal and action-sheet text — are the
2395
+ * app's own, and nothing re-renders them on its behalf.
2396
+ */
2397
+ onDisplayLanguageChange(callback: (language: string) => void): () => void;
1884
2398
  /**
1885
2399
  * Exit the host app immediately without a confirmation dialog.
1886
2400
  * If the user should confirm first, call `lx.showModal(...)` and invoke this
@@ -1900,14 +2414,31 @@ declare global {
1900
2414
  declare global {
1901
2415
  interface Lx {
1902
2416
  readonly app: HostAppApi;
2417
+ /**
2418
+ * Whether this host exposes a capability to this Logic context, right now.
2419
+ * Synchronous, because it is meant to be called from render paths. The answer
2420
+ * is live and may be stale by the time you act on it — it is an affordance for
2421
+ * deciding what to render, not a replacement for handling a rejection.
2422
+ * `{ capability: 'surface', value: 'aside' }` in particular changes when a
2423
+ * desktop window crosses the compact breakpoint; pair it with
2424
+ * `lx.surface.onContext` instead of polling. The answer is per runtime context:
2425
+ * a context that does not expose an API reports false for it.
2426
+ */
2427
+ supports(query: LxCapabilityQuery): boolean;
2428
+ /** Vibrate briefly, where the device has a vibrator. */
1903
2429
  vibrateShort(): boolean;
2430
+ /** Vibrate for a longer pulse, where the device has a vibrator. */
1904
2431
  vibrateLong(): boolean;
2432
+ /** Hand a number to the system dialer; the user still places the call. */
1905
2433
  makePhoneCall(options: MakePhoneCallOptions): boolean;
2434
+ /** Read the device and OS facts this host reports. */
1906
2435
  getDeviceInfo(): DeviceInfo;
2436
+ /** Read the screen geometry and pixel ratio this host reports. */
1907
2437
  getScreenInfo(): ScreenInfo;
2438
+ /** Read connectivity right now: whether it is connected, its type, and addresses. */
1908
2439
  getNetworkInfo(): Promise<NetworkInfo>;
1909
- onNetworkChange(callback: NetworkChangeCallback): void;
1910
- offNetworkChange(callback?: NetworkChangeCallback): void;
2440
+ /** Subscribes to network changes and returns the unsubscribe fn. */
2441
+ onNetworkChange(callback: NetworkChangeCallback): () => void;
1911
2442
  /** Initialize WiFi module */
1912
2443
  startWifi(): Promise<void>;
1913
2444
  /** Stop WiFi module */
@@ -1922,13 +2453,27 @@ declare global {
1922
2453
  getWifiList(): Promise<WifiInfo[]>;
1923
2454
  /** Get connected WiFi info */
1924
2455
  getConnectedWifi(): Promise<WifiInfo>;
1925
- onWifiConnected(callback: WifiConnectedCallback): void;
1926
- offWifiConnected(callback?: WifiConnectedCallback): void;
2456
+ /** Subscribes to WiFi connection events and returns the unsubscribe fn. */
2457
+ onWifiConnected(callback: WifiConnectedCallback): () => void;
2458
+ /**
2459
+ * Lock this lxapp to `portrait` or `landscape`.
2460
+ * Any other value rejects. Where the host does not report the change back,
2461
+ * the runtime emits the orientation event itself so JS state stays in sync.
2462
+ */
1927
2463
  setDeviceOrientation(orientation: DeviceOrientation): boolean;
1928
- onDeviceOrientationChange(callback: (event: DeviceOrientationChangeEvent) => void): void;
1929
- offDeviceOrientationChange(callback?: (event: DeviceOrientationChangeEvent) => void): void;
2464
+ /** Subscribes to orientation changes and returns the unsubscribe fn. */
2465
+ onDeviceOrientationChange(callback: (event: DeviceOrientationChangeEvent) => void): () => void;
1930
2466
  readonly env: LxEnv;
1931
2467
  downloadFile(options: never): never;
2468
+ /**
2469
+ * Upload a file over HTTP, streamed from disk.
2470
+ * Defaults to a `POST` with a `multipart/form-data` body. Set
2471
+ * `method: 'PUT'` with `bodyMode: 'raw'` to send the file bytes as the whole
2472
+ * body instead, which is what presigned object-storage URLs expect.
2473
+ * Returns the task handle synchronously, before the transfer starts, so
2474
+ * progress and cancellation can be wired up without racing it: the handle is
2475
+ * awaitable for the final result, async-iterable for progress, and cancelable.
2476
+ */
1932
2477
  uploadFile(options: UploadOptions): UploadTask;
1933
2478
  /**
1934
2479
  * Open a local file with the requested strategy.
@@ -1936,19 +2481,41 @@ declare global {
1936
2481
  * `mode: "auto"`.
1937
2482
  */
1938
2483
  openFile(options: OpenFileOptions): Promise<void>;
2484
+ /**
2485
+ * Opens a file picker.
2486
+ * Resolves `{ canceled: true }` only when the user dismisses the picker. A
2487
+ * completed selection resolves `{ canceled: false, paths }` with at least one
2488
+ * path. Rejects when the picker fails or returns an invalid payload.
2489
+ */
1939
2490
  chooseFile(options?: ChooseFileOptions): Promise<ChooseFileResult>;
2491
+ /**
2492
+ * Opens a directory picker.
2493
+ * Resolves `{ canceled: true }` only when the user dismisses the picker. A
2494
+ * completed selection resolves `{ canceled: false, path }`. Rejects when the
2495
+ * picker fails or returns an invalid payload.
2496
+ */
1940
2497
  chooseDirectory(options?: ChooseDirectoryOptions): Promise<ChooseDirectoryResult>;
1941
- getFileManager(): FileManager;
1942
- onKeyDown(callback: KeyEventCallback): void;
1943
- offKeyDown(callback?: KeyEventCallback): void;
1944
- onKeyUp(callback: KeyEventCallback): void;
1945
- offKeyUp(callback?: KeyEventCallback): void;
2498
+ readonly fs: FileSystemApi;
2499
+ /** Subscribes to key-down events and returns the unsubscribe fn. */
2500
+ onKeyDown(callback: KeyEventCallback): () => void;
2501
+ /** Subscribes to key-up events and returns the unsubscribe fn. */
2502
+ onKeyUp(callback: KeyEventCallback): () => void;
1946
2503
  /** Get location function */
1947
2504
  getLocation(options?: GetLocationOptions): Promise<LocationInfo>;
2505
+ /** Identify the running lxapp: its id, display name, version, and release type. */
1948
2506
  getLxAppInfo(): LxAppInfo;
2507
+ /** Read an image's dimensions, type, and orientation without decoding it into a view. */
1949
2508
  getImageInfo(options: GetImageInfoOptions): Promise<ImageInfo>;
2509
+ /** Re-encode an image at a lower quality or size, writing a new managed file. */
1950
2510
  compressImage(options: CompressImageOptions): Promise<CompressImageResult>;
1951
- chooseMedia(options?: ChooseMediaOptions): Promise<ChosenMediaEntry[]>;
2511
+ /**
2512
+ * Opens the media picker or camera.
2513
+ * Resolves `{ canceled: true }` only when the user dismisses the picker. A
2514
+ * completed selection resolves `{ canceled: false, entries }` with at least one
2515
+ * entry. Rejects when capture or selection fails, or the host returns an invalid
2516
+ * payload.
2517
+ */
2518
+ chooseMedia(options?: ChooseMediaOptions): Promise<ChooseMediaResult>;
1952
2519
  /**
1953
2520
  * Synchronously returns a JS handle so listeners can be attached before the
1954
2521
  * first event fires:
@@ -1967,9 +2534,18 @@ declare global {
1967
2534
  * caller never re-indexes their own array.
1968
2535
  */
1969
2536
  previewMedia(options: PreviewMediaOptions): PreviewMediaHandle;
2537
+ /** Save an image into the system photo library. */
1970
2538
  saveImageToPhotosAlbum(options: SaveMediaOptions): Promise<void>;
2539
+ /** Save a video into the system photo library. */
1971
2540
  saveVideoToPhotosAlbum(options: SaveMediaOptions): Promise<void>;
2541
+ /**
2542
+ * Opens the scanner.
2543
+ * Resolves `{ canceled: true }` only when the user dismisses the scanner. A
2544
+ * completed scan resolves `{ canceled: false, scanResult, scanType }`. Rejects
2545
+ * when scanning fails or the host returns an invalid payload.
2546
+ */
1972
2547
  scanCode(options?: ScanCodeOptions): Promise<ScanCodeResult>;
2548
+ /** Take a control handle for the `<lx-video>` component with this id. */
1973
2549
  createVideoContext(componentId: string): VideoContext;
1974
2550
  /**
1975
2551
  * Reads local video metadata for upload preflight and presentation.
@@ -1979,47 +2555,63 @@ declare global {
1979
2555
  * still validate the uploaded bytes.
1980
2556
  */
1981
2557
  getVideoInfo(options: GetVideoInfoOptions): Promise<VideoInfo>;
2558
+ /** Write one frame of a video out as an image file. */
1982
2559
  extractVideoThumbnail(options: ExtractVideoThumbnailOptions): Promise<ExtractVideoThumbnailResult>;
2560
+ /**
2561
+ * Transcode a video to a smaller file.
2562
+ * Returns a task handle synchronously, so progress and cancellation can be
2563
+ * wired up before transcoding starts.
2564
+ */
1983
2565
  compressVideo(options: CompressVideoOptions): CompressVideoTask;
1984
- navigateToLxApp(options: NavigateToLxAppOptions): Promise<void>;
1985
- navigateBackLxApp(): Promise<void>;
2566
+ /**
2567
+ * Open another lxapp, optionally at one of its pages.
2568
+ * Navigating to the lxapp already running is a no-op. Rejects with
2569
+ * `E_SURFACE_CONFLICT` when the target is currently docked as an aside —
2570
+ * close that aside before opening it as a main.
2571
+ */
2572
+ navigateToApp(options: NavigateToAppOptions): Promise<void>;
2573
+ /** Leave this lxapp and reveal the one that opened it. */
2574
+ navigateBackApp(): Promise<void>;
2575
+ /**
2576
+ * Hand content to the system share sheet.
2577
+ * Share text, files, or a page link — files cannot be combined with a page
2578
+ * target or with text; share those separately.
2579
+ */
1986
2580
  share(options: ShareOptions): Promise<ShareResult>;
1987
- getStorage(): Storage;
1988
2581
  /**
1989
- * `lx.openSurface(spec)` — unified surface entry point. The spec is a
1990
- * discriminated union keyed by exactly one of `page`, `surface`, or `url`:
1991
- * - `{ page, as, position?, size?, query? }` opens one of this lxapp's own
1992
- * pages as a `float` (overlay popup) or a `window` (bare standalone desktop
1993
- * window). Pages cannot be docked as an `aside` — an aside shows external
1994
- * content only.
1995
- * - `{ surface, edge?, query? }` shows a host-declared surface by its `ui` id.
1996
- * - `{ url }` opens an authorized HTTPS/file URL in the in-app chromed browser.
2582
+ * Open this lxapp's asynchronous persistent key-value store. `get` asserts the
2583
+ * value shape at the call site and resolves `undefined` for a missing key. Use
2584
+ * `lx.fs` instead for path-based data.
1997
2585
  */
1998
- openSurface(spec: never): Promise<never>;
2586
+ getStorage(): Storage;
1999
2587
  /** `lx.openExternal(url)` — hand the url off to the OS default browser. */
2000
2588
  openExternal(url: string): void;
2589
+ readonly surface: SurfaceApi;
2590
+ /** Read system switches the lxapp may branch on, such as location and WiFi. */
2591
+ getSystemSetting(): SystemSettingInfo;
2001
2592
  /**
2002
- * `lx.onSurfaceContext(handler)` — register a JS callback (scoped to this
2003
- * lxapp's JS context), invoke it immediately, then again whenever that
2004
- * presentation's actual viewport changes. Returns an unsubscribe fn.
2593
+ * Shows a list of actions.
2594
+ * Resolves `{ canceled: false, index }` when the user selects an item; `index`
2595
+ * points into `options.itemList`. Resolves `{ canceled: true }` only when the
2596
+ * user dismisses the sheet. Rejects when presentation fails or the host returns
2597
+ * an invalid selection.
2005
2598
  */
2006
- onSurfaceContext(handler: (context: SurfaceContext) => void): () => void;
2007
- getSystemSetting(): SystemSettingInfo;
2008
- /** Show action sheet function for JavaScript */
2009
2599
  showActionSheet(options: ShowActionSheetOptions): Promise<ActionSheetResult>;
2600
+ readonly appearance: AppearanceApi;
2010
2601
  /**
2011
- * Get capsule button bounding client rect (async)
2012
- * Returns Promise<{width, height, top, right, bottom, left}>
2602
+ * Shows a confirmation modal.
2603
+ * Resolves `{ canceled: false }` when the user confirms and `{ canceled: true }`
2604
+ * only when the user dismisses or cancels the modal. Rejects when presentation
2605
+ * fails or the host returns an invalid payload.
2013
2606
  */
2014
- getCapsuleRect(): Promise<CapsuleRect>;
2015
- /** Show modal function (async) */
2016
2607
  showModal(options: ShowModalOptions): Promise<ModalResult>;
2017
- /** Set navigation bar title */
2018
- setNavigationBarTitle(options: SetNavigationBarTitleOptions): boolean;
2019
- /** Set navigation bar color */
2020
- setNavigationBarColor(options: SetNavigationBarColorOptions): boolean;
2021
- /** Hide home button */
2022
- hideHomeButton(): boolean;
2608
+ /**
2609
+ * Replace the current lxapp's complete app-declared More action list (seven
2610
+ * entries maximum). Pass an empty array to clear it. Native hosts append these
2611
+ * entries after their own lifecycle actions.
2612
+ */
2613
+ setMoreActions(items: MoreAction[]): void;
2614
+ readonly navigationBar: NavigationBarApi;
2023
2615
  /**
2024
2616
  * lx.startPullDownRefresh()
2025
2617
  * Programmatically start the pull-to-refresh animation.
@@ -2032,38 +2624,47 @@ declare global {
2032
2624
  * This should be called after the refresh operation is complete.
2033
2625
  */
2034
2626
  stopPullDownRefresh(): void;
2035
- /** Navigate to a new page (forward navigation) */
2627
+ /**
2628
+ * Push a configured page onto the stack.
2629
+ * A route can appear on the stack only once. The promise rejects with
2630
+ * `data.reason === "duplicate_route"` when the target is already present,
2631
+ * or `data.reason === "stack_full"` when the ten-page limit is reached.
2632
+ */
2036
2633
  navigateTo(options: NavigateToOptions): Promise<PageMessagePort>;
2037
- /** Navigate back to previous page */
2038
- navigateBack(options: NavigateBackOptions): void;
2039
- /** Redirect to a new page (replace current page) */
2634
+ /**
2635
+ * Pop one or more pages and reveal the destination page.
2636
+ * `options` and `options.delta` are optional; both default to one page. The
2637
+ * promise resolves once the destination WebView is ready, so callers can
2638
+ * safely continue with work that targets the revealed page.
2639
+ */
2640
+ navigateBack(options?: NavigateBackOptions): Promise<void>;
2641
+ /**
2642
+ * Replace the current stack entry with a configured page.
2643
+ * Redirecting to the current route keeps its page instance and runs `onLoad`
2644
+ * again with the new query. Redirecting to a route lower in the stack rejects
2645
+ * with `data.reason === "duplicate_route"`.
2646
+ */
2040
2647
  redirectTo(options: RedirectToOptions): Promise<void>;
2041
- /** Switch to a tab page */
2648
+ /**
2649
+ * Switch to a configured tab page.
2650
+ * The tab page being left is hidden and retained. Non-tab pages pushed above
2651
+ * a tab leave the stack and receive `onUnload`.
2652
+ */
2042
2653
  switchTab(options: SwitchTabOptions): Promise<void>;
2043
- /** Relaunch to a new page (clear page stack) */
2654
+ /** Clear the page stack and launch a configured page as the new root. */
2044
2655
  reLaunch(options: ReLaunchOptions): Promise<void>;
2045
2656
  readonly shell: ShellApi;
2046
- /** Show TabBar red dot */
2047
- showTabBarRedDot(options: TabBarRedDotOptions): boolean;
2048
- /** Hide TabBar red dot */
2049
- hideTabBarRedDot(options: TabBarRedDotOptions): boolean;
2050
- /** Set TabBar badge */
2051
- setTabBarBadge(options: SetTabBarBadgeOptions): boolean;
2052
- /** Remove TabBar badge */
2053
- removeTabBarBadge(options: RemoveTabBarBadgeOptions): boolean;
2054
- /** Show TabBar */
2055
- showTabBar(): Promise<boolean>;
2056
- /** Hide TabBar */
2057
- hideTabBar(): Promise<boolean>;
2058
- /** Set TabBar style */
2059
- setTabBarStyle(options: SetTabBarStyleOptions): boolean;
2060
- /** Set TabBar item */
2061
- setTabBarItem(options: SetTabBarItemOptions): boolean;
2657
+ readonly tabBar: TabBarApi;
2062
2658
  /** Show toast function */
2063
2659
  showToast(options: ShowToastOptions): Promise<void>;
2064
2660
  /** Hide toast function */
2065
2661
  hideToast(): Promise<void>;
2066
2662
  readonly tray: TrayApi;
2663
+ /**
2664
+ * Return the callback-based update manager for this lxapp's bundle. This is
2665
+ * available to every lxapp and is distinct from the home-only
2666
+ * `lx.app.checkUpdate()`, which updates the native host app.
2667
+ */
2067
2668
  getUpdateManager(): UpdateManager;
2068
2669
  }
2069
2670
  }
@@ -2076,22 +2677,111 @@ declare global {
2076
2677
  }
2077
2678
 
2078
2679
  declare global {
2079
- interface ShellActivatorsApi {
2680
+ interface NavigationBarApi {
2681
+ /** Patch the navigation bar of the active page; unset fields stay as they are. */
2682
+ update(patch: NavigationBarPatch): Promise<void>;
2683
+ }
2684
+ }
2685
+
2686
+ declare global {
2687
+ interface ShellApi {
2688
+ /**
2689
+ * `lx.shell.openApp(appId, options)` — compose another lxapp into a shell
2690
+ * slot. Home-lxapp only; the namespace is the privilege.
2691
+ */
2692
+ openApp(appId: string, options: ShellOpenAppOptions): Promise<AppSurface>;
2693
+ /** `lx.shell.openBuiltin(page)` — a host builtin page. Home-lxapp only. */
2694
+ openBuiltin(page: BuiltinShellPage): Promise<BuiltinSurface>;
2080
2695
  /**
2081
- * Atomically replaces the complete desktop activator declaration. Home lxapp
2082
- * only. Relative icons resolve from the home app bundle. Every entry is bound
2083
- * to its generation-scoped callback; `replace([])` explicitly clears chrome.
2696
+ * `lx.shell.openDeclared(id, options?)` — the declared surface, plus the
2697
+ * keyed multi-instance form and placement overrides. Home-lxapp only.
2698
+ */
2699
+ openDeclared(id: string, options?: ShellOpenDeclaredOptions): Promise<DeclaredSurface>;
2700
+ /** `lx.shell.reconfigure(id, patch)` — re-place a live declared surface. */
2701
+ reconfigure(id: string, patch: ShellSurfacePatch): Promise<void>;
2702
+ }
2703
+ }
2704
+
2705
+ declare global {
2706
+ interface ShellSidebarActionsApi {
2707
+ /**
2708
+ * Atomically replaces the complete desktop sidebar action declaration. Only the
2709
+ * home lxapp may call this API. Ids must be non-empty and unique across both
2710
+ * placements; header accepts at most two entries. Icons must be bundled relative
2711
+ * paths or runtime-managed `lx://` paths accessible to the home lxapp.
2712
+ * Every entry is bound to its generation-scoped callback. The shell invokes that
2713
+ * callback but never infers navigation or selected state. Validation or host
2714
+ * projection failure leaves the previous generation active. `replace([])` clears
2715
+ * the chrome explicitly. Declarations are process-local, so call `replace` again
2716
+ * on every Logic launch.
2717
+ */
2718
+ replace(items: ShellSidebarAction[]): void;
2719
+ /**
2720
+ * Atomically updates the icon, label, and/or disabled state of one stable id.
2721
+ * Only the home lxapp may call this API. The patch must be non-empty; unknown
2722
+ * fields are rejected. The callback and placement stay unchanged. Throws
2723
+ * `E_NOT_FOUND` when `id` is not in the current declaration.
2724
+ */
2725
+ update(id: string, patch: ShellSidebarActionUpdate): void;
2726
+ /**
2727
+ * Atomically removes one stable id and its generation-scoped callback. Only the
2728
+ * home lxapp may call this API. Throws `E_NOT_FOUND` when `id` is not in the
2729
+ * current declaration.
2084
2730
  */
2085
- replace(items: ShellActivator[]): void;
2086
- /** Updates presentation fields for one stable id. Home lxapp only. */
2087
- update(id: string, patch: ShellActivatorUpdate): void;
2088
- /** Removes one stable id from the declaration. Home lxapp only. */
2089
2731
  remove(id: string): void;
2090
- /** Clears the current runtime declaration. Home lxapp only. */
2732
+ /**
2733
+ * Atomically clears every runtime sidebar action and callback. Only the home
2734
+ * lxapp may call this API. Equivalent to `replace([])` and safe when already
2735
+ * empty; the home lxapp must still redeclare actions after the next Logic launch.
2736
+ */
2091
2737
  clear(): void;
2092
2738
  }
2093
2739
  }
2094
2740
 
2741
+ declare global {
2742
+ interface SurfaceApi {
2743
+ /**
2744
+ * `lx.surface.openPage(page, options?)` — one of this lxapp's own pages as a
2745
+ * float or a window. A page can never be an aside: asides carry external
2746
+ * content only, which is why that member does not exist on this signature.
2747
+ */
2748
+ openPage(page: string, options?: OpenPageOptions): Promise<PageSurface>;
2749
+ /**
2750
+ * `lx.surface.openUrl(url, options?)` — external content in the in-app
2751
+ * browser, as a tab or docked as an aside.
2752
+ */
2753
+ openUrl(url: string, options?: OpenUrlOptions): Promise<TabSurface>;
2754
+ /**
2755
+ * `lx.surface.openDeclared(id, options?)` — a surface the host declared in
2756
+ * `lingxia.yaml`, opened with the declaration's own presentation.
2757
+ */
2758
+ openDeclared(id: string): Promise<DeclaredSurface>;
2759
+ /**
2760
+ * `lx.surface.get(keyOrId)` — the live handle for a surface this lxapp opened
2761
+ * **with a `key`**, so no caller has to cache one in order to reuse or close
2762
+ * it. An unkeyed surface is not addressable: nothing registers it, because
2763
+ * holding one for the session costs its closures and its message port and
2764
+ * nobody can look up a uuid they never chose.
2765
+ * A `key` you chose wins over a runtime-assigned `id`, so a key that happens
2766
+ * to spell another surface's id still finds yours.
2767
+ */
2768
+ get(keyOrId: string): AnySurface | undefined;
2769
+ /**
2770
+ * `lx.surface.onContext(handler)` — register a JS callback (scoped to this
2771
+ * lxapp's JS context), invoke it immediately, then again whenever that
2772
+ * presentation's actual viewport changes. Returns an unsubscribe fn.
2773
+ */
2774
+ onContext(handler: (context: SurfaceContext) => void): () => void;
2775
+ }
2776
+ }
2777
+
2778
+ declare global {
2779
+ interface TabBarApi {
2780
+ /** Patch this lxapp's tab bar; unset fields stay as they are. */
2781
+ update(patch: TabBarPatch): Promise<void>;
2782
+ }
2783
+ }
2784
+
2095
2785
  declare global {
2096
2786
  interface TrayApi {
2097
2787
  /** lx.tray.setBadge(value) — the menu-bar / system-tray badge. Null/empty clears it. */