@akhilpulse/samadhaan-session-replay 0.6.2 → 0.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akhilpulse/samadhaan-session-replay",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "React Native SDK for Samadhaan Guided Support — session recording, live remote screen-share, take-control, guided-tour overlays, and Shake-to-Assist AI voice guide.",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -56,7 +56,7 @@ let pixelCopyDisabled = false; // turned true after repeated runtime failures to
56
56
  let pixelCopyFailStreak = 0; // consecutive non-busy failures; resets on success
57
57
 
58
58
  const STORAGE_KEY = "@akhilpulse/samadhaan-session-replay/sessionId";
59
- const SDK_VERSION = "0.6.2";
59
+ const SDK_VERSION = "0.6.4";
60
60
 
61
61
  // ---- Module-level session singleton --------------------------------------
62
62
  // Multiple SessionReplayProvider mounts (intentional or via React StrictMode's
@@ -857,36 +857,6 @@ export function SessionReplayProvider({ config, children }: { config: Config; ch
857
857
  </>
858
858
  )}
859
859
 
860
- {/* Shake-to-Assist: on-screen arrow pointing at the next element to tap. */}
861
- {assistantActive && assistantArrow && (() => {
862
- const { w, h } = viewSizeRef.current;
863
- const px = assistantArrow.nx * (w || 0);
864
- const py = assistantArrow.ny * (h || 0);
865
- // Keep the label bubble on-screen: flip below the target near the top edge.
866
- const below = py < 96;
867
- return (
868
- <View pointerEvents="none" style={StyleSheet.absoluteFill}>
869
- <View style={[styles.assistHalo, { left: px - 26, top: py - 26 }]} />
870
- <View style={[styles.assistDot, { left: px - 9, top: py - 9 }]} />
871
- {/* Arrow shaft pointing down at the target (or up when flipped). */}
872
- <View
873
- style={[
874
- styles.assistArrow,
875
- below
876
- ? { left: px - 9, top: py + 12 }
877
- : { left: px - 9, top: py - 30, transform: [{ rotate: "180deg" }] },
878
- ]}
879
- />
880
- {!!(assistantArrow.label || assistantArrow.instruction) && (
881
- <View style={[styles.assistBubble, { left: 16, right: 16, [below ? "top" : "bottom"]: below ? py + 36 : (h || 0) - py + 36 } as any]}>
882
- {!!assistantArrow.label && <Text style={styles.assistBubbleLabel}>{assistantArrow.label}</Text>}
883
- {!!assistantArrow.instruction && <Text style={styles.assistBubbleText}>{assistantArrow.instruction}</Text>}
884
- </View>
885
- )}
886
- </View>
887
- );
888
- })()}
889
-
890
860
  {/* Floating help button — draggable, tap to open the AI voice guide.
891
861
  This is the definitive trigger for Expo apps (no native shake module).
892
862
  Hidden during guided tours so it never collides with the End-tour pill. */}
@@ -925,12 +895,90 @@ export function SessionReplayProvider({ config, children }: { config: Config; ch
925
895
  </View>
926
896
  </View>
927
897
  )}
898
+
899
+ {/* Shake-to-Assist: on-screen arrow pointing at the next element to tap.
900
+ Rendered LAST so it draws above the bottom voice dock — targets near
901
+ the bottom of the screen used to be hidden underneath it. */}
902
+ {assistantActive && assistantArrow && (
903
+ <AssistArrowOverlay
904
+ nx={assistantArrow.nx}
905
+ ny={assistantArrow.ny}
906
+ label={assistantArrow.label}
907
+ instruction={assistantArrow.instruction}
908
+ w={viewSizeRef.current.w}
909
+ h={viewSizeRef.current.h}
910
+ />
911
+ )}
928
912
  </View>
929
913
  </SessionReplayContext.Provider>
930
914
  );
931
915
  }
932
916
 
933
917
  // ---- Shake-to-Assist UI bits ----
918
+
919
+ /**
920
+ * Pulsing arrow + label bubble pointing at the next element to tap.
921
+ * - Halo pulses continuously so the target is hard to miss.
922
+ * - The label bubble is clamped so it never hides behind the bottom voice dock
923
+ * (which occupies roughly the bottom 110px of the screen).
924
+ */
925
+ function AssistArrowOverlay({
926
+ nx, ny, label, instruction, w, h,
927
+ }: { nx: number; ny: number; label?: string; instruction?: string; w: number; h: number }) {
928
+ const pulse = useRef(new Animated.Value(0)).current;
929
+ useEffect(() => {
930
+ const loop = Animated.loop(
931
+ Animated.sequence([
932
+ Animated.timing(pulse, { toValue: 1, duration: 650, useNativeDriver: true }),
933
+ Animated.timing(pulse, { toValue: 0, duration: 650, useNativeDriver: true }),
934
+ ])
935
+ );
936
+ loop.start();
937
+ return () => loop.stop();
938
+ }, [pulse]);
939
+
940
+ const px = nx * (w || 0);
941
+ const py = ny * (h || 0);
942
+ // Bubble placement:
943
+ // - target near the TOP edge -> bubble just below the target
944
+ // - target near the BOTTOM -> bubble pinned to the top of the screen, so it
945
+ // never covers the target or the app's bottom UI
946
+ // (bottom sheets / nav bars live there)
947
+ // - otherwise -> bubble just above the target
948
+ const nearTop = py < 96;
949
+ const nearBottom = py > (h || 0) - 260;
950
+ const bubblePos: any = nearTop
951
+ ? { top: py + 36 }
952
+ : nearBottom
953
+ ? { top: 70 }
954
+ : { bottom: (h || 0) - py + 36 };
955
+ const scale = pulse.interpolate({ inputRange: [0, 1], outputRange: [1, 1.45] });
956
+ const opacity = pulse.interpolate({ inputRange: [0, 1], outputRange: [1, 0.35] });
957
+
958
+ return (
959
+ <View pointerEvents="none" style={StyleSheet.absoluteFill}>
960
+ <Animated.View
961
+ style={[styles.assistHalo, { left: px - 26, top: py - 26, transform: [{ scale }], opacity }]}
962
+ />
963
+ <View style={[styles.assistDot, { left: px - 9, top: py - 9 }]} />
964
+ {/* Arrow shaft pointing down at the target (or up when flipped). */}
965
+ <View
966
+ style={[
967
+ styles.assistArrow,
968
+ nearTop
969
+ ? { left: px - 9, top: py + 12 }
970
+ : { left: px - 9, top: py - 30, transform: [{ rotate: "180deg" }] },
971
+ ]}
972
+ />
973
+ {!!(label || instruction) && (
974
+ <View style={[styles.assistBubble, { left: 16, right: 16, ...bubblePos }]}>
975
+ {!!label && <Text style={styles.assistBubbleLabel}>{label}</Text>}
976
+ {!!instruction && <Text style={styles.assistBubbleText}>{instruction}</Text>}
977
+ </View>
978
+ )}
979
+ </View>
980
+ );
981
+ }
934
982
  function assistantStatusLabel(phase: AssistantPhase, micStatus?: MicStatus | null): string {
935
983
  // Be honest when we can't actually hear the user — a fake "Listening…"
936
984
  // with a dead mic is the worst possible UX (bar stuck forever).