@calo-design/cli 0.9.5 → 0.9.7

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 (2) hide show
  1. package/bin/mirror-push.js +34 -21
  2. package/package.json +1 -1
@@ -444,7 +444,7 @@ function switchToChannel(channel: string): void {
444
444
  await Updates.fetchUpdateAsync();
445
445
  // Defer the reload off the awaited chain (JSI teardown race — mirror.ts).
446
446
  setTimeout(() => { void Updates.reloadAsync().catch(() => { switching = false; }); }, 300);
447
- })().catch(() => { switching = false; });
447
+ })().catch((e) => { console.warn("[mirror-switch] switch to", channel, "failed:", String(e)); switching = false; });
448
448
  }
449
449
 
450
450
  // "/open/<slug>", "scheme://open/<slug>" (host parses as "open"), and
@@ -465,14 +465,6 @@ function launcherSlug(path: string): string | null {
465
465
  }
466
466
  }
467
467
 
468
- /** "/" when the URL is the launcher's (starting a switch if it names another prototype), null otherwise. */
469
- export function handleLauncherPath(path: string): string | null {
470
- const slug = launcherSlug(path);
471
- if (slug === null) return null;
472
- if (slug && slug !== PROTOTYPE) switchToChannel(slug);
473
- return "/";
474
- }
475
-
476
468
  // Warm-link coverage. After Updates.reloadAsync() the React Native
477
469
  // Linking 'url' event no longer reaches the reloaded JS context (verified on
478
470
  // Android: the launcher's original context gets warm links, a prototype's
@@ -480,27 +472,48 @@ export function handleLauncherPath(path: string): string | null {
480
472
  // URL store, so listen there — and on every foreground, sweep getLinkingURL()
481
473
  // in case the event itself was missed. lastHandled stops the sweep from
482
474
  // re-firing on URLs we've already acted on.
483
- let lastHandled: string | null = null;
484
-
485
- function handleWarmUrl(url: string | null): void {
486
- if (!url || url === lastHandled) return;
487
- lastHandled = url;
488
- handleLauncherPath(url);
475
+ /**
476
+ * "/" when the URL is the launcher's, null otherwise. NEVER starts a switch:
477
+ * the initial URL of a reloaded context can be the STALE activity intent (on
478
+ * Android the activity keeps the intent that cold-started the app), and
479
+ * switching on it ping-pongs between prototypes. Switching decisions belong to
480
+ * the sweep below, which reads expo-linking's always-current URL store.
481
+ */
482
+ export function handleLauncherPath(path: string): string | null {
483
+ return launcherSlug(path) === null ? null : "/";
484
+ }
485
+
486
+ // The launcher-URL store is handle-once: after acting on a URL, clear it so a
487
+ // context created later (e.g. by a feed tap, which carries no URL) can't
488
+ // replay it as a phantom switch.
489
+ function sweepLinkingURL(): void {
490
+ let url: string | null = null;
491
+ try { url = ExpoLinking.getLinkingURL() ?? null; } catch { return; }
492
+ if (!url) return;
493
+ const slug = launcherSlug(url);
494
+ if (slug === null) return; // the prototype's own deep link — not ours to touch
495
+ try { ExpoLinking.clearInitialURL(); } catch {}
496
+ if (slug && slug !== PROTOTYPE) switchToChannel(slug);
489
497
  }
490
498
 
491
499
  export function subscribeLauncherLinks(): { remove: () => void } {
492
- // Seed with the URL that launched this context so the foreground sweep
493
- // doesn't replay it (+native-intent already handled the initial URL).
494
- try { lastHandled = ExpoLinking.getLinkingURL() ?? null; } catch {}
495
- const linkSub = ExpoLinking.addEventListener("url", ({ url }) => handleWarmUrl(url));
500
+ sweepLinkingURL(); // the URL that created this context (cold link or switch)
501
+ const linkSub = ExpoLinking.addEventListener("url", () => sweepLinkingURL());
496
502
  const stateSub = AppState.addEventListener("change", (state) => {
497
- if (state !== "active") return;
498
- try { handleWarmUrl(ExpoLinking.getLinkingURL() ?? null); } catch {}
503
+ if (state === "active") sweepLinkingURL();
499
504
  });
505
+ // The load-bearing path: in a context created by Updates.reloadAsync() the
506
+ // native->JS EVENT bridge (RN Linking, AppState, expo-linking's emitter) is
507
+ // dead on Android, but expo-linking's native lifecycle listener still writes
508
+ // every incoming URL to the store getLinkingURL() reads — and timers are
509
+ // pure JS. Poll it; the listeners above cover contexts where events work,
510
+ // and clearInitialURL() makes whichever path runs first win cleanly.
511
+ const timer = setInterval(sweepLinkingURL, 1500);
500
512
  return {
501
513
  remove: () => {
502
514
  linkSub.remove();
503
515
  stateSub.remove();
516
+ clearInterval(timer);
504
517
  },
505
518
  };
506
519
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calo-design/cli",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "One-line setup for Calo design tooling: logs in with your Calo email and installs the calo-design skill + design-system packages. No GitHub account needed.",
5
5
  "bin": {
6
6
  "calo-design": "bin/cli.js"