craft-native 0.0.237 → 0.0.241

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 (40) hide show
  1. package/README.md +71 -0
  2. package/dist/android/src/emulator.d.ts +23 -0
  3. package/dist/android/src/index.d.ts +24 -2
  4. package/dist/android/src/index.js +362 -103
  5. package/dist/android/src/native-map.d.ts +7 -0
  6. package/dist/android/templates/CraftBridge.kt.template +67 -55
  7. package/dist/android/templates/CraftNative.kt.template +27 -0
  8. package/dist/android/templates/CraftNativeCapabilities.kt.template +36 -5
  9. package/dist/android/templates/CraftNativeMapGoogle.kt.template +440 -0
  10. package/dist/android/templates/CraftNativeMapLibre.kt.template +438 -0
  11. package/dist/android/templates/CraftNativeMessagingService.kt.template +3 -1
  12. package/dist/android/templates/CraftNativeNotificationReceiver.kt.template +2 -1
  13. package/dist/android/templates/CraftNativeServices.kt.template +45 -35
  14. package/dist/android/templates/CraftNativeShell.kt.template +103 -20
  15. package/dist/android/templates/LocationRecordingService.kt.template +255 -47
  16. package/dist/android/templates/MainActivityNative.kt.template +7 -428
  17. package/dist/android/templates/build.gradle.kts.app.template +1 -1
  18. package/dist/android/templates/build.gradle.kts.project.template +1 -1
  19. package/dist/android-adb.d.ts +2 -0
  20. package/dist/api/index.d.ts +1 -1
  21. package/dist/api/mobile.d.ts +49 -14
  22. package/dist/browser/storage.d.ts +3 -0
  23. package/dist/browser.js +163 -51
  24. package/dist/cli.js +1 -1
  25. package/dist/index.cjs +126 -12
  26. package/dist/index.js +126 -12
  27. package/dist/ios/src/index.d.ts +5 -0
  28. package/dist/ios/src/index.js +17 -1
  29. package/dist/ios/src/testing.d.ts +16 -2
  30. package/dist/ios/templates/CraftApp.swift +418 -93
  31. package/dist/ios/templates/CraftHeartRate.swift +141 -44
  32. package/dist/ios/templates/CraftNativeActions.swift +46 -48
  33. package/dist/ios/templates/CraftNativeScreen.swift +82 -17
  34. package/dist/ios/templates/CraftPersistentStorage.swift +99 -0
  35. package/dist/ios/templates/CraftRecordingArchive.swift +85 -0
  36. package/dist/ios/templates/testing/DriverTests.swift.template +45 -1
  37. package/dist/macos/templates/native_layout.zig +41 -5
  38. package/dist/mobile-testing.js +30 -4
  39. package/dist/mobile.js +169 -17
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -575,3 +575,74 @@ MIT © [Chris Breuer](https://github.com/chrisbbreuer)
575
575
  - [Examples](../examples-ts)
576
576
  - [Benchmarks](../../benchmarks)
577
577
  - [Issues](https://github.com/home-lang/craft/issues)
578
+
579
+ ### Recording ownership and recovery
580
+
581
+ Native STX screens and the mobile page use the same native recorder. Before
582
+ starting an account-owned GPS or heart-rate session, require
583
+ `location.supportsRecordingOwnership()` or `heartRate.supportsRecordingOwnership()`.
584
+ The check requires both the current SDK adapter and a capable native host.
585
+ Unsupported hosts reject guarded operations with
586
+ `RECORDING_OWNERSHIP_UNSUPPORTED`; they never discard the guard and retry.
587
+
588
+ For account-owned recordings, also require `supportsRecordingIntentIdentity()`
589
+ on the chosen service. Allocate distinct GPS and heart-rate IDs with
590
+ `await crypto.randomUUID()` from `craft-native/mobile`, capture the account,
591
+ workout and clock generation, and persist those IDs **before** calling
592
+ `startRecording({ recordingId: id })`. Recheck the captured owner after each
593
+ await. The native recorder starts that exact ID and refuses reuse of a current,
594
+ completed or acknowledged ID. The SDK checks the response ID; older hosts and
595
+ adapters reject with `RECORDING_INTENT_IDENTITY_UNSUPPORTED`. A lost start
596
+ response or a full disk during a later checkpoint can then recover the exact
597
+ precommitted ID without adopting an unknown recording. The asynchronous Crypto
598
+ service uses the platform's secure UUID generator; an unsupported native host
599
+ rejects with `CRYPTO_UNSUPPORTED` without falling back to JavaScript entropy.
600
+
601
+ Pass `{ expectedId: id }` to pause, resume, stop, read and get-state calls. Heart-rate reads also accept
602
+ `since`. A supplied empty, whitespace or non-string ID rejects with
603
+ `INVALID_ARGUMENT`. Starting while another session is active rejects with
604
+ `RECORDING_ALREADY_ACTIVE`, preserving its files.
605
+
606
+ Completed recordings survive relaunch and subsequent sessions. Guarded reads,
607
+ get-state and repeated stops can recover a completed A while B is recording;
608
+ pausing or resuming A rejects with `RECORDING_CONFLICT`. The native recorder
609
+ archives A before reusing its current track. A failed archive or unreadable
610
+ known track refuses replacement rather than truncating the only copy.
611
+
612
+ After stopping, await durable storage of the result under the captured account
613
+ owner, then call `acknowledgeRecording({ expectedId: id })`. ACK is idempotent,
614
+ refuses active recordings and never removes another owner's current session.
615
+ It writes a persistent tombstone before cleanup, so interrupted deletion cannot
616
+ resurrect acknowledged data. Subsequent guarded reads return
617
+ `RECORDING_NOT_FOUND`. A failed account checkpoint must leave the native copy
618
+ unacknowledged for recovery. Stopping alone does not acknowledge a recording.
619
+
620
+ For account drafts, use `storage.supportsCompareAndSet()` and
621
+ `storage.compareAndSet(key, expectedValue, nextValue)`. This operation replaces
622
+ an **existing** matching JSON value under the native storage lock and waits for
623
+ its durable write. Object key order does not matter; array order and JSON types
624
+ do. A missing or changed key returns `false` without creating or changing it.
625
+ A storage failure rejects, preserving the previous value. Hosts without the
626
+ explicit `storageCompareAndSet` capability reject with
627
+ `STORAGE_COMPARE_AND_SET_UNSUPPORTED`; there is no read-then-set fallback.
628
+
629
+ For a missing draft, require `storage.supportsSetIfAbsent()` and atomically
630
+ register it with `storage.setIfAbsent(key, intent)`. It returns `true` only after
631
+ durable creation; any existing value, including JSON `null`, returns `false`.
632
+ An unsupported host rejects with `STORAGE_SET_IF_ABSENT_UNSUPPORTED`. Use CAS
633
+ for an existing draft; neither registration path uses ordinary `set` after a
634
+ read. The browser driver uses Web Locks across tabs for all Storage operations
635
+ and account cleanup. A queued operation whose captured account changes rejects
636
+ with `STORAGE_SCOPE_CHANGED`, so a previous account cannot write into the new
637
+ account's namespace or mistake that namespace for a deleted draft.
638
+
639
+ When keeping a completed result, read the latest value through asynchronous
640
+ `storage.get`, validate its captured account/session/recording identity, merge
641
+ its latest feedback, then compare-and-set that exact value. Retry concurrent
642
+ feedback changes with at most two fresh reads; never overwrite a newer session
643
+ or recreate a deleted draft. Only a successful durable checkpoint permits the
644
+ normal ACK. A failed write or exhausted conflict leaves the native archive.
645
+ An explicit discard may ACK the captured old ID only after a fresh committed
646
+ native read proves that its previously registered intent was deleted or
647
+ replaced. A cached missing draft or an unsuccessful initial registration is
648
+ not evidence of deletion.
@@ -0,0 +1,23 @@
1
+ export interface AndroidEmulatorOptions {
2
+ sdkRoot: string;
3
+ sdkmanager: string;
4
+ avdmanager: string;
5
+ image: string;
6
+ acceleration: 'hardware' | 'software';
7
+ artifactsDirectory: string;
8
+ temporaryDirectory?: string;
9
+ port?: number;
10
+ acceptLicenses?: boolean;
11
+ /** Defaults to 10 minutes; hardware is capped there, software cold provisioning at 20 minutes. */
12
+ bootTimeoutMs?: number;
13
+ env?: Record<string, string | undefined>;
14
+ }
15
+ export interface AndroidEmulatorSession {
16
+ serial: string;
17
+ adb: string;
18
+ env: Record<string, string | undefined>;
19
+ /** Stops only this session's process and removes only its disposable AVD. */
20
+ dispose: () => Promise<void>;
21
+ }
22
+ /** Provision and own a real Android emulator on a macOS or Linux SDK host. */
23
+ export declare function createAndroidEmulator(options: AndroidEmulatorOptions): Promise<AndroidEmulatorSession>;
@@ -1,9 +1,15 @@
1
1
  /**
2
2
  * Craft Android Builder
3
3
  *
4
- * Generates native Android apps from web content using WebView.
4
+ * Generates Android apps with native Craft components and optional WebView content.
5
5
  */
6
+ import { type AndroidMapProvider } from './native-map.js';
7
+ export type { AndroidMapProvider } from './native-map.js';
8
+ /** Providers this published builder can generate as real Android map views. */
9
+ export declare const nativeMapProviders: readonly ['google', 'maplibre'];
6
10
  export { runKotlinFixture } from './testing.js';
11
+ export { createAndroidEmulator } from './emulator.js';
12
+ export type { AndroidEmulatorOptions, AndroidEmulatorSession } from './emulator.js';
7
13
  /** Package custom fonts and their licenses; removed fonts cannot linger in the app. */
8
14
  export declare function syncNativeFonts(fonts: CraftAndroidConfig['nativeFonts'], output: string): string[];
9
15
  /** Oldest Android API level supported by the generated Kotlin runtime. */
@@ -69,8 +75,20 @@ export interface CraftAndroidConfig {
69
75
  }>;
70
76
  trustedOrigins?: string[];
71
77
  appIconPath?: string;
78
+ /**
79
+ * The small icon in the status bar and on every notification the app
80
+ * shows, push or local: an Android vector drawable (`.xml`) or a `.png`,
81
+ * white on transparent, since Android draws only its shape. Without one,
82
+ * a white Craft mark. Never the launcher icon, which shows as a white
83
+ * square.
84
+ */
85
+ notificationIconPath?: string;
86
+ /** The `#RRGGBB` Android tints the notification icon and app name with. The system's own colour without it. */
87
+ notificationColor?: string;
72
88
  googleServicesFile?: string;
73
- /** Google Maps Android SDK key used by native `Map` screens. */
89
+ /** Native map renderer. MapLibre needs no SDK key; Google remains the default for compatibility. */
90
+ mapProvider?: AndroidMapProvider;
91
+ /** Google Maps Android SDK key, used only by the Google native map host. */
74
92
  mapsApiKey?: string;
75
93
  devServerURL?: string;
76
94
  hasBundledFallback?: boolean;
@@ -88,6 +106,10 @@ export interface CraftAndroidConfig {
88
106
  id: string;
89
107
  title: string;
90
108
  symbol?: string;
109
+ /** Native selected-tab icon overrides, also supported by navigation.setOptions({ tabItem }). */
110
+ selectedSymbol?: string;
111
+ selectedIconTint?: string;
112
+ selectedIconPulse?: boolean;
91
113
  }>;
92
114
  nativeApp?: string;
93
115
  /** Wake the app script every so often (`craftBackgroundRefresh`), through WorkManager. */