@craft-native/ios 0.0.175 → 0.0.177

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.
package/README.md CHANGED
@@ -74,6 +74,11 @@ views when rows are materialized or recycled.
74
74
  bun add craft-native
75
75
  ```
76
76
 
77
+ ## Native map input contract
78
+
79
+ Map coordinates are finite and range-checked at the native boundary; invalid
80
+ points and pole-crossing regions are ignored on iOS just as on Android.
81
+
77
82
  ## Quick Start
78
83
 
79
84
  ### 1. Initialize Project
@@ -124,7 +129,7 @@ authorization or requiring local notifications to be enabled.
124
129
  Keyboard frame changes update the native controller's additional safe area, so
125
130
  focused inputs and responsive `onLayout` measurements move with the keyboard.
126
131
 
127
- The opt-in `native` renderer hosts compiled `.stx` screens in JavaScriptCore and renders their `View`, `SafeAreaView`, `Text`, `Button`, `Link`, `Checkbox`, `TextInput`, `Picker`, `DatePicker`, `Switch`, `Slider`, `ActivityIndicator`, `Icon`, `Image`, `Video`, `Map`, `ScrollView`, `SectionList`, and recycling `FlatList` nodes as UIKit controls. `Map` uses MapKit for trail polylines and marker taps; its overlays carry flat latitude/longitude `geometry` arrays and can fit the visible region to their bounds. `Icon` uses SF Symbols with the shared symbol/name contract and accessibility metadata. `SectionList` uses the same native scroll host for ordinary `.stx` slot children; grouped section data should use `FlatList` until the section-data protocol is available. `FlatList` uses `UICollectionView` with stable keyed rows, incremental diffs, grids, horizontal and inverted layouts, list chrome, and end-reached pagination. Native views support `onLayout` callbacks with point-based frame measurements that refresh after safe-area or orientation changes. Flex items support `flex`, `flexGrow`, `flexShrink`, and numeric `flexBasis` for main-axis space distribution. Native screens also expose the versioned capability bridge: JSON storage and SQLite survive relaunch, lifecycle and deep-link events are scoped to the top route, and local notifications use `UNUserNotificationCenter`. Enable each capability in `craft.config.json`; disabled calls preserve Craft's `CAPABILITY_DISABLED` rejection code and every request has deterministic timeout/cancellation behavior. The default remains `web`; existing iOS apps are unchanged. `Checkbox` supports controlled `checked`/`value`, indeterminate state, and boolean `onValueChange`; `Picker` renders `<select>`/`<option>` values with `onValueChange` or `onChange`; `DatePicker` emits local date/time values plus ISO and millisecond timestamps; `Switch` supports state-aware `trackColor`, `thumbColor`, and iOS `ios_backgroundColor`; `Slider` supports track/thumb tint props and `step` snapping; `Image` reports load lifecycle events and structured failures; `Video` uses an `AVPlayerViewController` attached to the native screen for URI playback, native controls, and route-safe teardown. The portable component, capability, list, style, image-source, event, accessibility, and reconciliation behavior is specified in the [Native STX guide](../../docs/guides/native-stx.md).
132
+ The opt-in `native` renderer hosts compiled `.stx` screens in JavaScriptCore and renders their `View`, `SafeAreaView`, `Text`, `Button`, `Link`, `Checkbox`, `TextInput`, `Picker`, `DatePicker`, `Switch`, `Slider`, `ActivityIndicator`, `Icon`, `Image`, `ImageBackground`, `Video`, `Map`, `Svg`, `G`, `SvgText`, `ScrollView`, `SectionList`, and recycling `FlatList` nodes as UIKit controls. `Map` uses MapKit for trail polylines and marker taps; its overlays carry flat latitude/longitude `geometry` arrays and can fit the visible region to their bounds. `Svg` is a DOM-free drawing surface; grouped `G` and `SvgText` nodes are painted inside it. `Icon` uses SF Symbols with the shared symbol/name contract and accessibility metadata. `SectionList` uses the same native scroll host for ordinary `.stx` slot children; grouped section data should use `FlatList` until the section-data protocol is available. `FlatList` uses `UICollectionView` with stable keyed rows, incremental diffs, grids, horizontal and inverted layouts, list chrome, and end-reached pagination. Native views support `onLayout` callbacks with point-based frame measurements that refresh after safe-area or orientation changes. Flex items support `flex`, `flexGrow`, `flexShrink`, and numeric `flexBasis` for main-axis space distribution. Native screens also expose the versioned capability bridge: JSON storage and SQLite survive relaunch, lifecycle and deep-link events are scoped to the top route, and local notifications use `UNUserNotificationCenter`. Enable each capability in `craft.config.json`; disabled calls preserve Craft's `CAPABILITY_DISABLED` rejection code and every request has deterministic timeout/cancellation behavior. The default remains `web`; existing iOS apps are unchanged. `Checkbox` supports controlled `checked`/`value`, indeterminate state, and boolean `onValueChange`; `Picker` renders `<select>`/`<option>` values with `onValueChange` or `onChange`; `DatePicker` emits local date/time values plus ISO and millisecond timestamps; `Switch` supports state-aware `trackColor`, `thumbColor`, and iOS `ios_backgroundColor`; `Slider` supports track/thumb tint props and `step` snapping; `Image` reports load lifecycle events and structured failures; `Video` uses an `AVPlayerViewController` attached to the native screen for URI playback, native controls, and route-safe teardown. The portable component, capability, list, style, image-source, event, accessibility, and reconciliation behavior is specified in the [Native STX guide](../../docs/guides/native-stx.md).
128
133
 
129
134
  ```bash
130
135
  craft ios init NativeDemo --renderer native --output ./ios-native
@@ -159,8 +164,9 @@ the [Native STX guide](../../docs/guides/native-stx.md). On macOS,
159
164
  `bun packages/ios/scripts/test-native-render.ts` runs the renderer's unit and
160
165
  simulator UI tests.
161
166
 
162
- The iOS host uses the shared layout ABI when it is available and retains a
163
- local fallback for older runtimes. Both paths implement wrapped-line and grid
167
+ The iOS host uses the shared tree-layout ABI for eligible definite-size,
168
+ non-grid, non-wrapped containers and retains a local fallback for older
169
+ runtimes or unsupported styles. Both paths implement wrapped-line and grid
164
170
  `alignContent`, including `space-evenly`, so a missing optional shared symbol
165
171
  does not change placement semantics.
166
172
  Reverse flex directions also retain wrapped line order on the cross axis while
package/dist/cli.js CHANGED
@@ -515,7 +515,9 @@ function renderFeatureSettings(config) {
515
515
  }
516
516
  function renderPrivacyManifest(config) {
517
517
  const privacy = config.privacy ?? {};
518
- const collected = privacy.collectedDataTypes ?? [];
518
+ const collected = [...privacy.collectedDataTypes ?? []];
519
+ if (config.crashReporting?.key && !collected.some((item) => item.type === "NSPrivacyCollectedDataTypeCrashData"))
520
+ collected.push({ type: "NSPrivacyCollectedDataTypeCrashData", linked: true, tracking: false, purposes: ["NSPrivacyCollectedDataTypePurposeAppFunctionality"] });
519
521
  const accessed = privacy.accessedApiTypes ?? [];
520
522
  const collectedXml = collected.map((item) => ` <dict>
521
523
  <key>NSPrivacyCollectedDataType</key>
@@ -877,6 +879,7 @@ function writeShell(output, config) {
877
879
  cpSync(join3(TEMPLATES_DIR, "CraftNativeFiles.swift"), join3(output, "Sources", "CraftNativeFiles.swift"));
878
880
  cpSync(join3(TEMPLATES_DIR, "CraftWebAudio.swift"), join3(output, "Sources", "CraftWebAudio.swift"));
879
881
  cpSync(join3(TEMPLATES_DIR, "CraftHybrid.swift"), join3(output, "Sources", "CraftHybrid.swift"));
882
+ writeFileSync(join3(output, "Sources", "CraftCrashReporter.swift"), readFileSync(join3(TEMPLATES_DIR, "CraftCrashReporter.swift"), "utf-8").replace(/\{\{CRAFT_VERSION\}\}/g, CRAFT_VERSION));
880
883
  cpSync(join3(TEMPLATES_DIR, "CraftActivityAttributes.swift"), join3(output, "Shared", "CraftActivityAttributes.swift"));
881
884
  for (const [enabled, folder, template, filename] of [
882
885
  [config.enableLiveActivities, "WidgetExtension", "CraftLiveActivityWidget.swift.template", `${name}LiveActivity.swift`],
@@ -951,6 +954,7 @@ async function init(options) {
951
954
  - Shared
952
955
  settings:
953
956
  INFOPLIST_FILE: WidgetExtension/Info.plist
957
+ PRODUCT_NAME: "$(TARGET_NAME)"
954
958
  PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.liveactivity
955
959
  SWIFT_VERSION: "5.0"
956
960
  APPLICATION_EXTENSION_API_ONLY: YES
@@ -966,6 +970,7 @@ async function init(options) {
966
970
  settings:
967
971
  INFOPLIST_FILE: WatchApp/Info.plist
968
972
  CODE_SIGN_ENTITLEMENTS: WatchApp/Watch.entitlements
973
+ PRODUCT_NAME: "$(TARGET_NAME)"
969
974
  PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.watchkitapp
970
975
  SWIFT_VERSION: "5.0"
971
976
  SKIP_INSTALL: YES
@@ -980,6 +985,7 @@ async function init(options) {
980
985
  settings:
981
986
  INFOPLIST_FILE: WatchExtension/Info.plist
982
987
  CODE_SIGN_ENTITLEMENTS: WatchExtension/Watch.entitlements
988
+ PRODUCT_NAME: "$(TARGET_NAME)"
983
989
  PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.watchkitapp.watchkitextension
984
990
  SWIFT_VERSION: "5.0"
985
991
  APPLICATION_EXTENSION_API_ONLY: YES
package/dist/index.d.ts CHANGED
@@ -239,6 +239,25 @@ export interface CraftConfig {
239
239
  * `craft.secureStorage.getSync('auth.token')`. Turns on secure storage.
240
240
  */
241
241
  shareStorage?: Record<string, string>;
242
+ /**
243
+ * Crash and error reporting to bughq (CraftCrashReporter.swift): crashes
244
+ * and hangs (signal handlers, NSException, MetricKit), uncaught JavaScript
245
+ * errors in native screens and the app script, and what the page sends
246
+ * through `craft.crashReporter`. Queued on disk and sent when online.
247
+ */
248
+ crashReporting?: CraftCrashReporting;
249
+ }
250
+ export interface CraftCrashReporting {
251
+ /** The bughq project's public ingest key. Ships in the app; it is not a secret. */
252
+ key: string;
253
+ /** The bughq host. Default https://bughq.org. */
254
+ host?: string;
255
+ /** Default `production`. */
256
+ environment?: string;
257
+ /** The release every event names. Default `<version>+<build>`, e.g. `1.0.0+41`. */
258
+ release?: string;
259
+ /** One session per launch, for crash-free sessions per release. Default on. */
260
+ sessions?: boolean;
242
261
  }
243
262
  export interface CraftTab {
244
263
  /** The tab's root path; comes back to the page in `craftTabSelect`. */
package/dist/index.js CHANGED
@@ -643,7 +643,9 @@ function renderFeatureSettings(config) {
643
643
  }
644
644
  function renderPrivacyManifest(config) {
645
645
  const privacy = config.privacy ?? {};
646
- const collected = privacy.collectedDataTypes ?? [];
646
+ const collected = [...privacy.collectedDataTypes ?? []];
647
+ if (config.crashReporting?.key && !collected.some((item) => item.type === "NSPrivacyCollectedDataTypeCrashData"))
648
+ collected.push({ type: "NSPrivacyCollectedDataTypeCrashData", linked: true, tracking: false, purposes: ["NSPrivacyCollectedDataTypePurposeAppFunctionality"] });
647
649
  const accessed = privacy.accessedApiTypes ?? [];
648
650
  const collectedXml = collected.map((item) => ` <dict>
649
651
  <key>NSPrivacyCollectedDataType</key>
@@ -1009,6 +1011,7 @@ function writeShell(output, config) {
1009
1011
  cpSync(join3(TEMPLATES_DIR, "CraftNativeFiles.swift"), join3(output, "Sources", "CraftNativeFiles.swift"));
1010
1012
  cpSync(join3(TEMPLATES_DIR, "CraftWebAudio.swift"), join3(output, "Sources", "CraftWebAudio.swift"));
1011
1013
  cpSync(join3(TEMPLATES_DIR, "CraftHybrid.swift"), join3(output, "Sources", "CraftHybrid.swift"));
1014
+ writeFileSync2(join3(output, "Sources", "CraftCrashReporter.swift"), readFileSync2(join3(TEMPLATES_DIR, "CraftCrashReporter.swift"), "utf-8").replace(/\{\{CRAFT_VERSION\}\}/g, CRAFT_VERSION));
1012
1015
  cpSync(join3(TEMPLATES_DIR, "CraftActivityAttributes.swift"), join3(output, "Shared", "CraftActivityAttributes.swift"));
1013
1016
  for (const [enabled, folder, template, filename] of [
1014
1017
  [config.enableLiveActivities, "WidgetExtension", "CraftLiveActivityWidget.swift.template", `${name}LiveActivity.swift`],
@@ -1083,6 +1086,7 @@ async function init(options) {
1083
1086
  - Shared
1084
1087
  settings:
1085
1088
  INFOPLIST_FILE: WidgetExtension/Info.plist
1089
+ PRODUCT_NAME: "$(TARGET_NAME)"
1086
1090
  PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.liveactivity
1087
1091
  SWIFT_VERSION: "5.0"
1088
1092
  APPLICATION_EXTENSION_API_ONLY: YES
@@ -1098,6 +1102,7 @@ async function init(options) {
1098
1102
  settings:
1099
1103
  INFOPLIST_FILE: WatchApp/Info.plist
1100
1104
  CODE_SIGN_ENTITLEMENTS: WatchApp/Watch.entitlements
1105
+ PRODUCT_NAME: "$(TARGET_NAME)"
1101
1106
  PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.watchkitapp
1102
1107
  SWIFT_VERSION: "5.0"
1103
1108
  SKIP_INSTALL: YES
@@ -1112,6 +1117,7 @@ async function init(options) {
1112
1117
  settings:
1113
1118
  INFOPLIST_FILE: WatchExtension/Info.plist
1114
1119
  CODE_SIGN_ENTITLEMENTS: WatchExtension/Watch.entitlements
1120
+ PRODUCT_NAME: "$(TARGET_NAME)"
1115
1121
  PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.watchkitapp.watchkitextension
1116
1122
  SWIFT_VERSION: "5.0"
1117
1123
  APPLICATION_EXTENSION_API_ONLY: YES
package/dist/testing.d.ts CHANGED
@@ -24,6 +24,11 @@ export type SimulatorStep = {
24
24
  } | {
25
25
  command: 'swipe';
26
26
  direction: 'up' | 'down' | 'left' | 'right';
27
+ }
28
+ /** The system back-swipe: a drag in from the left edge (or the right). */
29
+ | {
30
+ command: 'edge';
31
+ direction: 'left' | 'right';
27
32
  } | {
28
33
  command: 'tabbar';
29
34
  state: 'shown' | 'hidden';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craft-native/ios",
3
- "version": "0.0.175",
3
+ "version": "0.0.177",
4
4
  "homepage": "https://github.com/craft-native/craft/tree/main/packages/ios#readme",
5
5
  "repository": {
6
6
  "type": "git",
@@ -194,8 +194,10 @@ struct CraftRecoveryBudget {
194
194
 
195
195
  final class CraftAppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
196
196
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
197
- UNUserNotificationCenter.current().delegate = self
198
197
  let config = CraftConfig.bundled()
198
+ // First, so a crash anywhere in the rest of launch is reported too.
199
+ CraftCrashReporter.shared.start(config)
200
+ UNUserNotificationCenter.current().delegate = self
199
201
  // Before launch finishes, which is the only time iOS accepts it.
200
202
  CraftBackgroundWork.shared.registerRefresh(config)
201
203
  // The shell, the app script and an all-native app's services now,
@@ -1297,6 +1299,9 @@ struct CraftConfig: Codable {
1297
1299
  /// The bundle route of the app script: a screen-less script that runs for
1298
1300
  /// the app's whole life and hears the app's events (CraftHybrid.swift).
1299
1301
  var nativeApp: String? = nil
1302
+ /// Crash and error reporting to bughq (CraftCrashReporter.swift). Off
1303
+ /// unless the config names a project key.
1304
+ var crashReporting: CrashReporting? = nil
1300
1305
 
1301
1306
  struct Tab: Codable {
1302
1307
  var id: String
@@ -1311,6 +1316,18 @@ struct CraftConfig: Codable {
1311
1316
  /// The soonest iOS may run it again, in minutes (15 if unset).
1312
1317
  var minimumIntervalMinutes: Double? = nil
1313
1318
  }
1319
+
1320
+ struct CrashReporting: Codable {
1321
+ /// The bughq project's public ingest key.
1322
+ var key: String
1323
+ /// The bughq host; https://bughq.org if unset.
1324
+ var host: String? = nil
1325
+ var environment: String? = nil
1326
+ /// The release events name; `<version>+<build>` if unset.
1327
+ var release: String? = nil
1328
+ /// A session per launch, for crash-free sessions. On unless false.
1329
+ var sessions: Bool? = nil
1330
+ }
1314
1331
  }
1315
1332
 
1316
1333
  extension CraftConfig {
@@ -3458,6 +3475,7 @@ struct CraftWebView: UIViewRepresentable {
3458
3475
  heartRate: \(config.enableHeartRate),
3459
3476
  scheduledWorkouts: \(config.enableScheduledWorkouts),
3460
3477
  speech: true,
3478
+ crashReporter: \(CraftCrashReporter.shared.enabled),
3461
3479
  network: true,
3462
3480
  deviceInfo: true,
3463
3481
  badge: true,
@@ -4451,6 +4469,38 @@ struct CraftWebView: UIViewRepresentable {
4451
4469
  }
4452
4470
  },
4453
4471
 
4472
+ // Crash and error reporting (CraftCrashReporter.swift). The
4473
+ // page's error SDK hands its events here, so they share the
4474
+ // app's on-disk outbox (sent when online), its session and the
4475
+ // device's own context. Each resolves at once: the work is a
4476
+ // file write, and the send happens later.
4477
+ crashReporter: {
4478
+ _post: function(message) {
4479
+ var self = window.craft;
4480
+ var id = 'cb_' + (++self._callbackId);
4481
+ message.callbackId = id;
4482
+ window.webkit.messageHandlers.craft.postMessage(message);
4483
+ return new Promise(function(resolve, reject) {
4484
+ self._callbacks[id] = {resolve: resolve, reject: reject};
4485
+ });
4486
+ },
4487
+ capture: function(event) {
4488
+ return this._post({action: 'crashReporterCapture', event: event || {}});
4489
+ },
4490
+ setUser: function(id) {
4491
+ return this._post({action: 'crashReporterSetUser', id: id == null ? null : String(id)});
4492
+ },
4493
+ addBreadcrumb: function(breadcrumb) {
4494
+ return this._post({action: 'crashReporterAddBreadcrumb', breadcrumb: breadcrumb || {}});
4495
+ },
4496
+ flush: function() {
4497
+ return this._post({action: 'crashReporterFlush'});
4498
+ },
4499
+ context: function() {
4500
+ return this._post({action: 'crashReporterContext'});
4501
+ }
4502
+ },
4503
+
4454
4504
  // Orientation Lock
4455
4505
  lockOrientation: function(orientation) {
4456
4506
  var self = this;