@calo-design/cli 0.12.2 → 0.12.4

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 +67 -6
  2. package/package.json +1 -1
@@ -404,6 +404,7 @@ export default function RootLayout() {
404
404
  }
405
405
  fs.writeFileSync(path.join(stagedAppDir, "_layout.tsx"), managed);
406
406
  fs.writeFileSync(path.join(mirrorChromeDir, "mirror-chrome.tsx"), mirrorChromeSource({ slug, owner }));
407
+ fs.writeFileSync(path.join(mirrorChromeDir, "mirror-escape.tsx"), mirrorEscapeSource());
407
408
  injectNativeIntent(stagedAppDir, mirrorChromeDir, slug);
408
409
  }
409
410
 
@@ -522,15 +523,23 @@ function channelForRef(ref: string): { channel: string; viaLauncher: boolean } {
522
523
  // context created later (e.g. by a feed tap, which carries no URL) can't
523
524
  // replay it as a phantom switch. Exception: a ref we bounce to the launcher
524
525
  // stays in the store on purpose — the launcher consumes (and clears) it.
526
+ //
527
+ // NO slug-based self no-op: with version pinning, one slug spans MANY channels
528
+ // (head + every pinned version), and this same bundle serves several of them —
529
+ // so "slug === PROTOTYPE" cannot mean "already here". A plain self-link now
530
+ // switches to the HEAD channel (the escape hatch out of a pinned version; from
531
+ // the head it's a redundant refresh-to-latest, which is harmless and even
532
+ // useful). Ping-pong protection comes from the handle-once clearing + the
533
+ // switching latch, not from slug comparison.
525
534
  function sweepLinkingURL(): void {
526
535
  let url: string | null = null;
527
536
  try { url = ExpoLinking.getLinkingURL() ?? null; } catch { return; }
528
537
  if (!url) return;
529
538
  const slug = launcherSlug(url);
530
539
  if (slug === null) return; // the prototype's own deep link — not ours to touch
531
- if (!slug || slug === PROTOTYPE) {
540
+ if (!slug) {
532
541
  try { ExpoLinking.clearInitialURL(); } catch {}
533
- return;
542
+ return; // bare /open — nothing to switch to
534
543
  }
535
544
  const target = channelForRef(slug);
536
545
  if (!target.viaLauncher) {
@@ -539,6 +548,13 @@ function sweepLinkingURL(): void {
539
548
  switchToChannel(target.channel);
540
549
  }
541
550
 
551
+ // Return to the Mirror launcher (its channel is "mirror"). The shell's native
552
+ // shake-to-return exists too, but this JS path works on every binary — it's what
553
+ // the chrome's floating escape control calls.
554
+ export function returnToMirror(): void {
555
+ switchToChannel("mirror");
556
+ }
557
+
542
558
  export function subscribeLauncherLinks(): { remove: () => void } {
543
559
  sweepLinkingURL(); // the URL that created this context (cold link or switch)
544
560
  const linkSub = ExpoLinking.addEventListener("url", () => sweepLinkingURL());
@@ -596,13 +612,52 @@ function runtimeHasSentry() {
596
612
  // killing the shell. Everything is defensive — on an old shell binary without
597
613
  // the Sentry NATIVE module, init falls back to JS-only transport (and the
598
614
  // try/catch backstops that), so prototypes never crash BECAUSE of reporting.
615
+ // Floating return-to-launcher control, rendered by BOTH MirrorChrome variants.
616
+ // The shell's native shake-to-return exists too, but this JS escape works on
617
+ // every binary and every OS — a prototype must never be a dead end. Small and
618
+ // translucent on purpose; bottom-left so it never collides with FABs/CTAs that
619
+ // conventionally live bottom-right.
620
+ function mirrorEscapeSource() {
621
+ return `import { Pressable, Text } from "react-native";
622
+ import { returnToMirror } from "./mirror-switch";
623
+
624
+ // Managed by calo-design push — the always-available way OUT of a prototype.
625
+ export function MirrorEscape() {
626
+ return (
627
+ <Pressable
628
+ onPress={returnToMirror}
629
+ accessibilityRole="button"
630
+ accessibilityLabel="Back to Calo Mirror"
631
+ hitSlop={10}
632
+ style={({ pressed }) => ({
633
+ position: "absolute",
634
+ left: 12,
635
+ bottom: 44,
636
+ width: 38,
637
+ height: 38,
638
+ borderRadius: 19,
639
+ alignItems: "center",
640
+ justifyContent: "center",
641
+ backgroundColor: "rgba(17,17,17,0.42)",
642
+ opacity: pressed ? 0.9 : 0.55,
643
+ zIndex: 9999,
644
+ elevation: 9,
645
+ })}
646
+ >
647
+ <Text style={{ color: "#ffffff", fontSize: 17, lineHeight: 20 }}>{"\\u21A9"}</Text>
648
+ </Pressable>
649
+ );
650
+ }
651
+ `;
652
+ }
653
+
599
654
  function mirrorChromeSource({ slug, owner }) {
600
655
  if (!SENTRY_DSN || !runtimeHasSentry()) {
601
656
  return `import { useEffect, type ReactNode } from "react";
657
+ import { View } from "react-native";
602
658
  import { subscribeLauncherLinks } from "./mirror-switch";
659
+ import { MirrorEscape } from "./mirror-escape";
603
660
 
604
- // Returning to the Mirror launcher is handled NATIVELY by the shell binary:
605
- // shake the device (see calo-design-mirror/plugins/withCaloMirrorIos.js).
606
661
  // Crash reporting was NOT injected: no Sentry DSN configured or the shared
607
662
  // runtime predates @sentry/react-native (run \`calo-design update\`).
608
663
  export function MirrorChrome({ children }: { children: ReactNode }) {
@@ -611,13 +666,19 @@ export function MirrorChrome({ children }: { children: ReactNode }) {
611
666
  const sub = subscribeLauncherLinks();
612
667
  return () => sub.remove();
613
668
  }, []);
614
- return <>{children}</>;
669
+ return (
670
+ <View style={{ flex: 1 }}>
671
+ {children}
672
+ <MirrorEscape />
673
+ </View>
674
+ );
615
675
  }
616
676
  `;
617
677
  }
618
678
  return `import { Component, type ErrorInfo, type ReactNode } from "react";
619
679
  import { Pressable, ScrollView, Text, View } from "react-native";
620
680
  import { subscribeLauncherLinks } from "./mirror-switch";
681
+ import { MirrorEscape } from "./mirror-escape";
621
682
 
622
683
  // Injected by \`calo-design push\` — crash reporting for this prototype.
623
684
  // Returning to the Mirror launcher stays NATIVE: shake the device.
@@ -662,7 +723,7 @@ export class MirrorChrome extends Component<{ children: ReactNode }, CrashState>
662
723
 
663
724
  render() {
664
725
  const { error } = this.state;
665
- if (!error) return <>{this.props.children}</>;
726
+ if (!error) return <View style={{ flex: 1 }}>{this.props.children}<MirrorEscape /></View>;
666
727
  return (
667
728
  <View style={{ flex: 1, backgroundColor: "#ffffff", padding: 24, justifyContent: "center", gap: 12 }}>
668
729
  <Text style={{ fontSize: 20, fontWeight: "700", color: "#1a1a1a" }}>This prototype crashed</Text>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calo-design/cli",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
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"