snapback4-expo 0.4.9 → 0.4.11

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
@@ -59,8 +59,16 @@ runner, commands and artifact contract are in `snapback4 guide witness`
59
59
  “Proving it on an iOS simulator”. No Expo Go or Metro process supplies a
60
60
  Release witness.
61
61
 
62
- Own-build preparation (`witness ios --build … --smoke`) needs the updated native
63
- witness helper in this source revision. Its synchronous native observation hashes
62
+ At every milestone, run `npm run export:ios` for Hermes; on a Mac with Xcode,
63
+ `npm run native:smoke` (about 3 minutes warm). The Expo starters include both scripts.
64
+ The smoke builds and installs your Release app, chooses its own simulator and free
65
+ port, and keeps receipts in `artifacts/native-smoke/<timestamp>`. It proves local
66
+ store acquisition, cold offline reopen and reconnect. Pending write replay, media
67
+ and application assertions need the deeper scenario in `snapback4 guide witness`.
68
+ An empty starter proves lifecycle only; export alone proves no native execution.
69
+
70
+ Own-build preparation (`witness ios --build . --smoke`) uses the witness helper
71
+ in matching current `snapback4` and `snapback4-expo` packages. Its synchronous native observation hashes
64
72
  the executable and the file selected by React Native SourceCode.scriptURL.
65
73
  Selection must resolve to the verified installed main.jsbundle; unknown, remote
66
74
  or alternate URLs fail, as do runtime __DEV__ true/unknown and launcher/updates
@@ -72,13 +80,13 @@ update selected at runtime. The JS smoke helper consumes compiler query facts
72
80
  from the driver, bound to the generation actually served. It checks every
73
81
  runs/held fact against the kept backend before querying. Stale facts fail;
74
82
  device-answerable loading fails, while server-only offline
75
- loading or E_OFFLINE is recorded as expected unavailability. Server-only reads
83
+ E_OFFLINE is recorded as expected unavailability. Server-only reads
76
84
  must answer online in prime and restart. Conditional native-module queries
77
85
  require an application witness; generic smoke refuses that unsupported shape. Client semantics are unchanged.
78
86
 
79
- After JS/assets edits, repeat `witness ios --build . --smoke` with the same
80
- dedicated simulator and a fresh artifacts directory. Warm Xcode intermediates
87
+ After JS/assets edits, repeat `npm run native:smoke`; simulator selection and
88
+ fresh artifact directories are automatic. Warm Xcode intermediates
81
89
  are reused; each run builds the current app and seals a new receipt. Repack
82
- is withdrawn. Five complete warm build-plus-smoke runs on the shared M5 Pro mini
83
- measured 159.841 s median / 197.562 s nearest-rank p95, all passing; cold native preparation was about six minutes, with smoke and setup
84
- additional. See `guide witness` for pins, scope and exact invocation.
90
+ is withdrawn. NS round 0 cohort (2026-09-30), fresh starter: **221.7 s cold**, **83.5 s warm median (3 runs)**; social **186.2 s**.
91
+ Shared Apartment M5 Pro mini, 18 cores/64 GiB, Xcode/iOS 27; one-minute load: cold 3.61–61.41, warm 3.21–7.22, social 14.62–47.52.
92
+ Tools/dependencies installed first. Guidance, not a promise. See `guide witness` for scope.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snapback4-expo",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "type": "module",
5
5
  "description": "Snapback 4's native module for Expo: the device in Rust (the partition in its own SQLite, the stream applied, the outbox kept, queries and predictions answered) behind one JSON call, and the opener `snapback4/native` takes.",
6
6
  "main": "src/index.ts",
@@ -8,9 +8,11 @@
8
8
  "exports": {
9
9
  ".": { "types": "./src/index.ts", "default": "./src/index.ts" },
10
10
  "./witness": { "types": "./src/witness.ts", "default": "./src/witness.ts" },
11
+ "./plugin": "./plugin.cjs",
11
12
  "./package.json": "./package.json"
12
13
  },
13
14
  "files": [
15
+ "plugin.cjs",
14
16
  "src",
15
17
  "ios",
16
18
  "expo-module.config.json",
package/plugin.cjs ADDED
@@ -0,0 +1,61 @@
1
+ // @ref LLP 3000.030#13 — the pinned Expo starter must launch on iOS 27 without a manual migration.
2
+ const { withAppDelegate, withInfoPlist } = require('expo/config-plugins');
3
+ const marker = '// Snapback4 starter scene lifecycle';
4
+ const legacy = `#if os(iOS) || os(tvOS)
5
+ window = UIWindow(frame: UIScreen.main.bounds)
6
+ factory.startReactNative(
7
+ withModuleName: "main",
8
+ in: window,
9
+ launchOptions: launchOptions)
10
+ #endif`;
11
+ const scene = `
12
+ ${marker}
13
+ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
14
+ var window: UIWindow?
15
+ func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16
+ guard let scene = scene as? UIWindowScene,
17
+ let app = UIApplication.shared.delegate as? AppDelegate,
18
+ let factory = app.reactNativeFactory else { return }
19
+ let window = UIWindow(windowScene: scene)
20
+ self.window = window
21
+ app.window = window
22
+ factory.startReactNative(withModuleName: "main", in: window, launchOptions: nil)
23
+ for context in connectionOptions.urlContexts {
24
+ _ = app.application(UIApplication.shared, open: context.url, options: [:])
25
+ }
26
+ for activity in connectionOptions.userActivities {
27
+ _ = app.application(UIApplication.shared, continue: activity, restorationHandler: { _ in })
28
+ }
29
+ }
30
+ func scene(_ scene: UIScene, openURLContexts contexts: Set<UIOpenURLContext>) {
31
+ guard let app = UIApplication.shared.delegate as? AppDelegate else { return }
32
+ for context in contexts { _ = app.application(UIApplication.shared, open: context.url, options: [:]) }
33
+ }
34
+ func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
35
+ guard let app = UIApplication.shared.delegate as? AppDelegate else { return }
36
+ _ = app.application(UIApplication.shared, continue: userActivity, restorationHandler: { _ in })
37
+ }
38
+ }
39
+ `;
40
+ module.exports = function withStarterScene(config) {
41
+ config = withAppDelegate(config, config => {
42
+ const mod = config.modResults;
43
+ if (mod.contents.includes(marker)) return config;
44
+ if (mod.language !== 'swift' || !mod.contents.includes(legacy) || mod.contents.includes('class SceneDelegate')) {
45
+ throw new Error('snapback4-expo/plugin requires the Expo 57 Swift starter; migrate custom UIScene lifecycle manually and remove this plugin');
46
+ }
47
+ mod.contents = mod.contents.replace(legacy, '') + scene;
48
+ return config;
49
+ });
50
+ return withInfoPlist(config, config => {
51
+ const manifest = { UIApplicationSupportsMultipleScenes: false, UISceneConfigurations: {
52
+ UIWindowSceneSessionRoleApplication: [{ UISceneConfigurationName: 'Default Configuration', UISceneDelegateClassName: '$(PRODUCT_MODULE_NAME).SceneDelegate' }],
53
+ } };
54
+ const existing = config.modResults.UIApplicationSceneManifest;
55
+ if (existing && JSON.stringify(existing) !== JSON.stringify(manifest)) {
56
+ throw new Error('snapback4-expo/plugin refuses a custom UIApplicationSceneManifest; keep its own lifecycle and remove this plugin');
57
+ }
58
+ config.modResults.UIApplicationSceneManifest = manifest;
59
+ return config;
60
+ });
61
+ };
package/src/witness.ts CHANGED
@@ -13,3 +13,11 @@ export function witness(): Witness | null {
13
13
  }
14
14
  return instance;
15
15
  }
16
+
17
+ /** The starter may use normal guest sign-in during prime; never on ordinary launches. */
18
+ export function smokeSegment(): "prime" | "offline" | "restart" | null {
19
+ const container = requireOptionalNativeModule<WitnessContainer>("Snapback4");
20
+ if (!container?.witnessLaunch) return null;
21
+ const config = JSON.parse(container.witnessLaunch);
22
+ return config.smoke === true && ["prime", "offline", "restart"].includes(config.segment) ? config.segment : null;
23
+ }