craft-native 0.0.123 → 0.0.125

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.
@@ -59,6 +59,7 @@ dependencies {
59
59
  implementation("androidx.appcompat:appcompat:1.6.1")
60
60
  implementation("androidx.fragment:fragment-ktx:1.6.2")
61
61
  implementation("androidx.recyclerview:recyclerview:1.3.2")
62
+ {{REFRESH_LAYOUT_DEPENDENCY}}
62
63
  implementation("androidx.webkit:webkit:1.12.1")
63
64
  {{JAVASCRIPT_ENGINE_DEPENDENCY}}
64
65
  implementation("com.google.android.material:material:1.11.0")
@@ -203,6 +203,14 @@ export declare const haptics: {
203
203
  * Use when a selection changes (e.g., picker value change).
204
204
  */
205
205
  selection(): Promise<void>;
206
+ /**
207
+ * Warm the haptic engine ahead of a haptic the page knows is coming (the
208
+ * start of a drag, a long press about to complete), so it plays on the frame
209
+ * it is asked for. `impact`, `notification` or `selection` for one kind, an
210
+ * impact style or notification type for that one, nothing for all. iOS keeps
211
+ * a prepared engine awake for a few seconds. A no-op elsewhere.
212
+ */
213
+ prepare(kind?: HapticStyle | HapticNotificationType | 'impact' | 'notification' | 'selection'): Promise<void>;
206
214
  /**
207
215
  * Vibrate with a custom pattern.
208
216
  *
@@ -608,6 +616,18 @@ export declare const lifecycle: {
608
616
  * @returns Function to remove listener
609
617
  */
610
618
  onStateChange(callback: (state: AppState) => void): () => void;
619
+ /**
620
+ * The app came back to the foreground (`craftResume`), with how long it was
621
+ * away. A page can refresh what may have gone stale. iOS shell only.
622
+ */
623
+ onResume(callback: (event: {
624
+ backgroundedMs: number;
625
+ }) => void): () => void;
626
+ /**
627
+ * iOS is short of memory (`craftMemoryWarning`): drop caches, or the page's
628
+ * content process may be ended. iOS shell only.
629
+ */
630
+ onMemoryWarning(callback: () => void): () => void;
611
631
  };
612
632
  /**
613
633
  * Notification options.
@@ -792,12 +812,229 @@ export declare const tabBar: {
792
812
  * The launch splash, which the shell holds over the page until the page says
793
813
  * it is ready, so the app goes from its launch screen to content with no blank
794
814
  * page in between. Call `hide()` once the first screen has painted. The shell
795
- * hides it anyway a moment after the page finishes loading, and after ten
796
- * seconds at most.
815
+ * hides it anyway once the page's first contentful paint is on screen, and
816
+ * after `splashMaxSeconds` (three by default) at most.
797
817
  */
798
818
  export declare const splash: {
799
819
  hide(): boolean;
800
820
  };
821
+ /** Letters, digits, `.`, `_` and `-`, not starting with a dot: the names the shell accepts. */
822
+ export declare function isSnapshotName(name: string): boolean;
823
+ /**
824
+ * Data the page keeps for its native screens. A native screen reads a
825
+ * snapshot synchronously before its first frame (`craft.snapshots.get(name)`),
826
+ * so it opens on the last data the page saw instead of an empty skeleton.
827
+ * Write one whenever the page has fresh data a native screen shows.
828
+ *
829
+ * Each is `Application Support/craft-snapshots/<name>.json` in the app.
830
+ * Outside a hybrid app every call is a no-op answering false.
831
+ */
832
+ export declare const snapshots: {
833
+ isAvailable(): boolean;
834
+ /** Keep `value` (anything JSON can hold) as `name`. Answers whether it was handed to the shell. */
835
+ set(name: string, value: unknown): boolean;
836
+ remove(name: string): boolean;
837
+ /** Every snapshot, as on signing out. */
838
+ clear(): boolean;
839
+ };
840
+ /**
841
+ * Navigation between the page and the app's native screens.
842
+ *
843
+ * The shell already hands the stx router's navigations to native paths to
844
+ * the native screens, and Back at the entry a native screen opened goes back
845
+ * to it, so most pages need none of this. It is for code that navigates
846
+ * without the router.
847
+ */
848
+ export declare const hybrid: {
849
+ /** Whether this page runs inside a hybrid app. */
850
+ isActive(): boolean;
851
+ /** The native screen `path` opens, or null when it is the page's. */
852
+ nativeScreenFor(path: string): {
853
+ path: string;
854
+ screen: string;
855
+ params: Record<string, string>;
856
+ } | null;
857
+ /** Open `path` as the shell would: a tab, a native screen, or the page pushed. */
858
+ open(path: string): boolean;
859
+ /** Back to the native screen under this page, when there is one. */
860
+ back(): boolean;
861
+ };
862
+ /** A rect in the viewport's CSS pixels. */
863
+ export interface NativeRect {
864
+ x: number;
865
+ y: number;
866
+ width: number;
867
+ height: number;
868
+ }
869
+ /** Where a popover, sheet or menu points: a rect, or an element to point at. */
870
+ export type NativeAnchor = NativeRect | {
871
+ getBoundingClientRect(): {
872
+ left: number;
873
+ top: number;
874
+ width: number;
875
+ height: number;
876
+ };
877
+ };
878
+ export interface DialogAlertOptions {
879
+ title: string;
880
+ message?: string;
881
+ /** The button. The system's own "OK" if unset. */
882
+ okLabel?: string;
883
+ }
884
+ export interface DialogConfirmOptions {
885
+ title: string;
886
+ message?: string;
887
+ /** The confirming button. The system's own "OK" if unset. */
888
+ confirmLabel?: string;
889
+ /** The system's own "Cancel" if unset. */
890
+ cancelLabel?: string;
891
+ /** Paints the confirming button red, as iOS does for a delete. */
892
+ destructive?: boolean;
893
+ }
894
+ export interface ActionSheetAction {
895
+ id: string;
896
+ title: string;
897
+ /** `cancel` answers `null` whatever its id. */
898
+ style?: 'default' | 'destructive' | 'cancel';
899
+ }
900
+ export interface ActionSheetOptions {
901
+ title?: string;
902
+ message?: string;
903
+ actions: ActionSheetAction[];
904
+ /** Where the sheet points on iPad (and, from iOS 26, on iPhone). */
905
+ anchor?: NativeAnchor;
906
+ }
907
+ export interface ContextMenuItem {
908
+ id: string;
909
+ title: string;
910
+ /** An SF Symbol name, such as `trash`. */
911
+ symbol?: string;
912
+ destructive?: boolean;
913
+ disabled?: boolean;
914
+ }
915
+ export interface ContextMenuOptions {
916
+ items: ContextMenuItem[];
917
+ anchor: NativeAnchor;
918
+ title?: string;
919
+ }
920
+ export interface BrowserOpenOptions {
921
+ /** `safari`: SFSafariViewController in the app. `auth`: the system's web sign-in sheet. */
922
+ mode?: 'safari' | 'auth';
923
+ /** For `auth`: the URL scheme the provider redirects back to. */
924
+ callbackScheme?: string;
925
+ }
926
+ export interface BrowserOpenResult {
927
+ /** For `auth`: the callback URL the provider redirected to. */
928
+ url?: string;
929
+ cancelled: boolean;
930
+ error?: string;
931
+ }
932
+ export type SymbolWeight = 'ultraLight' | 'thin' | 'light' | 'regular' | 'medium' | 'semibold' | 'bold' | 'heavy' | 'black';
933
+ export interface SymbolImageOptions {
934
+ /** Default 17. */
935
+ pointSize?: number;
936
+ weight?: SymbolWeight;
937
+ /** A CSS colour. The label colour if unset. */
938
+ color?: string;
939
+ scale?: 'small' | 'medium' | 'large';
940
+ }
941
+ export type StatusBarStyle = 'default' | 'light' | 'dark';
942
+ /** The reading and motion settings the page should match. */
943
+ export interface NativeAppearance {
944
+ /** The Dynamic Type size, as UIKit names it: `large` is the default. */
945
+ contentSizeCategory: string;
946
+ /** Body text's scale from its default size; also `--craft-font-scale` on the root element. */
947
+ fontScale: number;
948
+ reduceMotion: boolean;
949
+ reduceTransparency: boolean;
950
+ colorScheme: 'light' | 'dark';
951
+ }
952
+ /** A `craftSilentPush` or `craftBackgroundRefresh`. */
953
+ export interface BackgroundWorkEvent {
954
+ /** Pass to `background.complete` to answer this one. */
955
+ id?: string;
956
+ /** The push's payload, for a silent push. */
957
+ payload?: Record<string, unknown>;
958
+ }
959
+ /**
960
+ * The system's alert, confirmation and action sheet. Off the iOS shell every
961
+ * call resolves without showing anything (`confirm` with false, `actionSheet`
962
+ * with null), so check `isAvailable()` to draw a web fallback.
963
+ */
964
+ export declare const dialog: {
965
+ isAvailable(): boolean;
966
+ alert(options: DialogAlertOptions | string): Promise<void>;
967
+ confirm(options: DialogConfirmOptions | string): Promise<boolean>;
968
+ /** The chosen action's id, or null when dismissed. */
969
+ actionSheet(options: ActionSheetOptions): Promise<string | null>;
970
+ };
971
+ /**
972
+ * The system's context menu, anchored at a rect or element: the list with SF
973
+ * Symbols a long press shows (iOS 17.4 and later; the edit menu, then an
974
+ * action sheet, before). The chosen item's id, or null when dismissed.
975
+ */
976
+ export declare const contextMenu: {
977
+ isAvailable(): boolean;
978
+ show(options: ContextMenuOptions): Promise<string | null>;
979
+ };
980
+ /**
981
+ * A page elsewhere without leaving the app: SFSafariViewController, or for
982
+ * `mode: 'auth'` the system's web sign-in sheet, which answers with the
983
+ * callback URL. On the web, `safari` opens a new tab and `auth` cannot catch
984
+ * the callback, so it answers cancelled.
985
+ */
986
+ export declare const browser: {
987
+ isAvailable(): boolean;
988
+ open(url: string, options?: BrowserOpenOptions): Promise<BrowserOpenResult>;
989
+ };
990
+ /** An SF Symbol rasterised to a PNG data URL, or null where there are none. */
991
+ export declare const symbols: {
992
+ image(name: string, options?: SymbolImageOptions): Promise<string | null>;
993
+ };
994
+ /** The status bar's text: light, dark, or the system's choice for the app's appearance. */
995
+ export declare const statusBar: {
996
+ setStyle(style: StatusBarStyle): Promise<boolean>;
997
+ };
998
+ /** The shell's chrome around the page. */
999
+ export declare const chrome: {
1000
+ /** What shows past the page's edges when it rubber-bands (iOS 15). */
1001
+ setUnderPageColor(color: string): Promise<boolean>;
1002
+ /** WebKit's bar above the keyboard (previous, next, Done). Hidden by default. */
1003
+ setKeyboardAccessory(visible: boolean): Promise<boolean>;
1004
+ };
1005
+ /**
1006
+ * The system's pull-to-refresh on the page's scroll view. A pull fires
1007
+ * `onRefresh`; call `end()` once the new content is in. It is removed when the
1008
+ * document changes, so a page enables it for itself.
1009
+ */
1010
+ export declare const refresh: {
1011
+ isAvailable(): boolean;
1012
+ enable(options?: {
1013
+ tintColor?: string;
1014
+ }): Promise<boolean>;
1015
+ disable(): Promise<boolean>;
1016
+ end(): Promise<boolean>;
1017
+ onRefresh(callback: () => void): () => void;
1018
+ };
1019
+ /**
1020
+ * Background time on iOS: a silent push (`content-available`) and the
1021
+ * scheduled refresh (`backgroundRefresh.enabled` in the Craft config). Each
1022
+ * waits for `complete(ok)` from the page, or 25 seconds.
1023
+ */
1024
+ export declare const background: {
1025
+ complete(ok?: boolean, id?: string): Promise<boolean>;
1026
+ onRefresh(callback: (event: BackgroundWorkEvent) => void): () => void;
1027
+ onSilentPush(callback: (event: BackgroundWorkEvent) => void): () => void;
1028
+ };
1029
+ /**
1030
+ * Text size, Reduce Motion, Reduce Transparency and Light or Dark. In the iOS
1031
+ * shell from the native settings (Dynamic Type included, which a web page
1032
+ * cannot otherwise see); elsewhere from the media queries a browser has.
1033
+ */
1034
+ export declare const appearance: {
1035
+ get(): NativeAppearance | null;
1036
+ onChange(callback: (appearance: NativeAppearance) => void): () => void;
1037
+ };
801
1038
  export declare const appReview: {
802
1039
  request(): Promise<boolean>;
803
1040
  };
@@ -957,5 +1194,16 @@ declare const mobile: {
957
1194
  watchConnectivity: typeof watchConnectivity;
958
1195
  tabBar: typeof tabBar;
959
1196
  splash: typeof splash;
1197
+ snapshots: typeof snapshots;
1198
+ hybrid: typeof hybrid;
1199
+ dialog: typeof dialog;
1200
+ contextMenu: typeof contextMenu;
1201
+ browser: typeof browser;
1202
+ symbols: typeof symbols;
1203
+ statusBar: typeof statusBar;
1204
+ chrome: typeof chrome;
1205
+ refresh: typeof refresh;
1206
+ background: typeof background;
1207
+ appearance: typeof appearance;
960
1208
  };
961
1209
  export default mobile;
package/dist/cli.js CHANGED
@@ -3303,7 +3303,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
3303
3303
  `);
3304
3304
  }
3305
3305
  // package.json
3306
- var version = "0.0.123";
3306
+ var version = "0.0.125";
3307
3307
 
3308
3308
  // bin/cli.ts
3309
3309
  var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
@@ -3611,7 +3611,7 @@ cli.command("ios init <name>", "Initialize a new iOS project").option("--bundle-
3611
3611
  config: options?.renderer ? { renderer: options.renderer } : undefined
3612
3612
  });
3613
3613
  });
3614
- cli.command("ios build", "Build iOS project").option("--html-path <path>", "Path to HTML file").option("--native-bundle <path>", "JavaScript bundle compiled by stx-native").option("-d, --dev-server <url>", "Development server URL").option("-o, --output <dir>", "iOS project directory", { default: "./ios" }).option("-w, --watch", "Watch for file changes and rebuild").example("craft ios build").example("craft ios build --html-path ./dist/index.html").example("craft ios build --native-bundle ./screen.js").example("craft ios build --dev-server http://localhost:3456").example("craft ios build --watch").action(async (options) => {
3614
+ cli.command("ios build", "Build iOS project").option("--html-path <path>", "Path to HTML file").option("--native-bundle <path>", "JavaScript bundle from `stx native compile`").option("-d, --dev-server <url>", "Development server URL").option("-o, --output <dir>", "iOS project directory", { default: "./ios" }).option("-w, --watch", "Watch for file changes and rebuild").example("craft ios build").example("craft ios build --html-path ./dist/index.html").example("craft ios build --native-bundle ./screen.js").example("craft ios build --dev-server http://localhost:3456").example("craft ios build --watch").action(async (options) => {
3615
3615
  const iosModule = await import(iosModuleURL);
3616
3616
  const doBuild = async () => {
3617
3617
  await iosModule.build({
@@ -3661,7 +3661,7 @@ cli.command("android init <name>", "Initialize a new Android project").option("-
3661
3661
  config: options?.renderer ? { renderer: options.renderer } : undefined
3662
3662
  });
3663
3663
  });
3664
- cli.command("android build", "Build Android project").option("--html-path <path>", "Path to HTML file").option("--native-bundle <path>", "JavaScript bundle compiled by stx-native").option("-d, --dev-server <url>", "Development server URL").option("-o, --output <dir>", "Android project directory", { default: "./android" }).option("--release", "Build release APK").option("-w, --watch", "Watch for file changes and rebuild").example("craft android build").example("craft android build --release").example("craft android build --native-bundle ./screen.js").example("craft android build --watch").action(async (options) => {
3664
+ cli.command("android build", "Build Android project").option("--html-path <path>", "Path to HTML file").option("--native-bundle <path>", "JavaScript bundle from `stx native compile`").option("-d, --dev-server <url>", "Development server URL").option("-o, --output <dir>", "Android project directory", { default: "./android" }).option("--release", "Build release APK").option("-w, --watch", "Watch for file changes and rebuild").example("craft android build").example("craft android build --release").example("craft android build --native-bundle ./screen.js").example("craft android build --watch").action(async (options) => {
3665
3665
  const androidModule = await import(androidModuleURL);
3666
3666
  const doBuild = async () => {
3667
3667
  await androidModule.build({
package/dist/index.cjs CHANGED
@@ -310,7 +310,7 @@ function craftBinaryNotFoundMessage(triedPath) {
310
310
  `);
311
311
  }
312
312
  // package.json
313
- var version = "0.0.123";
313
+ var version = "0.0.125";
314
314
 
315
315
  // src/sdk-version.ts
316
316
  var SDK_VERSION = version;
@@ -4885,6 +4885,14 @@ var haptics = {
4885
4885
  navigator.vibrate(5);
4886
4886
  }
4887
4887
  },
4888
+ async prepare(kind) {
4889
+ const craft = getCraftMobile();
4890
+ if (typeof window !== "undefined" && craft?.haptics?.prepare) {
4891
+ try {
4892
+ await craft.haptics.prepare(kind);
4893
+ } catch {}
4894
+ }
4895
+ },
4888
4896
  async vibrate(pattern) {
4889
4897
  const craft = getCraftMobile();
4890
4898
  if (typeof window !== "undefined" && craft?.haptics) {
@@ -5256,6 +5264,12 @@ var lifecycle = {
5256
5264
  };
5257
5265
  document.addEventListener("visibilitychange", handler);
5258
5266
  return () => document.removeEventListener("visibilitychange", handler);
5267
+ },
5268
+ onResume(callback) {
5269
+ return onCraftEvent("craftResume", (detail) => callback({ backgroundedMs: Number(detail.backgroundedMs) || 0 }));
5270
+ },
5271
+ onMemoryWarning(callback) {
5272
+ return onCraftEvent("craftMemoryWarning", () => callback());
5259
5273
  }
5260
5274
  };
5261
5275
  function bridgeNotification(options, now = Date.now()) {
@@ -5310,6 +5324,13 @@ var notifications = {
5310
5324
  }
5311
5325
  };
5312
5326
  var speakingOnTheWeb = new Set;
5327
+ function onCraftEvent(eventName, callback) {
5328
+ if (typeof globalThis === "undefined")
5329
+ return () => {};
5330
+ const listener = (event) => callback(event.detail ?? {});
5331
+ globalThis.addEventListener(eventName, listener);
5332
+ return () => globalThis.removeEventListener(eventName, listener);
5333
+ }
5313
5334
  function detectPlatform2() {
5314
5335
  if (typeof window === "undefined")
5315
5336
  return "linux";
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ function craftBinaryNotFoundMessage(triedPath) {
58
58
  `);
59
59
  }
60
60
  // package.json
61
- var version = "0.0.123";
61
+ var version = "0.0.125";
62
62
 
63
63
  // src/sdk-version.ts
64
64
  var SDK_VERSION = version;
@@ -4645,6 +4645,14 @@ var haptics = {
4645
4645
  navigator.vibrate(5);
4646
4646
  }
4647
4647
  },
4648
+ async prepare(kind) {
4649
+ const craft = getCraftMobile();
4650
+ if (typeof window !== "undefined" && craft?.haptics?.prepare) {
4651
+ try {
4652
+ await craft.haptics.prepare(kind);
4653
+ } catch {}
4654
+ }
4655
+ },
4648
4656
  async vibrate(pattern) {
4649
4657
  const craft = getCraftMobile();
4650
4658
  if (typeof window !== "undefined" && craft?.haptics) {
@@ -5016,6 +5024,12 @@ var lifecycle = {
5016
5024
  };
5017
5025
  document.addEventListener("visibilitychange", handler);
5018
5026
  return () => document.removeEventListener("visibilitychange", handler);
5027
+ },
5028
+ onResume(callback) {
5029
+ return onCraftEvent("craftResume", (detail) => callback({ backgroundedMs: Number(detail.backgroundedMs) || 0 }));
5030
+ },
5031
+ onMemoryWarning(callback) {
5032
+ return onCraftEvent("craftMemoryWarning", () => callback());
5019
5033
  }
5020
5034
  };
5021
5035
  function bridgeNotification(options, now = Date.now()) {
@@ -5070,6 +5084,13 @@ var notifications = {
5070
5084
  }
5071
5085
  };
5072
5086
  var speakingOnTheWeb = new Set;
5087
+ function onCraftEvent(eventName, callback) {
5088
+ if (typeof globalThis === "undefined")
5089
+ return () => {};
5090
+ const listener = (event) => callback(event.detail ?? {});
5091
+ globalThis.addEventListener(eventName, listener);
5092
+ return () => globalThis.removeEventListener(eventName, listener);
5093
+ }
5073
5094
  function detectPlatform2() {
5074
5095
  if (typeof window === "undefined")
5075
5096
  return "linux";
@@ -78,11 +78,30 @@ export interface CraftConfig {
78
78
  /**
79
79
  * Also refuse to load anything but the app-bound domains in the web view
80
80
  * (WKWebViewConfiguration.limitsNavigationsToAppBoundDomains). Off by
81
- * default: links elsewhere already open in Safari, and embedded players and
82
- * maps in iframes keep loading.
81
+ * default: links elsewhere already open in Safari. WebKit applies the limit
82
+ * to the main frame only, so embedded players and maps in cross-origin
83
+ * iframes (YouTube, Vimeo) keep loading with it on (checked in the iOS 26
84
+ * simulator); WebKit only declines to inject user scripts into them, which
85
+ * the bridge never does.
83
86
  */
84
87
  limitNavigationsToAppBoundDomains?: boolean;
88
+ /**
89
+ * Universal links and shared web credentials
90
+ * (`com.apple.developer.associated-domains`). A bare host such as
91
+ * `hq.training` is written as `applinks:hq.training`; an entry with its own
92
+ * service (`webcredentials:`, `applinks:…?mode=developer`) is kept as given.
93
+ * A universal link reaches the page the way a custom-scheme link does, as
94
+ * `craftDeepLink`.
95
+ */
85
96
  associatedDomains?: string[];
97
+ /**
98
+ * iOS's scheduled background refresh (BGAppRefreshTask). When enabled the
99
+ * page gets `craftBackgroundRefresh` and answers with
100
+ * `craft.background.complete(ok)`. The identifier defaults to
101
+ * `<bundleId>.refresh` and is declared in BGTaskSchedulerPermittedIdentifiers,
102
+ * with the `fetch` background mode.
103
+ */
104
+ backgroundRefresh?: CraftBackgroundRefresh;
86
105
  appGroups?: string[];
87
106
  appIconPath?: string;
88
107
  /**
@@ -103,8 +122,83 @@ export interface CraftConfig {
103
122
  */
104
123
  usesNonExemptEncryption?: boolean;
105
124
  privacy?: CraftPrivacyManifest;
125
+ /** Press-and-hold link previews, as in Safari. Default false. */
126
+ allowsLinkPreview?: boolean;
127
+ /**
128
+ * WebKit's bar above the keyboard for a form field (previous, next, Done),
129
+ * which no native app's keyboard has. Default false: hidden. A page can
130
+ * change it with `craft.chrome.setKeyboardAccessory()`.
131
+ */
132
+ keyboardAccessory?: boolean;
133
+ /** Pinch and double-tap zoom off, and the page's viewport pinned at scale 1. Default true. */
134
+ disableZoom?: boolean;
135
+ /**
136
+ * The longest the launch splash stays up, in seconds, when the page neither
137
+ * hides it (`splash.hide()`) nor paints. Default 3.
138
+ */
139
+ splashMaxSeconds?: number;
140
+ /**
141
+ * How long the app's own page may take to answer, in seconds, before the
142
+ * load counts as unreachable and the bundled page stands in. Default 10.
143
+ */
144
+ requestTimeoutSeconds?: number;
106
145
  orientations?: Array<'portrait' | 'landscape-left' | 'landscape-right' | 'portrait-upside-down'>;
107
146
  deviceFamilies?: Array<'iphone' | 'ipad'>;
147
+ /**
148
+ * Hybrid: app paths drawn as native stx screens instead of in the
149
+ * web view, each to the screen in the native bundle that draws it, e.g.
150
+ * `{ "/m": "Today", "/m/calendar": "Calendar" }`. A segment may be a
151
+ * parameter (`/m/workout/:id`) and the last one `*`. Needs `renderer: "web"`
152
+ * and `nativeBundle`; every other path stays in the web view, and a path
153
+ * whose screen is missing or throws falls back to it.
154
+ */
155
+ nativeScreens?: Record<string, string>;
156
+ /** Hybrid: the bundle `stx native compile --format bundle` wrote, copied to `dist/native-screen.js`. */
157
+ nativeBundle?: string;
158
+ /**
159
+ * The native tab bar, shown from the first frame, before the page loads.
160
+ * The page's own `tabBar.set` replaces it later. Each `id` is the tab's
161
+ * root path, as the page's tab links name it.
162
+ */
163
+ tabs?: CraftTab[];
164
+ /**
165
+ * Hybrid: web-storage keys the page writes (localStorage or
166
+ * sessionStorage) that native screens need, mirrored into the Keychain
167
+ * under the name given, e.g. `{ "auth_token": "auth.token" }` for
168
+ * `craft.secureStorage.getSync('auth.token')`. Turns on secure storage.
169
+ */
170
+ shareStorage?: Record<string, string>;
171
+ }
172
+ export interface CraftTab {
173
+ /** The tab's root path; comes back to the page in `craftTabSelect`. */
174
+ id: string;
175
+ title: string;
176
+ /** An SF Symbol name. */
177
+ symbol?: string;
178
+ }
179
+ /** `/a//b/?q#h` → `/a/b`, the way the shell and the page compare paths. Null for a relative path. */
180
+ export declare function normalizeNativePath(raw: string): string | null;
181
+ export interface NativePathMatch {
182
+ path: string;
183
+ screen: string;
184
+ params: Record<string, string>;
185
+ }
186
+ /**
187
+ * The native screen for a path, by the rules the shell (`CraftHybridRoutes`)
188
+ * and the page script apply: static segments win over `:params`, which win
189
+ * over a trailing `*`; query items become params under the path's own.
190
+ */
191
+ export declare function matchNativePath(nativeScreens: Record<string, string> | undefined, raw: string): NativePathMatch | null;
192
+ /** Whether a config mixes native screens into its web page. */
193
+ export declare function isHybrid(config: Pick<CraftConfig, 'renderer' | 'nativeScreens'>): boolean;
194
+ /** What is wrong with a config's hybrid and tab settings, one sentence each. */
195
+ export declare function hybridConfigProblems(config: Pick<CraftConfig, 'renderer' | 'nativeScreens' | 'tabs' | 'shareStorage'>): string[];
196
+ export interface CraftBackgroundRefresh {
197
+ enabled: boolean;
198
+ /** The BGTaskScheduler identifier. Default `<bundleId>.refresh`. */
199
+ identifier?: string;
200
+ /** The soonest iOS may run it again, in minutes. Default 15. */
201
+ minimumIntervalMinutes?: number;
108
202
  }
109
203
  export interface CraftPrivacyDataType {
110
204
  type: string;
@@ -145,7 +239,7 @@ export interface InitOptions {
145
239
  }
146
240
  export interface BuildOptions {
147
241
  htmlPath?: string;
148
- /** JavaScript bundle emitted by `stx-native compile Screen.stx --format bundle`. */
242
+ /** JavaScript bundle emitted by `stx native compile Screen.stx --format bundle`. */
149
243
  nativeBundlePath?: string;
150
244
  devServer?: string;
151
245
  output: string;
@@ -185,6 +279,16 @@ export declare function renderOrientations(config: CraftConfig): string;
185
279
  export declare function renderAppBoundDomains(config: CraftConfig): string;
186
280
  export declare function renderUrlTypes(config: CraftConfig): string;
187
281
  export declare function renderBackgroundModes(config: CraftConfig): string;
282
+ /** The background refresh task's identifier, or null when it is off. */
283
+ export declare function backgroundRefreshIdentifier(config: Pick<CraftConfig, 'backgroundRefresh' | 'bundleId'>): string | null;
284
+ /** BGTaskSchedulerPermittedIdentifiers: iOS runs no task it does not list. */
285
+ export declare function renderBackgroundTaskIdentifiers(config: Pick<CraftConfig, 'backgroundRefresh' | 'bundleId'>): string;
286
+ /**
287
+ * The associated-domains entitlement's entries. Each needs a service prefix,
288
+ * and a bare host (the easy thing to write) used to be signed as it was and
289
+ * then matched nothing, so universal links silently never opened the app.
290
+ */
291
+ export declare function associatedDomainEntries(domains: string[] | undefined): string[];
188
292
  export declare function renderEntitlements(config: CraftConfig): string;
189
293
  export declare function renderWatchEntitlements(config: CraftConfig): string;
190
294
  /**
@@ -219,6 +323,24 @@ export declare function pngHasAlpha(bytes: Uint8Array): boolean;
219
323
  export declare const LAUNCH_LOGO = "LaunchLogo";
220
324
  /** The launch screen: the background colour, and the splash logo on it when there is one. */
221
325
  export declare function renderLaunchScreen(config: Pick<CraftConfig, 'splashImagePath'>): string;
326
+ /** Near-black or near-white, whichever reads on `background` (WCAG relative luminance). */
327
+ export declare function readableTextOn(background: string | undefined): string;
328
+ /**
329
+ * The page the app shows when its own page cannot be reached: the generated
330
+ * `dist/index.html` until the app's web assets replace it.
331
+ *
332
+ * An app whose page has a service worker normally opens from it offline, so
333
+ * this only shows on a first launch without one, or when the remote fails
334
+ * hard. It used to be a placeholder that said "Built with Craft iOS" in white
335
+ * on whatever the background was (invisible on a light one) with a bridge
336
+ * status line under it: debug output, in front of the person using the app.
337
+ *
338
+ * Now it says what happened in the app's own colours, light and dark, sized
339
+ * with Dynamic Type, and Retry asks the shell to load the app's page again.
340
+ * The shell also goes back to the app's page by itself when the connection
341
+ * returns, so the page does not retry on `online` as well.
342
+ */
343
+ export declare function renderOfflinePage(config: Pick<CraftConfig, 'appName' | 'backgroundColor' | 'backgroundColorDark'>): string;
222
344
  /** Replace the bundled web application atomically so removed assets cannot linger. */
223
345
  export declare function syncWebAssets(source: string, output: string): void;
224
346
  /**