craft-native 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.
Files changed (34) hide show
  1. package/dist/android/src/index.d.ts +19 -0
  2. package/dist/android/src/index.js +19 -2
  3. package/dist/android/templates/AndroidManifest.xml.template +1 -0
  4. package/dist/android/templates/CraftBridge.kt.template +51 -0
  5. package/dist/android/templates/CraftCrashReporter.kt.template +544 -0
  6. package/dist/android/templates/CraftNative.kt.template +17 -0
  7. package/dist/android/templates/CraftNativeFlatList.kt.template +20 -9
  8. package/dist/android/templates/CraftNativeMutation.kt.template +4 -1
  9. package/dist/android/templates/CraftNativeServices.kt.template +31 -1
  10. package/dist/android/templates/MainActivityNative.kt.template +986 -304
  11. package/dist/android/templates/build.gradle.kts.app.template +4 -2
  12. package/dist/api/mobile.d.ts +31 -0
  13. package/dist/cli.js +1 -1
  14. package/dist/components/index.d.ts +203 -2
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.d.cts +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/ios/src/index.d.ts +19 -0
  20. package/dist/ios/src/index.js +7 -1
  21. package/dist/ios/src/testing.d.ts +5 -0
  22. package/dist/ios/templates/CraftApp.swift +51 -1
  23. package/dist/ios/templates/CraftCrashReporter.swift +1010 -0
  24. package/dist/ios/templates/CraftHybrid.swift +10 -0
  25. package/dist/ios/templates/CraftNativeActions.swift +49 -1
  26. package/dist/ios/templates/CraftNativeFlatList.swift +30 -28
  27. package/dist/ios/templates/CraftNativeMutation.swift +1 -1
  28. package/dist/ios/templates/CraftNativeScreen.swift +559 -86
  29. package/dist/ios/templates/project.yml.template +1 -0
  30. package/dist/ios/templates/testing/DriverTests.swift.template +8 -0
  31. package/dist/macos/templates/CraftNativeMutation.swift +1 -1
  32. package/dist/macos/templates/native_layout.zig +389 -17
  33. package/dist/mobile.js +66 -0
  34. package/package.json +1 -1
@@ -59,6 +59,9 @@ android {
59
59
 
60
60
  buildFeatures {
61
61
  viewBinding = true
62
+ // BuildConfig.DEBUG: debug-only code (the crash reporter's test
63
+ // crash) is a constant false in release, so R8 removes it.
64
+ buildConfig = true
62
65
  }
63
66
 
64
67
  // Android's default AAPT ignore pattern drops underscore-prefixed asset
@@ -94,8 +97,7 @@ dependencies {
94
97
  // Google Play Location Services (for geolocation)
95
98
  implementation("com.google.android.gms:play-services-location:21.1.0")
96
99
 
97
- // Native STX Map overlays and trail routes
98
- implementation("com.google.android.gms:play-services-maps:18.2.0")
100
+ {{GOOGLE_MAPS_DEPENDENCY}}
99
101
 
100
102
  // Google Play In-App Review
101
103
  implementation("com.google.android.play:review:2.0.1")
@@ -762,6 +762,36 @@ export declare const speech: {
762
762
  /** Stop speaking at once. Every pending `speak()` resolves `false`. */
763
763
  stop(): Promise<void>;
764
764
  };
765
+ export interface CrashReporterContext {
766
+ enabled: boolean;
767
+ platform?: 'ios' | 'android';
768
+ release?: string;
769
+ environment?: string;
770
+ session?: {
771
+ id: string;
772
+ startedAt: string;
773
+ };
774
+ contexts?: Record<string, Record<string, unknown>>;
775
+ [key: string]: unknown;
776
+ }
777
+ export declare const crashReporter: {
778
+ /** True inside an app whose config turns crash reporting on. */
779
+ isAvailable(): boolean;
780
+ /** Queue an event in the bughq ingest shape (`type`, `message`, `stack`, ...). */
781
+ capture(event: Record<string, unknown>): Promise<boolean>;
782
+ /** The app's own user id (never an email), or null on sign-out. */
783
+ setUser(id: string | number | null): Promise<boolean>;
784
+ addBreadcrumb(breadcrumb: {
785
+ type?: string;
786
+ category?: string;
787
+ level?: string;
788
+ message?: string;
789
+ data?: Record<string, unknown>;
790
+ }): Promise<boolean>;
791
+ flush(): Promise<boolean>;
792
+ /** The release, session and device context the native reporter stamps on events. */
793
+ context(): Promise<CrashReporterContext | null>;
794
+ };
765
795
  export type MusicAuthorization = 'authorized' | 'denied' | 'restricted' | 'notDetermined' | 'unavailable';
766
796
  export type MusicPlaybackState = 'stopped' | 'playing' | 'paused' | 'interrupted' | 'seekingForward' | 'seekingBackward' | 'unknown';
767
797
  export interface NowPlaying {
@@ -1640,6 +1670,7 @@ declare const mobile: {
1640
1670
  keepAwake: typeof keepAwake;
1641
1671
  brightness: typeof brightness;
1642
1672
  speech: typeof speech;
1673
+ crashReporter: typeof crashReporter;
1643
1674
  music: typeof music;
1644
1675
  heartRate: typeof heartRate;
1645
1676
  scheduledWorkouts: typeof scheduledWorkouts;
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.175";
3306
+ var version = "0.0.177";
3307
3307
 
3308
3308
  // bin/cli.ts
3309
3309
  var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
@@ -314,6 +314,16 @@ export interface ViewProps extends BaseProps {
314
314
  /** Pointer events behavior */
315
315
  pointerEvents?: 'auto' | 'none' | 'box-none' | 'box-only';
316
316
  }
317
+ /** Safe-area container props used by native iOS and Android screens. */
318
+ export interface SafeAreaViewProps extends ViewProps {
319
+ /** Insets to apply; omitted means all edges. */
320
+ edges?: Array<'top' | 'right' | 'bottom' | 'left'>;
321
+ }
322
+ /** Keyboard-aware container props used by native screens. */
323
+ export interface KeyboardAvoidingViewProps extends ViewProps {
324
+ behavior?: 'height' | 'position' | 'padding';
325
+ keyboardVerticalOffset?: number;
326
+ }
317
327
  /**
318
328
  * Text component props.
319
329
  */
@@ -331,6 +341,29 @@ export interface TextProps extends BaseProps {
331
341
  /** Long press handler */
332
342
  onLongPress?: () => void;
333
343
  }
344
+ /** Native button/link action props. */
345
+ export interface ButtonProps extends BaseProps {
346
+ title?: string;
347
+ onPress?: () => void;
348
+ disabled?: boolean;
349
+ color?: string;
350
+ }
351
+ /** Link is a button with link semantics in the native renderer. */
352
+ export interface LinkProps extends ButtonProps {
353
+ href?: string;
354
+ to?: string;
355
+ }
356
+ export interface TouchableOpacityProps extends ViewProps {
357
+ activeOpacity?: number;
358
+ disabled?: boolean;
359
+ }
360
+ export interface TouchableHighlightProps extends ViewProps {
361
+ underlayColor?: string;
362
+ activeOpacity?: number;
363
+ disabled?: boolean;
364
+ onShowUnderlay?: () => void;
365
+ onHideUnderlay?: () => void;
366
+ }
334
367
  /**
335
368
  * Image source type.
336
369
  */
@@ -367,6 +400,84 @@ export interface ImageProps extends BaseProps {
367
400
  /** Alt text for accessibility */
368
401
  alt?: string;
369
402
  }
403
+ export interface IconProps extends BaseProps {
404
+ name?: string;
405
+ symbol?: string;
406
+ color?: string;
407
+ }
408
+ export interface VideoProps extends BaseProps {
409
+ source?: string | {
410
+ uri: string;
411
+ };
412
+ src?: string;
413
+ controls?: boolean;
414
+ autoplay?: boolean;
415
+ autoPlay?: boolean;
416
+ loop?: boolean;
417
+ muted?: boolean;
418
+ paused?: boolean;
419
+ resizeMode?: 'contain' | 'cover' | 'stretch';
420
+ onLoad?: (event: {
421
+ duration: number;
422
+ }) => void;
423
+ onPlay?: () => void;
424
+ onPause?: () => void;
425
+ onEnd?: () => void;
426
+ onProgress?: (event: {
427
+ currentTime: number;
428
+ duration: number;
429
+ }) => void;
430
+ onError?: (event: {
431
+ message: string;
432
+ }) => void;
433
+ }
434
+ export interface MapRegion {
435
+ center: {
436
+ latitude: number;
437
+ longitude: number;
438
+ };
439
+ span: {
440
+ latitudeDelta: number;
441
+ longitudeDelta: number;
442
+ };
443
+ }
444
+ export interface MapProps extends BaseProps {
445
+ region?: MapRegion;
446
+ fitToBounds?: boolean;
447
+ showsUserLocation?: boolean;
448
+ scrollEnabled?: boolean;
449
+ zoomEnabled?: boolean;
450
+ rotateEnabled?: boolean;
451
+ mapType?: 'standard' | 'satellite' | 'hybrid' | 'terrain';
452
+ onMarkerPress?: (event: {
453
+ nativeEvent: {
454
+ id: string;
455
+ };
456
+ }) => void;
457
+ }
458
+ export interface MapPolylineProps extends BaseProps {
459
+ geometry: number[];
460
+ strokeColor?: string;
461
+ strokeWidth?: number;
462
+ }
463
+ export interface MapMarkerProps extends BaseProps {
464
+ geometry: number[];
465
+ id: string;
466
+ title?: string;
467
+ onPress?: (event: {
468
+ nativeEvent: {
469
+ id: string;
470
+ };
471
+ }) => void;
472
+ onMarkerPress?: (event: {
473
+ nativeEvent: {
474
+ id: string;
475
+ };
476
+ }) => void;
477
+ }
478
+ export interface ImageBackgroundProps extends ImageProps {
479
+ imageStyle?: ImageStyle | ImageStyle[];
480
+ }
370
481
  /**
371
482
  * ScrollView component props.
372
483
  */
@@ -454,9 +565,9 @@ export interface TextInputProps extends BaseProps {
454
565
  /** Placeholder text color */
455
566
  placeholderTextColor?: string;
456
567
  /** Keyboard type */
457
- keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'decimal-pad' | 'url';
568
+ keyboardType?: KeyboardType;
458
569
  /** Return key type */
459
- returnKeyType?: 'done' | 'go' | 'next' | 'search' | 'send';
570
+ returnKeyType?: ReturnKeyType;
460
571
  /** Auto capitalize */
461
572
  autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
462
573
  /** Auto correct */
@@ -486,6 +597,71 @@ export interface TextInputProps extends BaseProps {
486
597
  /** End editing handler */
487
598
  onEndEditing?: () => void;
488
599
  }
600
+ export type KeyboardType = 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'number-pad' | 'decimal-pad' | 'url' | 'web-search' | 'visible-password' | 'ascii-capable' | 'numbers-and-punctuation' | 'name-phone-pad' | 'twitter';
601
+ export type ReturnKeyType = 'done' | 'go' | 'next' | 'search' | 'send' | 'join' | 'route' | 'continue' | 'emergency-call' | 'default';
602
+ export interface SwitchProps extends BaseProps {
603
+ value?: boolean;
604
+ onValueChange?: (value: boolean) => void;
605
+ disabled?: boolean;
606
+ trackColor?: {
607
+ false?: string;
608
+ true?: string;
609
+ };
610
+ thumbColor?: string;
611
+ ios_backgroundColor?: string;
612
+ }
613
+ export interface CheckboxProps extends BaseProps {
614
+ value?: boolean;
615
+ checked?: boolean;
616
+ indeterminate?: boolean;
617
+ label?: string;
618
+ onValueChange?: (value: boolean) => void;
619
+ disabled?: boolean;
620
+ color?: string;
621
+ }
622
+ export interface SliderProps extends BaseProps {
623
+ value?: number;
624
+ minimumValue?: number;
625
+ maximumValue?: number;
626
+ step?: number;
627
+ onValueChange?: (value: number) => void;
628
+ onSlidingComplete?: (value: number) => void;
629
+ disabled?: boolean;
630
+ minimumTrackTintColor?: string;
631
+ maximumTrackTintColor?: string;
632
+ thumbTintColor?: string;
633
+ }
634
+ export interface PickerOption {
635
+ label: string;
636
+ value: string | number;
637
+ }
638
+ export interface PickerProps extends BaseProps {
639
+ items: PickerOption[];
640
+ selectedValue?: string | number;
641
+ onValueChange?: (value: string | number, index: number) => void;
642
+ disabled?: boolean;
643
+ }
644
+ export interface DatePickerChange {
645
+ value: string;
646
+ iso: string;
647
+ timestamp: number;
648
+ }
649
+ export interface DatePickerProps extends BaseProps {
650
+ mode?: 'date' | 'time' | 'datetime' | 'countdown';
651
+ value?: string | number;
652
+ minimumDate?: string | number;
653
+ maximumDate?: string | number;
654
+ minuteInterval?: number;
655
+ display?: 'compact' | 'inline' | 'wheels';
656
+ onValueChange?: (event: DatePickerChange) => void;
657
+ disabled?: boolean;
658
+ }
659
+ export interface ActivityIndicatorProps extends BaseProps {
660
+ color?: string;
661
+ animating?: boolean;
662
+ hidesWhenStopped?: boolean;
663
+ size?: 'small' | 'large' | number;
664
+ }
489
665
  /**
490
666
  * FlatList component props.
491
667
  */
@@ -530,6 +706,31 @@ export interface FlatListProps<T> extends ScrollViewProps {
530
706
  /** Refreshing state */
531
707
  refreshing?: boolean;
532
708
  }
709
+ export interface SectionListSection<T> {
710
+ data: T[];
711
+ key?: string;
712
+ title?: string;
713
+ }
714
+ export interface SectionListProps<T> extends ScrollViewProps {
715
+ sections: SectionListSection<T>[];
716
+ renderItem: (info: {
717
+ item: T;
718
+ index: number;
719
+ section: SectionListSection<T>;
720
+ }) => any;
721
+ renderSectionHeader?: (info: {
722
+ section: SectionListSection<T>;
723
+ }) => any;
724
+ keyExtractor?: (item: T, index: number) => string;
725
+ }
726
+ export interface ModalProps extends BaseProps {
727
+ visible?: boolean;
728
+ transparent?: boolean;
729
+ animationType?: 'none' | 'fade' | 'slide';
730
+ onShow?: () => void;
731
+ onDismiss?: () => void;
732
+ onRequestClose?: () => void;
733
+ }
533
734
  /**
534
735
  * Layout event.
535
736
  */
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.175";
313
+ var version = "0.0.177";
314
314
 
315
315
  // src/sdk-version.ts
316
316
  var SDK_VERSION = version;
package/dist/index.d.cts CHANGED
@@ -30,7 +30,7 @@ export * from './utils/index.js';
30
30
  export * from './api/index.js';
31
31
  export * as components from './components/index.js';
32
32
  export { createTahoeSidebar, createArcSidebar, createOrbStackSidebar, sidebarItem, sidebarSection, sidebarSeparator, tahoeStyle, arcStyle, orbstackStyle, createSplitView, SplitViewInstance, createFileBrowser, FileBrowserInstance, createOutlineView, OutlineViewInstance, createTableView, TableViewInstance, showQuickLook, hideQuickLook, canQuickLook, showColorPicker, showFontPicker, showDatePicker, createProgress, ProgressInstance, setToolbar, updateToolbarItem, setToolbarVisible, setTouchBar, updateTouchBarItem, StyleSheet, Animated, } from './components/index.js';
33
- export type { ContextMenuItem, SidebarStyle, SidebarPosition, SidebarHeaderConfig, SidebarFooterConfig, SidebarEventType, SidebarEventMap, SidebarEventHandler, ComponentProps, ComponentInstance, SplitViewOrientation, SplitViewDividerStyle, SplitViewConfig, FileBrowserConfig, FileBrowserSelection, OutlineItem, OutlineViewConfig, TableRow, TableViewConfig, QuickLookConfig, ColorPickerConfig, FontPickerConfig, FontResult, DatePickerConfig, ProgressConfig, ToolbarItem, ToolbarConfig, TouchBarConfig, FlexStyle, LayoutStyle, SpacingStyle, BorderStyle, ColorStyle, ShadowStyle, TransformStyle, ViewStyle, TextStyleProps, TextStyle, ImageStyleProps, ImageStyle, BaseProps, ViewProps, TextProps, ImageSource, ImageProps, ScrollViewProps, PressableProps, TextInputProps, FlatListProps, LayoutEvent, ScrollEvent, } from './components/index.js';
33
+ export type { ContextMenuItem, SidebarStyle, SidebarPosition, SidebarHeaderConfig, SidebarFooterConfig, SidebarEventType, SidebarEventMap, SidebarEventHandler, ComponentProps, ComponentInstance, SplitViewOrientation, SplitViewDividerStyle, SplitViewConfig, FileBrowserConfig, FileBrowserSelection, OutlineItem, OutlineViewConfig, TableRow, TableViewConfig, QuickLookConfig, ColorPickerConfig, FontPickerConfig, FontResult, DatePickerConfig, ProgressConfig, ToolbarItem, ToolbarConfig, TouchBarConfig, FlexStyle, LayoutStyle, SpacingStyle, BorderStyle, ColorStyle, ShadowStyle, TransformStyle, ViewStyle, TextStyleProps, TextStyle, ImageStyleProps, ImageStyle, BaseProps, ViewProps, SafeAreaViewProps, KeyboardAvoidingViewProps, TextProps, ButtonProps, LinkProps, TouchableOpacityProps, TouchableHighlightProps, ImageSource, ImageProps, ImageBackgroundProps, IconProps, VideoProps, MapRegion, MapProps, MapPolylineProps, MapMarkerProps, ScrollViewProps, PressableProps, TextInputProps, KeyboardType, ReturnKeyType, SwitchProps, CheckboxProps, SliderProps, PickerOption, PickerProps, DatePickerChange, DatePickerProps, ActivityIndicatorProps, FlatListProps, SectionListSection, SectionListProps, ModalProps, LayoutEvent, ScrollEvent, } from './components/index.js';
34
34
  export * from './styles/headwind.js';
35
35
  export * from './styles/index.js';
36
36
  export * from './optimizations/index.js';
package/dist/index.d.ts CHANGED
@@ -30,7 +30,7 @@ export * from './utils/index.js';
30
30
  export * from './api/index.js';
31
31
  export * as components from './components/index.js';
32
32
  export { createTahoeSidebar, createArcSidebar, createOrbStackSidebar, sidebarItem, sidebarSection, sidebarSeparator, tahoeStyle, arcStyle, orbstackStyle, createSplitView, SplitViewInstance, createFileBrowser, FileBrowserInstance, createOutlineView, OutlineViewInstance, createTableView, TableViewInstance, showQuickLook, hideQuickLook, canQuickLook, showColorPicker, showFontPicker, showDatePicker, createProgress, ProgressInstance, setToolbar, updateToolbarItem, setToolbarVisible, setTouchBar, updateTouchBarItem, StyleSheet, Animated, } from './components/index.js';
33
- export type { ContextMenuItem, SidebarStyle, SidebarPosition, SidebarHeaderConfig, SidebarFooterConfig, SidebarEventType, SidebarEventMap, SidebarEventHandler, ComponentProps, ComponentInstance, SplitViewOrientation, SplitViewDividerStyle, SplitViewConfig, FileBrowserConfig, FileBrowserSelection, OutlineItem, OutlineViewConfig, TableRow, TableViewConfig, QuickLookConfig, ColorPickerConfig, FontPickerConfig, FontResult, DatePickerConfig, ProgressConfig, ToolbarItem, ToolbarConfig, TouchBarConfig, FlexStyle, LayoutStyle, SpacingStyle, BorderStyle, ColorStyle, ShadowStyle, TransformStyle, ViewStyle, TextStyleProps, TextStyle, ImageStyleProps, ImageStyle, BaseProps, ViewProps, TextProps, ImageSource, ImageProps, ScrollViewProps, PressableProps, TextInputProps, FlatListProps, LayoutEvent, ScrollEvent, } from './components/index.js';
33
+ export type { ContextMenuItem, SidebarStyle, SidebarPosition, SidebarHeaderConfig, SidebarFooterConfig, SidebarEventType, SidebarEventMap, SidebarEventHandler, ComponentProps, ComponentInstance, SplitViewOrientation, SplitViewDividerStyle, SplitViewConfig, FileBrowserConfig, FileBrowserSelection, OutlineItem, OutlineViewConfig, TableRow, TableViewConfig, QuickLookConfig, ColorPickerConfig, FontPickerConfig, FontResult, DatePickerConfig, ProgressConfig, ToolbarItem, ToolbarConfig, TouchBarConfig, FlexStyle, LayoutStyle, SpacingStyle, BorderStyle, ColorStyle, ShadowStyle, TransformStyle, ViewStyle, TextStyleProps, TextStyle, ImageStyleProps, ImageStyle, BaseProps, ViewProps, SafeAreaViewProps, KeyboardAvoidingViewProps, TextProps, ButtonProps, LinkProps, TouchableOpacityProps, TouchableHighlightProps, ImageSource, ImageProps, ImageBackgroundProps, IconProps, VideoProps, MapRegion, MapProps, MapPolylineProps, MapMarkerProps, ScrollViewProps, PressableProps, TextInputProps, KeyboardType, ReturnKeyType, SwitchProps, CheckboxProps, SliderProps, PickerOption, PickerProps, DatePickerChange, DatePickerProps, ActivityIndicatorProps, FlatListProps, SectionListSection, SectionListProps, ModalProps, LayoutEvent, ScrollEvent, } from './components/index.js';
34
34
  export * from './styles/headwind.js';
35
35
  export * from './styles/index.js';
36
36
  export * from './optimizations/index.js';
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.175";
61
+ var version = "0.0.177";
62
62
 
63
63
  // src/sdk-version.ts
64
64
  var SDK_VERSION = version;
@@ -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`. */
@@ -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
@@ -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';
@@ -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;