@mushi-mushi/react-native 0.19.2 → 0.20.0

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/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  useEffect as useEffect3,
13
13
  useRef as useRef3,
14
14
  useState as useState2,
15
- useCallback as useCallback3,
15
+ useCallback as useCallback4,
16
16
  useMemo
17
17
  } from "react";
18
18
  import {
@@ -474,33 +474,65 @@ import {
474
474
  ScrollView,
475
475
  useColorScheme
476
476
  } from "react-native";
477
- import { mushiPalette } from "@mushi-mushi/core";
477
+ import { mushiPalette, MUSHI_BANNER_NEON, MUSHI_COPY } from "@mushi-mushi/core";
478
+ import { getLocale } from "@mushi-mushi/web/i18n";
479
+
480
+ // src/reporter-status.ts
481
+ function reporterStatusShort(status) {
482
+ switch (status) {
483
+ case "new":
484
+ case "queued":
485
+ case "pending":
486
+ case "submitted":
487
+ return "Received";
488
+ case "classified":
489
+ case "triaged":
490
+ case "grouped":
491
+ case "dispatched":
492
+ return "In review";
493
+ case "fixing":
494
+ return "Fixing";
495
+ case "fixed":
496
+ case "resolved":
497
+ case "completed":
498
+ return "Fixed";
499
+ case "dismissed":
500
+ return "Closed";
501
+ default:
502
+ return status.replace(/_/g, " ").slice(0, 16);
503
+ }
504
+ }
505
+
506
+ // src/components/MushiBottomSheet.tsx
478
507
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
479
508
  var { height: SCREEN_HEIGHT } = Dimensions2.get("window");
480
509
  var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
481
510
  var DISMISS_THRESHOLD = 80;
482
- var NEON = {
483
- accent: "#0FFF50",
484
- accentHover: "#00E646",
485
- ink: "#0a1a0a",
486
- border: "#00C43A"
511
+ var CATEGORY_KEYS = ["bug", "slow", "visual", "confusing", "other"];
512
+ var CATEGORY_EMOJI = {
513
+ bug: "\u{1F41B}",
514
+ slow: "\u{1F422}",
515
+ visual: "\u{1F3A8}",
516
+ confusing: "\u{1F615}",
517
+ other: "\u{1F4AC}"
487
518
  };
488
- var CATEGORIES = [
489
- { key: "bug", emoji: "\u{1F41B}", label: "Bug" },
490
- { key: "slow", emoji: "\u{1F422}", label: "Slow" },
491
- { key: "visual", emoji: "\u{1F3A8}", label: "Visual" },
492
- { key: "confusing", emoji: "\u{1F615}", label: "Confusing" },
493
- { key: "other", emoji: "\u{1F4AC}", label: "Other" }
494
- ];
495
519
  var VERIFIABLE_STATUSES = /* @__PURE__ */ new Set(["fixed", "resolved", "verified"]);
496
520
  var MushiBottomSheet = ({
497
521
  visible,
498
522
  onClose,
523
+ preferredTab = "report",
499
524
  screenshotDataUrl,
500
525
  onClearScreenshot,
501
- screenshotSensitiveHint
526
+ screenshotSensitiveHint,
527
+ assistantEnabled = false,
528
+ assistantLabel = MUSHI_COPY.assistantTab,
529
+ assistantGreeting,
530
+ assistantSuggestions = [],
531
+ inboxPollIntervalMs = 0
502
532
  }) => {
503
533
  const mushi = useMushiContext();
534
+ const t = getLocale();
535
+ const greeting = assistantGreeting ?? t.assistant.defaultGreeting;
504
536
  const scheme = useColorScheme();
505
537
  const dark = scheme === "dark";
506
538
  const translateY = useRef(new Animated.Value(SHEET_HEIGHT)).current;
@@ -514,6 +546,11 @@ var MushiBottomSheet = ({
514
546
  const [selectedReportId, setSelectedReportId] = useState(null);
515
547
  const [threadComments, setThreadComments] = useState([]);
516
548
  const [replyText, setReplyText] = useState("");
549
+ const [assistantInput, setAssistantInput] = useState("");
550
+ const [assistantThreadId, setAssistantThreadId] = useState(null);
551
+ const [assistantSending, setAssistantSending] = useState(false);
552
+ const [assistantError, setAssistantError] = useState(null);
553
+ const [assistantTurns, setAssistantTurns] = useState([]);
517
554
  const [screenshotAttached, setScreenshotAttached] = useState(true);
518
555
  const loadInbox = useCallback(async () => {
519
556
  if (!mushi?.listMyReports) return;
@@ -528,9 +565,17 @@ var MushiBottomSheet = ({
528
565
  useEffect(() => {
529
566
  if (visible) {
530
567
  setScreenshotAttached(true);
531
- if (sheetTab === "inbox") void loadInbox();
568
+ setSheetTab(preferredTab);
569
+ if (preferredTab === "inbox") void loadInbox();
532
570
  }
533
- }, [visible, sheetTab, loadInbox]);
571
+ }, [visible, preferredTab, loadInbox]);
572
+ useEffect(() => {
573
+ if (!visible || sheetTab !== "inbox" || inboxPollIntervalMs <= 0) return;
574
+ const timer = setInterval(() => {
575
+ void loadInbox();
576
+ }, inboxPollIntervalMs);
577
+ return () => clearInterval(timer);
578
+ }, [visible, sheetTab, inboxPollIntervalMs, loadInbox]);
534
579
  const openThread = useCallback(async (reportId) => {
535
580
  setSelectedReportId(reportId);
536
581
  if (!mushi?.listMyComments) return;
@@ -549,6 +594,29 @@ var MushiBottomSheet = ({
549
594
  await loadInbox();
550
595
  await openThread(selectedReportId);
551
596
  }, [mushi, selectedReportId, loadInbox, openThread]);
597
+ const sendAssistant = useCallback(async (message) => {
598
+ if (!mushi?.askAssistant || !message.trim() || assistantSending) return;
599
+ const trimmed = message.trim();
600
+ setAssistantInput("");
601
+ setAssistantError(null);
602
+ setAssistantTurns((prev) => [...prev, { role: "user", text: trimmed }]);
603
+ setAssistantSending(true);
604
+ try {
605
+ const reply = await mushi.askAssistant(trimmed, assistantThreadId);
606
+ if (!reply) {
607
+ setAssistantError("Could not reach the assistant. Try again.");
608
+ return;
609
+ }
610
+ if (reply.threadId) setAssistantThreadId(reply.threadId);
611
+ const botText = reply.kind === "clarify" ? reply.question ?? reply.text ?? "" : reply.text ?? "";
612
+ setAssistantTurns((prev) => [
613
+ ...prev,
614
+ { role: "bot", text: botText, options: reply.options }
615
+ ]);
616
+ } finally {
617
+ setAssistantSending(false);
618
+ }
619
+ }, [mushi, assistantSending, assistantThreadId]);
552
620
  const resetForm = useCallback(() => {
553
621
  setCategory(null);
554
622
  setDescription("");
@@ -633,7 +701,8 @@ var MushiBottomSheet = ({
633
701
  };
634
702
  const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
635
703
  const pal = mushiPalette(dark ? "dark" : "light");
636
- const colors = dark ? { bg: pal.paper, text: pal.ink, sub: pal.inkMuted, card: pal.paperRaised, accent: NEON.accent, accentInk: NEON.ink, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.6)", disabled: "#3a3a3c", disabledText: pal.inkFaint } : { bg: pal.paperRaised, text: pal.ink, sub: pal.inkMuted, card: pal.paper, accent: NEON.accent, accentInk: NEON.ink, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.35)", disabled: "#d1d1d6", disabledText: pal.inkFaint };
704
+ const colors = dark ? { bg: pal.paper, text: pal.ink, sub: pal.inkMuted, card: pal.paperRaised, accent: MUSHI_BANNER_NEON.bg, accentInk: MUSHI_BANNER_NEON.fg, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.6)", disabled: "#3a3a3c", disabledText: pal.inkFaint } : { bg: pal.paperRaised, text: pal.ink, sub: pal.inkMuted, card: pal.paper, accent: MUSHI_BANNER_NEON.bg, accentInk: MUSHI_BANNER_NEON.fg, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.35)", disabled: "#d1d1d6", disabledText: pal.inkFaint };
705
+ const sheetTabs = ["report", "inbox", ...assistantEnabled ? ["assistant"] : []];
637
706
  const canSubmit = !!category && description.trim().length > 0 && phase === "form";
638
707
  return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, children: /* @__PURE__ */ jsxs(
639
708
  KeyboardAvoidingView,
@@ -658,11 +727,11 @@ var MushiBottomSheet = ({
658
727
  ],
659
728
  children: [
660
729
  /* @__PURE__ */ jsx(View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ jsx(View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
661
- /* @__PURE__ */ jsxs(View, { style: [s.brandHeader, { backgroundColor: NEON.accent, borderBottomColor: NEON.border }], children: [
662
- /* @__PURE__ */ jsx(Text, { style: [s.brandEyebrow, { color: NEON.ink }], children: "MUSHI \xB7 BETA" }),
663
- /* @__PURE__ */ jsx(Text, { style: [s.brandTitle, { color: NEON.ink }], children: sheetTab === "inbox" ? "Your reports" : sheetTab === "community" ? "Community" : "Report an issue" })
730
+ /* @__PURE__ */ jsxs(View, { style: [s.brandHeader, { backgroundColor: MUSHI_BANNER_NEON.bg, borderBottomColor: MUSHI_BANNER_NEON.border }], children: [
731
+ /* @__PURE__ */ jsx(Text, { style: [s.brandEyebrow, { color: MUSHI_BANNER_NEON.fg }], children: "MUSHI \xB7 BETA" }),
732
+ /* @__PURE__ */ jsx(Text, { style: [s.brandTitle, { color: MUSHI_BANNER_NEON.fg }], children: sheetTab === "assistant" ? assistantLabel : sheetTab === "inbox" ? t.flows.reports.title : t.widget.title })
664
733
  ] }),
665
- /* @__PURE__ */ jsx(View, { style: s.tabRow, children: ["report", "inbox", "community"].map((tab) => /* @__PURE__ */ jsx(
734
+ /* @__PURE__ */ jsx(View, { style: s.tabRow, children: sheetTabs.map((tab) => /* @__PURE__ */ jsx(
666
735
  TouchableOpacity,
667
736
  {
668
737
  onPress: () => {
@@ -671,12 +740,68 @@ var MushiBottomSheet = ({
671
740
  else setSelectedReportId(null);
672
741
  },
673
742
  style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
674
- children: /* @__PURE__ */ jsx(Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.text : colors.sub }], children: tab === "report" ? "Report" : tab === "inbox" ? "Your reports" : "Community" })
743
+ children: /* @__PURE__ */ jsx(Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.text : colors.sub }], children: tab === "report" ? t.widget.trigger : tab === "inbox" ? t.flows.reports.title : assistantLabel })
675
744
  },
676
745
  tab
677
746
  )) }),
678
- sheetTab === "inbox" ? /* @__PURE__ */ jsx(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: inboxLoading ? /* @__PURE__ */ jsx(Text, { style: { color: colors.sub }, children: "Loading\u2026" }) : selectedReportId ? /* @__PURE__ */ jsxs(Fragment, { children: [
679
- /* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ jsx(Text, { style: { color: colors.accent, marginBottom: 8 }, children: "\u2190 Back" }) }),
747
+ sheetTab === "assistant" ? /* @__PURE__ */ jsxs(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
748
+ assistantTurns.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
749
+ /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, marginBottom: 12, lineHeight: 20 }, children: greeting }),
750
+ assistantSuggestions.map((chip) => /* @__PURE__ */ jsx(
751
+ TouchableOpacity,
752
+ {
753
+ style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.card }],
754
+ onPress: () => void sendAssistant(chip),
755
+ children: /* @__PURE__ */ jsx(Text, { style: { color: colors.text, fontSize: 13 }, children: chip })
756
+ },
757
+ chip
758
+ ))
759
+ ] }) : assistantTurns.map((turn, idx) => /* @__PURE__ */ jsxs(
760
+ View,
761
+ {
762
+ style: [
763
+ s.assistantBubble,
764
+ {
765
+ alignSelf: turn.role === "user" ? "flex-end" : "flex-start",
766
+ backgroundColor: turn.role === "user" ? colors.accent : colors.card
767
+ }
768
+ ],
769
+ children: [
770
+ /* @__PURE__ */ jsx(Text, { style: { color: turn.role === "user" ? colors.accentInk : colors.text }, children: turn.text }),
771
+ turn.options?.map((opt) => /* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => void sendAssistant(opt), style: { marginTop: 8 }, children: /* @__PURE__ */ jsx(Text, { style: { color: colors.accent, fontSize: 12 }, children: opt }) }, opt))
772
+ ]
773
+ },
774
+ `${turn.role}-${idx}`
775
+ )),
776
+ assistantSending ? /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, marginTop: 8 }, accessibilityLiveRegion: "polite", children: t.assistant.thinking }) : null,
777
+ assistantError ? /* @__PURE__ */ jsx(Text, { style: { color: pal.danger, marginTop: 8 }, accessibilityRole: "alert", children: assistantError }) : null,
778
+ /* @__PURE__ */ jsxs(View, { style: s.assistantComposer, children: [
779
+ /* @__PURE__ */ jsx(
780
+ TextInput,
781
+ {
782
+ style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.card, color: colors.text, borderColor: colors.border }],
783
+ placeholder: t.assistant.inputPlaceholder,
784
+ placeholderTextColor: colors.sub,
785
+ value: assistantInput,
786
+ onChangeText: setAssistantInput,
787
+ editable: !assistantSending
788
+ }
789
+ ),
790
+ /* @__PURE__ */ jsx(
791
+ TouchableOpacity,
792
+ {
793
+ style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16 }],
794
+ onPress: () => void sendAssistant(assistantInput),
795
+ disabled: !assistantInput.trim() || assistantSending,
796
+ children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: "\u2191" })
797
+ }
798
+ )
799
+ ] })
800
+ ] }) : sheetTab === "inbox" ? /* @__PURE__ */ jsx(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: inboxLoading ? /* @__PURE__ */ jsx(Text, { style: { color: colors.sub }, children: t.flows.reports.loading }) : selectedReportId ? /* @__PURE__ */ jsxs(Fragment, { children: [
801
+ /* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ jsxs(Text, { style: { color: colors.accent, marginBottom: 8 }, children: [
802
+ "\u2190 ",
803
+ t.widget.back
804
+ ] }) }),
680
805
  threadComments.map((c) => /* @__PURE__ */ jsxs(View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
681
806
  /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
682
807
  /* @__PURE__ */ jsx(Text, { style: { color: colors.text }, children: c.body })
@@ -684,42 +809,26 @@ var MushiBottomSheet = ({
684
809
  VERIFIABLE_STATUSES.has(
685
810
  inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
686
811
  ) && /* @__PURE__ */ jsxs(View, { style: s.verifyRow, children: [
687
- /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: "Yes, fixed" }) }),
688
- /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.text }], children: "Not fixed" }) })
812
+ /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: t.flows.thread.confirmFixed }) }),
813
+ /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.text }], children: t.flows.thread.notFixed }) })
689
814
  ] }),
690
815
  /* @__PURE__ */ jsx(
691
816
  TextInput,
692
817
  {
693
818
  style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
694
- placeholder: "Reply\u2026",
819
+ placeholder: t.flows.thread.replyPlaceholder,
695
820
  placeholderTextColor: colors.sub,
696
821
  value: replyText,
697
822
  onChangeText: setReplyText
698
823
  }
699
824
  ),
700
- /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: "Send" }) })
825
+ /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: t.flows.thread.send }) })
701
826
  ] }) : inboxReports.map((r) => /* @__PURE__ */ jsxs(TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
702
827
  /* @__PURE__ */ jsx(Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
703
- /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: r.status })
704
- ] }, r.id)) }) : sheetTab === "community" ? /* @__PURE__ */ jsxs(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
705
- /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 13, marginBottom: 16, lineHeight: 18 }, children: "Sign in to track your reports across apps and see the global leaderboard. No password needed." }),
706
- /* @__PURE__ */ jsx(
707
- TextInput,
708
- {
709
- style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
710
- placeholder: "you@example.com",
711
- placeholderTextColor: colors.sub,
712
- keyboardType: "email-address",
713
- autoCapitalize: "none",
714
- autoCorrect: false,
715
- textContentType: "emailAddress"
716
- }
717
- ),
718
- /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: "Send sign-in link \u2192" }) }),
719
- /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11, textAlign: "center", marginTop: 12 }, children: "We'll email you a one-tap sign-in link. No password, ever." })
720
- ] }) : phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, children: [
828
+ /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: reporterStatusShort(r.status) })
829
+ ] }, r.id)) }) : phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, children: [
721
830
  /* @__PURE__ */ jsx(Text, { style: [s.sentEmoji], children: "\u2705" }),
722
- /* @__PURE__ */ jsx(Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
831
+ /* @__PURE__ */ jsx(Text, { style: [s.sentText, { color: colors.text }], children: t.widget.submitted })
723
832
  ] }) : /* @__PURE__ */ jsxs(
724
833
  ScrollView,
725
834
  {
@@ -727,13 +836,13 @@ var MushiBottomSheet = ({
727
836
  keyboardShouldPersistTaps: "handled",
728
837
  contentContainerStyle: { paddingBottom: 24 },
729
838
  children: [
730
- /* @__PURE__ */ jsx(Text, { style: [s.stepLabel, { color: colors.sub }], children: "What kind of issue?" }),
731
- /* @__PURE__ */ jsx(View, { style: s.catRow, children: CATEGORIES.map((c) => {
732
- const active = category === c.key;
839
+ /* @__PURE__ */ jsx(Text, { style: [s.stepLabel, { color: colors.sub }], children: t.step1.heading }),
840
+ /* @__PURE__ */ jsx(View, { style: s.catRow, children: CATEGORY_KEYS.map((key) => {
841
+ const active = category === key;
733
842
  return /* @__PURE__ */ jsxs(
734
843
  TouchableOpacity,
735
844
  {
736
- onPress: () => setCategory(c.key),
845
+ onPress: () => setCategory(key),
737
846
  activeOpacity: 0.7,
738
847
  style: [
739
848
  s.catBtn,
@@ -743,7 +852,7 @@ var MushiBottomSheet = ({
743
852
  }
744
853
  ],
745
854
  children: [
746
- /* @__PURE__ */ jsx(Text, { style: s.catEmoji, children: c.emoji }),
855
+ /* @__PURE__ */ jsx(Text, { style: s.catEmoji, children: CATEGORY_EMOJI[key] }),
747
856
  /* @__PURE__ */ jsx(
748
857
  Text,
749
858
  {
@@ -751,12 +860,12 @@ var MushiBottomSheet = ({
751
860
  s.catLabel,
752
861
  { color: active ? colors.accentInk : colors.text }
753
862
  ],
754
- children: c.label
863
+ children: t.step1.categories[key]
755
864
  }
756
865
  )
757
866
  ]
758
867
  },
759
- c.key
868
+ key
760
869
  );
761
870
  }) }),
762
871
  /* @__PURE__ */ jsx(
@@ -770,7 +879,7 @@ var MushiBottomSheet = ({
770
879
  borderColor: colors.border
771
880
  }
772
881
  ],
773
- placeholder: "What happened?",
882
+ placeholder: t.step3.descriptionPlaceholder,
774
883
  placeholderTextColor: colors.sub,
775
884
  multiline: true,
776
885
  textAlignVertical: "top",
@@ -785,12 +894,12 @@ var MushiBottomSheet = ({
785
894
  {
786
895
  source: { uri: activeScreenshot },
787
896
  style: s.screenshotThumb,
788
- accessibilityLabel: "Attached screenshot"
897
+ accessibilityLabel: t.step3.screenshotPreviewAlt
789
898
  }
790
899
  ),
791
900
  /* @__PURE__ */ jsxs(View, { style: s.screenshotMeta, children: [
792
- /* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
793
- /* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.sub }], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : "Helps the team see the issue" })
901
+ /* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.text }], children: t.step3.screenshotAttached.replace(" \u2713", "") }),
902
+ /* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.sub }], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : t.step3.screenshotSensitiveHint.split("\u2014")[0]?.trim() ?? t.step3.screenshotSensitiveHint })
794
903
  ] }),
795
904
  /* @__PURE__ */ jsx(
796
905
  TouchableOpacity,
@@ -800,7 +909,7 @@ var MushiBottomSheet = ({
800
909
  onClearScreenshot?.();
801
910
  },
802
911
  hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
803
- accessibilityLabel: "Remove screenshot",
912
+ accessibilityLabel: t.widget.close,
804
913
  children: /* @__PURE__ */ jsx(Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
805
914
  }
806
915
  )
@@ -815,7 +924,7 @@ var MushiBottomSheet = ({
815
924
  s.submitBtn,
816
925
  { backgroundColor: canSubmit ? colors.accent : colors.disabled }
817
926
  ],
818
- children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: canSubmit ? colors.accentInk : colors.disabledText }], children: phase === "sending" ? "Sending\u2026" : "Submit report" })
927
+ children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: canSubmit ? colors.accentInk : colors.disabledText }], children: phase === "sending" ? t.widget.submitting : t.widget.submit })
819
928
  }
820
929
  )
821
930
  ]
@@ -1021,6 +1130,27 @@ var s = StyleSheet.create({
1021
1130
  borderRadius: 10,
1022
1131
  paddingVertical: 10,
1023
1132
  alignItems: "center"
1133
+ },
1134
+ assistantBubble: {
1135
+ maxWidth: "88%",
1136
+ borderRadius: 12,
1137
+ padding: 12,
1138
+ marginBottom: 10
1139
+ },
1140
+ assistantChip: {
1141
+ borderWidth: 1,
1142
+ borderRadius: 999,
1143
+ paddingHorizontal: 12,
1144
+ paddingVertical: 8,
1145
+ marginBottom: 8,
1146
+ alignSelf: "flex-start"
1147
+ },
1148
+ assistantComposer: {
1149
+ flexDirection: "row",
1150
+ alignItems: "flex-end",
1151
+ gap: 8,
1152
+ marginTop: 12,
1153
+ paddingBottom: 8
1024
1154
  }
1025
1155
  });
1026
1156
 
@@ -1034,9 +1164,9 @@ import {
1034
1164
  Dimensions as Dimensions3,
1035
1165
  useColorScheme as useColorScheme2
1036
1166
  } from "react-native";
1037
- import { mushiPalette as mushiPalette2, MUSHI_COPY } from "@mushi-mushi/core";
1167
+ import { mushiPalette as mushiPalette2, MUSHI_COPY as MUSHI_COPY2, MUSHI_GEOMETRY } from "@mushi-mushi/core";
1038
1168
  import { jsx as jsx2 } from "react/jsx-runtime";
1039
- var FAB_SIZE = 56;
1169
+ var FAB_SIZE = MUSHI_GEOMETRY.fabSize;
1040
1170
  var EDGE_PADDING = 12;
1041
1171
  function getScreenBounds() {
1042
1172
  const { width, height } = Dimensions3.get("window");
@@ -1196,7 +1326,7 @@ var MushiFloatingButton = ({
1196
1326
  onStartShouldSetResponder: () => !draggable,
1197
1327
  onResponderGrant: handlePressIn,
1198
1328
  onResponderRelease: handlePressOut,
1199
- children: /* @__PURE__ */ jsx2(Animated2.Text, { style: s2.emoji, children: MUSHI_COPY.triggerText })
1329
+ children: /* @__PURE__ */ jsx2(Animated2.Text, { style: s2.emoji, children: MUSHI_COPY2.triggerText })
1200
1330
  }
1201
1331
  );
1202
1332
  };
@@ -1219,12 +1349,119 @@ var s2 = StyleSheet2.create({
1219
1349
  }
1220
1350
  });
1221
1351
 
1352
+ // src/components/MushiBanner.tsx
1353
+ import { useCallback as useCallback3 } from "react";
1354
+ import {
1355
+ View as View2,
1356
+ Text as Text2,
1357
+ TouchableOpacity as TouchableOpacity2,
1358
+ StyleSheet as StyleSheet3,
1359
+ Platform as Platform4
1360
+ } from "react-native";
1361
+ import { MUSHI_BANNER_NEON as MUSHI_BANNER_NEON2, MUSHI_COPY as MUSHI_COPY3, MUSHI_GEOMETRY as MUSHI_GEOMETRY2 } from "@mushi-mushi/core";
1362
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1363
+ var MUSHI_BANNER_DEFAULT_HEIGHT = MUSHI_GEOMETRY2.bannerHeight;
1364
+ var MushiBanner = ({
1365
+ onPress,
1366
+ onDismiss,
1367
+ message = MUSHI_COPY3.bannerMessage,
1368
+ label = MUSHI_COPY3.bannerLabel,
1369
+ onLayoutHeight
1370
+ }) => {
1371
+ const handleLayout = useCallback3(
1372
+ (e) => {
1373
+ onLayoutHeight?.(e.nativeEvent.layout.height);
1374
+ },
1375
+ [onLayoutHeight]
1376
+ );
1377
+ return /* @__PURE__ */ jsxs2(
1378
+ View2,
1379
+ {
1380
+ style: s3.wrap,
1381
+ onLayout: handleLayout,
1382
+ accessibilityRole: "header",
1383
+ accessibilityLabel: "Mushi feedback banner",
1384
+ children: [
1385
+ /* @__PURE__ */ jsxs2(TouchableOpacity2, { style: s3.main, onPress, activeOpacity: 0.85, children: [
1386
+ /* @__PURE__ */ jsx3(Text2, { style: s3.eyebrow, children: label.toUpperCase() }),
1387
+ /* @__PURE__ */ jsx3(Text2, { style: s3.message, numberOfLines: 2, children: message }),
1388
+ /* @__PURE__ */ jsx3(Text2, { style: s3.cta, children: MUSHI_COPY3.bugCta })
1389
+ ] }),
1390
+ onDismiss ? /* @__PURE__ */ jsx3(
1391
+ TouchableOpacity2,
1392
+ {
1393
+ style: s3.dismiss,
1394
+ onPress: onDismiss,
1395
+ accessibilityRole: "button",
1396
+ accessibilityLabel: "Dismiss banner",
1397
+ hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
1398
+ children: /* @__PURE__ */ jsx3(Text2, { style: s3.dismissText, children: "\xD7" })
1399
+ }
1400
+ ) : null
1401
+ ]
1402
+ }
1403
+ );
1404
+ };
1405
+ var s3 = StyleSheet3.create({
1406
+ wrap: {
1407
+ position: "absolute",
1408
+ left: 0,
1409
+ right: 0,
1410
+ bottom: 0,
1411
+ minHeight: MUSHI_GEOMETRY2.bannerHeight,
1412
+ flexDirection: "row",
1413
+ alignItems: "stretch",
1414
+ backgroundColor: MUSHI_BANNER_NEON2.bg,
1415
+ borderTopWidth: 1,
1416
+ borderTopColor: MUSHI_BANNER_NEON2.border,
1417
+ zIndex: 99998,
1418
+ elevation: 8,
1419
+ paddingBottom: Platform4.OS === "ios" ? 0 : 0
1420
+ },
1421
+ main: {
1422
+ flex: 1,
1423
+ flexDirection: "row",
1424
+ alignItems: "center",
1425
+ gap: 8,
1426
+ paddingHorizontal: 12,
1427
+ paddingVertical: 8
1428
+ },
1429
+ eyebrow: {
1430
+ fontSize: 10,
1431
+ fontWeight: "700",
1432
+ letterSpacing: 1.2,
1433
+ color: MUSHI_BANNER_NEON2.fg
1434
+ },
1435
+ message: {
1436
+ flex: 1,
1437
+ fontSize: 12,
1438
+ fontWeight: "500",
1439
+ color: MUSHI_BANNER_NEON2.fg
1440
+ },
1441
+ cta: {
1442
+ fontSize: 12,
1443
+ fontWeight: "700",
1444
+ color: MUSHI_BANNER_NEON2.fg,
1445
+ textDecorationLine: "underline"
1446
+ },
1447
+ dismiss: {
1448
+ justifyContent: "center",
1449
+ paddingHorizontal: 12
1450
+ },
1451
+ dismissText: {
1452
+ fontSize: 20,
1453
+ lineHeight: 22,
1454
+ color: MUSHI_BANNER_NEON2.fg,
1455
+ opacity: 0.7
1456
+ }
1457
+ });
1458
+
1222
1459
  // src/version.ts
1223
1460
  var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
1224
- var MUSHI_SDK_VERSION = "0.19.2" ? "0.19.2" : "0.17.0";
1461
+ var MUSHI_SDK_VERSION = "0.20.0" ? "0.20.0" : "0.17.0";
1225
1462
 
1226
1463
  // src/provider.tsx
1227
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1464
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1228
1465
  var MushiContext = createContext(null);
1229
1466
  var DEFAULT_SCREENSHOT_HINT = "Check the preview \u2014 remove it if any private info (balances, personal details) is visible.";
1230
1467
  function resolveScreenshotHint(v) {
@@ -1273,6 +1510,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1273
1510
  createBreadcrumbBuffer({ max: config.capture?.maxBreadcrumbs ?? 50 })
1274
1511
  );
1275
1512
  const [sheetVisible, setSheetVisible] = useState2(false);
1513
+ const [sheetPreferredTab, setSheetPreferredTab] = useState2("report");
1514
+ const [bannerDismissed, setBannerDismissed] = useState2(false);
1276
1515
  const [sheetScreenshot, setSheetScreenshot] = useState2(null);
1277
1516
  const reporterTokenRef = useRef3(`rn-${config.projectId}-anon`);
1278
1517
  let resolveReporterTokenReady = () => {
@@ -1370,7 +1609,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1370
1609
  }
1371
1610
  return () => unsubscribe?.();
1372
1611
  }, []);
1373
- const open = useCallback3(() => {
1612
+ const open = useCallback4(() => {
1613
+ setSheetPreferredTab("report");
1374
1614
  breadcrumbsRef.current.add({
1375
1615
  category: "lifecycle",
1376
1616
  level: "info",
@@ -1400,8 +1640,14 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1400
1640
  setSheetVisible(true);
1401
1641
  });
1402
1642
  }, [config.capture?.screenshot]);
1403
- const close = useCallback3(() => setSheetVisible(false), []);
1404
- const attachTo = useCallback3(() => ({ onPress: open }), [open]);
1643
+ const openAssistant = useCallback4(() => {
1644
+ if (!config.assistant?.enabled) return;
1645
+ setSheetPreferredTab("assistant");
1646
+ setSheetScreenshot(null);
1647
+ setSheetVisible(true);
1648
+ }, [config.assistant?.enabled]);
1649
+ const close = useCallback4(() => setSheetVisible(false), []);
1650
+ const attachTo = useCallback4(() => ({ onPress: open }), [open]);
1405
1651
  useEffect3(() => {
1406
1652
  const trigger2 = config.widget?.trigger ?? "button";
1407
1653
  if (trigger2 !== "shake" && trigger2 !== "both") return;
@@ -1430,8 +1676,9 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1430
1676
  cleanup?.();
1431
1677
  };
1432
1678
  }, [config.widget?.trigger, config.widget?.shakeThreshold, open]);
1433
- const submitReport = useCallback3(
1679
+ const submitReport = useCallback4(
1434
1680
  async (data) => {
1681
+ await reporterTokenReadyRef.current;
1435
1682
  const deviceInfo = getDeviceInfo();
1436
1683
  breadcrumbsRef.current.add({
1437
1684
  category: "lifecycle",
@@ -1675,15 +1922,36 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1675
1922
  confirmedBugs: 0,
1676
1923
  totalReports: 0
1677
1924
  };
1925
+ },
1926
+ openAssistant,
1927
+ async askAssistant(message, threadId) {
1928
+ const client = apiClientRef.current;
1929
+ if (!client || !config.assistant?.enabled) return null;
1930
+ const res = await client.askAssistant({
1931
+ message,
1932
+ threadId: threadId ?? null,
1933
+ context: pageContextRef.current
1934
+ });
1935
+ return res.ok ? res.data : null;
1678
1936
  }
1679
1937
  }),
1680
- [open, close, attachTo, submitReport, config.rewards?.enabled]
1938
+ [open, close, attachTo, submitReport, openAssistant, config.rewards?.enabled, config.assistant?.enabled]
1681
1939
  );
1682
1940
  const trigger = config.widget?.trigger ?? "button";
1683
- const showFab = trigger === "button" || trigger === "both" || trigger === "auto" || trigger === "edge-tab";
1684
- return /* @__PURE__ */ jsxs2(MushiContext.Provider, { value: instance, children: [
1941
+ const showBanner = trigger === "banner" && !bannerDismissed;
1942
+ const showFab = !showBanner && (trigger === "button" || trigger === "both" || trigger === "auto" || trigger === "edge-tab");
1943
+ return /* @__PURE__ */ jsxs3(MushiContext.Provider, { value: instance, children: [
1685
1944
  children,
1686
- showFab && /* @__PURE__ */ jsx3(
1945
+ showBanner ? /* @__PURE__ */ jsx4(
1946
+ MushiBanner,
1947
+ {
1948
+ onPress: open,
1949
+ onDismiss: () => setBannerDismissed(true),
1950
+ message: config.widget?.banner?.message,
1951
+ label: config.widget?.banner?.label
1952
+ }
1953
+ ) : null,
1954
+ showFab && /* @__PURE__ */ jsx4(
1687
1955
  MushiFloatingButton,
1688
1956
  {
1689
1957
  onPress: open,
@@ -1691,14 +1959,20 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1691
1959
  inset: config.widget?.inset
1692
1960
  }
1693
1961
  ),
1694
- /* @__PURE__ */ jsx3(
1962
+ /* @__PURE__ */ jsx4(
1695
1963
  MushiBottomSheet,
1696
1964
  {
1697
1965
  visible: sheetVisible,
1698
1966
  onClose: close,
1967
+ preferredTab: sheetPreferredTab,
1699
1968
  screenshotDataUrl: sheetScreenshot ?? void 0,
1700
1969
  onClearScreenshot: () => setSheetScreenshot(null),
1701
- screenshotSensitiveHint: resolveScreenshotHint(config.widget?.screenshotSensitiveHint)
1970
+ screenshotSensitiveHint: resolveScreenshotHint(config.widget?.screenshotSensitiveHint),
1971
+ assistantEnabled: Boolean(config.assistant?.enabled),
1972
+ assistantLabel: config.assistant?.label,
1973
+ assistantGreeting: config.assistant?.greeting,
1974
+ assistantSuggestions: config.assistant?.suggestions,
1975
+ inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0
1702
1976
  }
1703
1977
  )
1704
1978
  ] });
@@ -1713,10 +1987,10 @@ function useMushi() {
1713
1987
  }
1714
1988
 
1715
1989
  // src/hooks/useMushiReport.ts
1716
- import { useCallback as useCallback4 } from "react";
1990
+ import { useCallback as useCallback5 } from "react";
1717
1991
  function useMushiReport() {
1718
1992
  const mushi = useMushiContext();
1719
- const submitReport = useCallback4(
1993
+ const submitReport = useCallback5(
1720
1994
  async (data) => {
1721
1995
  if (!mushi) throw new Error("MushiProvider not found");
1722
1996
  await mushi.submitReport(data);
@@ -1727,12 +2001,12 @@ function useMushiReport() {
1727
2001
  }
1728
2002
 
1729
2003
  // src/hooks/useMushiWidget.ts
1730
- import { useCallback as useCallback5 } from "react";
2004
+ import { useCallback as useCallback6 } from "react";
1731
2005
  function useMushiWidget() {
1732
2006
  const mushi = useMushiContext();
1733
2007
  return {
1734
- open: useCallback5(() => mushi?.open(), [mushi]),
1735
- close: useCallback5(() => mushi?.close(), [mushi])
2008
+ open: useCallback6(() => mushi?.open(), [mushi]),
2009
+ close: useCallback6(() => mushi?.close(), [mushi])
1736
2010
  };
1737
2011
  }
1738
2012
 
@@ -1769,6 +2043,8 @@ function useNavigationCapture(navigationRef) {
1769
2043
  }
1770
2044
  export {
1771
2045
  AsyncStorageQueue,
2046
+ MUSHI_BANNER_DEFAULT_HEIGHT,
2047
+ MushiBanner,
1772
2048
  MushiBottomSheet,
1773
2049
  MushiFloatingButton,
1774
2050
  MushiProvider,