@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.cjs CHANGED
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AsyncStorageQueue: () => AsyncStorageQueue,
34
+ MUSHI_BANNER_DEFAULT_HEIGHT: () => MUSHI_BANNER_DEFAULT_HEIGHT,
35
+ MushiBanner: () => MushiBanner,
34
36
  MushiBottomSheet: () => MushiBottomSheet,
35
37
  MushiFloatingButton: () => MushiFloatingButton,
36
38
  MushiProvider: () => MushiProvider,
@@ -46,8 +48,8 @@ __export(index_exports, {
46
48
  module.exports = __toCommonJS(index_exports);
47
49
 
48
50
  // src/provider.tsx
49
- var import_react3 = require("react");
50
- var import_core5 = require("@mushi-mushi/core");
51
+ var import_react4 = require("react");
52
+ var import_core6 = require("@mushi-mushi/core");
51
53
 
52
54
  // src/capture/console-capture.ts
53
55
  var import_core = require("@mushi-mushi/core");
@@ -476,32 +478,64 @@ function sleep(ms) {
476
478
  var import_react = require("react");
477
479
  var import_react_native2 = require("react-native");
478
480
  var import_core3 = require("@mushi-mushi/core");
481
+ var import_i18n = require("@mushi-mushi/web/i18n");
482
+
483
+ // src/reporter-status.ts
484
+ function reporterStatusShort(status) {
485
+ switch (status) {
486
+ case "new":
487
+ case "queued":
488
+ case "pending":
489
+ case "submitted":
490
+ return "Received";
491
+ case "classified":
492
+ case "triaged":
493
+ case "grouped":
494
+ case "dispatched":
495
+ return "In review";
496
+ case "fixing":
497
+ return "Fixing";
498
+ case "fixed":
499
+ case "resolved":
500
+ case "completed":
501
+ return "Fixed";
502
+ case "dismissed":
503
+ return "Closed";
504
+ default:
505
+ return status.replace(/_/g, " ").slice(0, 16);
506
+ }
507
+ }
508
+
509
+ // src/components/MushiBottomSheet.tsx
479
510
  var import_jsx_runtime = require("react/jsx-runtime");
480
511
  var { height: SCREEN_HEIGHT } = import_react_native2.Dimensions.get("window");
481
512
  var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
482
513
  var DISMISS_THRESHOLD = 80;
483
- var NEON = {
484
- accent: "#0FFF50",
485
- accentHover: "#00E646",
486
- ink: "#0a1a0a",
487
- border: "#00C43A"
514
+ var CATEGORY_KEYS = ["bug", "slow", "visual", "confusing", "other"];
515
+ var CATEGORY_EMOJI = {
516
+ bug: "\u{1F41B}",
517
+ slow: "\u{1F422}",
518
+ visual: "\u{1F3A8}",
519
+ confusing: "\u{1F615}",
520
+ other: "\u{1F4AC}"
488
521
  };
489
- var CATEGORIES = [
490
- { key: "bug", emoji: "\u{1F41B}", label: "Bug" },
491
- { key: "slow", emoji: "\u{1F422}", label: "Slow" },
492
- { key: "visual", emoji: "\u{1F3A8}", label: "Visual" },
493
- { key: "confusing", emoji: "\u{1F615}", label: "Confusing" },
494
- { key: "other", emoji: "\u{1F4AC}", label: "Other" }
495
- ];
496
522
  var VERIFIABLE_STATUSES = /* @__PURE__ */ new Set(["fixed", "resolved", "verified"]);
497
523
  var MushiBottomSheet = ({
498
524
  visible,
499
525
  onClose,
526
+ preferredTab = "report",
500
527
  screenshotDataUrl,
501
528
  onClearScreenshot,
502
- screenshotSensitiveHint
529
+ screenshotSensitiveHint,
530
+ assistantEnabled = false,
531
+ assistantLabel = import_core3.MUSHI_COPY.assistantTab,
532
+ assistantGreeting,
533
+ assistantSuggestions = [],
534
+ inboxPollIntervalMs = 0
503
535
  }) => {
504
536
  const mushi = useMushiContext();
537
+ const t = (0, import_i18n.getLocale)();
538
+ const greeting = assistantGreeting ?? t.assistant.defaultGreeting;
505
539
  const scheme = (0, import_react_native2.useColorScheme)();
506
540
  const dark = scheme === "dark";
507
541
  const translateY = (0, import_react.useRef)(new import_react_native2.Animated.Value(SHEET_HEIGHT)).current;
@@ -515,6 +549,11 @@ var MushiBottomSheet = ({
515
549
  const [selectedReportId, setSelectedReportId] = (0, import_react.useState)(null);
516
550
  const [threadComments, setThreadComments] = (0, import_react.useState)([]);
517
551
  const [replyText, setReplyText] = (0, import_react.useState)("");
552
+ const [assistantInput, setAssistantInput] = (0, import_react.useState)("");
553
+ const [assistantThreadId, setAssistantThreadId] = (0, import_react.useState)(null);
554
+ const [assistantSending, setAssistantSending] = (0, import_react.useState)(false);
555
+ const [assistantError, setAssistantError] = (0, import_react.useState)(null);
556
+ const [assistantTurns, setAssistantTurns] = (0, import_react.useState)([]);
518
557
  const [screenshotAttached, setScreenshotAttached] = (0, import_react.useState)(true);
519
558
  const loadInbox = (0, import_react.useCallback)(async () => {
520
559
  if (!mushi?.listMyReports) return;
@@ -529,9 +568,17 @@ var MushiBottomSheet = ({
529
568
  (0, import_react.useEffect)(() => {
530
569
  if (visible) {
531
570
  setScreenshotAttached(true);
532
- if (sheetTab === "inbox") void loadInbox();
571
+ setSheetTab(preferredTab);
572
+ if (preferredTab === "inbox") void loadInbox();
533
573
  }
534
- }, [visible, sheetTab, loadInbox]);
574
+ }, [visible, preferredTab, loadInbox]);
575
+ (0, import_react.useEffect)(() => {
576
+ if (!visible || sheetTab !== "inbox" || inboxPollIntervalMs <= 0) return;
577
+ const timer = setInterval(() => {
578
+ void loadInbox();
579
+ }, inboxPollIntervalMs);
580
+ return () => clearInterval(timer);
581
+ }, [visible, sheetTab, inboxPollIntervalMs, loadInbox]);
535
582
  const openThread = (0, import_react.useCallback)(async (reportId) => {
536
583
  setSelectedReportId(reportId);
537
584
  if (!mushi?.listMyComments) return;
@@ -550,6 +597,29 @@ var MushiBottomSheet = ({
550
597
  await loadInbox();
551
598
  await openThread(selectedReportId);
552
599
  }, [mushi, selectedReportId, loadInbox, openThread]);
600
+ const sendAssistant = (0, import_react.useCallback)(async (message) => {
601
+ if (!mushi?.askAssistant || !message.trim() || assistantSending) return;
602
+ const trimmed = message.trim();
603
+ setAssistantInput("");
604
+ setAssistantError(null);
605
+ setAssistantTurns((prev) => [...prev, { role: "user", text: trimmed }]);
606
+ setAssistantSending(true);
607
+ try {
608
+ const reply = await mushi.askAssistant(trimmed, assistantThreadId);
609
+ if (!reply) {
610
+ setAssistantError("Could not reach the assistant. Try again.");
611
+ return;
612
+ }
613
+ if (reply.threadId) setAssistantThreadId(reply.threadId);
614
+ const botText = reply.kind === "clarify" ? reply.question ?? reply.text ?? "" : reply.text ?? "";
615
+ setAssistantTurns((prev) => [
616
+ ...prev,
617
+ { role: "bot", text: botText, options: reply.options }
618
+ ]);
619
+ } finally {
620
+ setAssistantSending(false);
621
+ }
622
+ }, [mushi, assistantSending, assistantThreadId]);
553
623
  const resetForm = (0, import_react.useCallback)(() => {
554
624
  setCategory(null);
555
625
  setDescription("");
@@ -634,7 +704,8 @@ var MushiBottomSheet = ({
634
704
  };
635
705
  const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
636
706
  const pal = (0, import_core3.mushiPalette)(dark ? "dark" : "light");
637
- 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 };
707
+ const colors = dark ? { bg: pal.paper, text: pal.ink, sub: pal.inkMuted, card: pal.paperRaised, accent: import_core3.MUSHI_BANNER_NEON.bg, accentInk: import_core3.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: import_core3.MUSHI_BANNER_NEON.bg, accentInk: import_core3.MUSHI_BANNER_NEON.fg, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.35)", disabled: "#d1d1d6", disabledText: pal.inkFaint };
708
+ const sheetTabs = ["report", "inbox", ...assistantEnabled ? ["assistant"] : []];
638
709
  const canSubmit = !!category && description.trim().length > 0 && phase === "form";
639
710
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
640
711
  import_react_native2.KeyboardAvoidingView,
@@ -659,11 +730,11 @@ var MushiBottomSheet = ({
659
730
  ],
660
731
  children: [
661
732
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
662
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.brandHeader, { backgroundColor: NEON.accent, borderBottomColor: NEON.border }], children: [
663
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.brandEyebrow, { color: NEON.ink }], children: "MUSHI \xB7 BETA" }),
664
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.brandTitle, { color: NEON.ink }], children: sheetTab === "inbox" ? "Your reports" : sheetTab === "community" ? "Community" : "Report an issue" })
733
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.brandHeader, { backgroundColor: import_core3.MUSHI_BANNER_NEON.bg, borderBottomColor: import_core3.MUSHI_BANNER_NEON.border }], children: [
734
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.brandEyebrow, { color: import_core3.MUSHI_BANNER_NEON.fg }], children: "MUSHI \xB7 BETA" }),
735
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.brandTitle, { color: import_core3.MUSHI_BANNER_NEON.fg }], children: sheetTab === "assistant" ? assistantLabel : sheetTab === "inbox" ? t.flows.reports.title : t.widget.title })
665
736
  ] }),
666
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.tabRow, children: ["report", "inbox", "community"].map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
737
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.tabRow, children: sheetTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
667
738
  import_react_native2.TouchableOpacity,
668
739
  {
669
740
  onPress: () => {
@@ -672,12 +743,68 @@ var MushiBottomSheet = ({
672
743
  else setSelectedReportId(null);
673
744
  },
674
745
  style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
675
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.text : colors.sub }], children: tab === "report" ? "Report" : tab === "inbox" ? "Your reports" : "Community" })
746
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.text : colors.sub }], children: tab === "report" ? t.widget.trigger : tab === "inbox" ? t.flows.reports.title : assistantLabel })
676
747
  },
677
748
  tab
678
749
  )) }),
679
- sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: inboxLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub }, children: "Loading\u2026" }) : selectedReportId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
680
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.accent, marginBottom: 8 }, children: "\u2190 Back" }) }),
750
+ sheetTab === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
751
+ assistantTurns.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
752
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, marginBottom: 12, lineHeight: 20 }, children: greeting }),
753
+ assistantSuggestions.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
754
+ import_react_native2.TouchableOpacity,
755
+ {
756
+ style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.card }],
757
+ onPress: () => void sendAssistant(chip),
758
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text, fontSize: 13 }, children: chip })
759
+ },
760
+ chip
761
+ ))
762
+ ] }) : assistantTurns.map((turn, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
763
+ import_react_native2.View,
764
+ {
765
+ style: [
766
+ s.assistantBubble,
767
+ {
768
+ alignSelf: turn.role === "user" ? "flex-end" : "flex-start",
769
+ backgroundColor: turn.role === "user" ? colors.accent : colors.card
770
+ }
771
+ ],
772
+ children: [
773
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: turn.role === "user" ? colors.accentInk : colors.text }, children: turn.text }),
774
+ turn.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { onPress: () => void sendAssistant(opt), style: { marginTop: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.accent, fontSize: 12 }, children: opt }) }, opt))
775
+ ]
776
+ },
777
+ `${turn.role}-${idx}`
778
+ )),
779
+ assistantSending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, marginTop: 8 }, accessibilityLiveRegion: "polite", children: t.assistant.thinking }) : null,
780
+ assistantError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: pal.danger, marginTop: 8 }, accessibilityRole: "alert", children: assistantError }) : null,
781
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.assistantComposer, children: [
782
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
783
+ import_react_native2.TextInput,
784
+ {
785
+ style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.card, color: colors.text, borderColor: colors.border }],
786
+ placeholder: t.assistant.inputPlaceholder,
787
+ placeholderTextColor: colors.sub,
788
+ value: assistantInput,
789
+ onChangeText: setAssistantInput,
790
+ editable: !assistantSending
791
+ }
792
+ ),
793
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
794
+ import_react_native2.TouchableOpacity,
795
+ {
796
+ style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16 }],
797
+ onPress: () => void sendAssistant(assistantInput),
798
+ disabled: !assistantInput.trim() || assistantSending,
799
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: "\u2191" })
800
+ }
801
+ )
802
+ ] })
803
+ ] }) : sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: inboxLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub }, children: t.flows.reports.loading }) : selectedReportId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
804
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.Text, { style: { color: colors.accent, marginBottom: 8 }, children: [
805
+ "\u2190 ",
806
+ t.widget.back
807
+ ] }) }),
681
808
  threadComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
682
809
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
683
810
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text }, children: c.body })
@@ -685,42 +812,26 @@ var MushiBottomSheet = ({
685
812
  VERIFIABLE_STATUSES.has(
686
813
  inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
687
814
  ) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.verifyRow, children: [
688
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: "Yes, fixed" }) }),
689
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.text }], children: "Not fixed" }) })
815
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: t.flows.thread.confirmFixed }) }),
816
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.text }], children: t.flows.thread.notFixed }) })
690
817
  ] }),
691
818
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
692
819
  import_react_native2.TextInput,
693
820
  {
694
821
  style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
695
- placeholder: "Reply\u2026",
822
+ placeholder: t.flows.thread.replyPlaceholder,
696
823
  placeholderTextColor: colors.sub,
697
824
  value: replyText,
698
825
  onChangeText: setReplyText
699
826
  }
700
827
  ),
701
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: "Send" }) })
828
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: t.flows.thread.send }) })
702
829
  ] }) : inboxReports.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
703
830
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
704
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: r.status })
705
- ] }, r.id)) }) : sheetTab === "community" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
706
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.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." }),
707
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
708
- import_react_native2.TextInput,
709
- {
710
- style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
711
- placeholder: "you@example.com",
712
- placeholderTextColor: colors.sub,
713
- keyboardType: "email-address",
714
- autoCapitalize: "none",
715
- autoCorrect: false,
716
- textContentType: "emailAddress"
717
- }
718
- ),
719
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: "Send sign-in link \u2192" }) }),
720
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.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." })
721
- ] }) : phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, children: [
831
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: reporterStatusShort(r.status) })
832
+ ] }, r.id)) }) : phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, children: [
722
833
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentEmoji], children: "\u2705" }),
723
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
834
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.text }], children: t.widget.submitted })
724
835
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
725
836
  import_react_native2.ScrollView,
726
837
  {
@@ -728,13 +839,13 @@ var MushiBottomSheet = ({
728
839
  keyboardShouldPersistTaps: "handled",
729
840
  contentContainerStyle: { paddingBottom: 24 },
730
841
  children: [
731
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.stepLabel, { color: colors.sub }], children: "What kind of issue?" }),
732
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.catRow, children: CATEGORIES.map((c) => {
733
- const active = category === c.key;
842
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.stepLabel, { color: colors.sub }], children: t.step1.heading }),
843
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.catRow, children: CATEGORY_KEYS.map((key) => {
844
+ const active = category === key;
734
845
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
735
846
  import_react_native2.TouchableOpacity,
736
847
  {
737
- onPress: () => setCategory(c.key),
848
+ onPress: () => setCategory(key),
738
849
  activeOpacity: 0.7,
739
850
  style: [
740
851
  s.catBtn,
@@ -744,7 +855,7 @@ var MushiBottomSheet = ({
744
855
  }
745
856
  ],
746
857
  children: [
747
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.catEmoji, children: c.emoji }),
858
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.catEmoji, children: CATEGORY_EMOJI[key] }),
748
859
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
749
860
  import_react_native2.Text,
750
861
  {
@@ -752,12 +863,12 @@ var MushiBottomSheet = ({
752
863
  s.catLabel,
753
864
  { color: active ? colors.accentInk : colors.text }
754
865
  ],
755
- children: c.label
866
+ children: t.step1.categories[key]
756
867
  }
757
868
  )
758
869
  ]
759
870
  },
760
- c.key
871
+ key
761
872
  );
762
873
  }) }),
763
874
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -771,7 +882,7 @@ var MushiBottomSheet = ({
771
882
  borderColor: colors.border
772
883
  }
773
884
  ],
774
- placeholder: "What happened?",
885
+ placeholder: t.step3.descriptionPlaceholder,
775
886
  placeholderTextColor: colors.sub,
776
887
  multiline: true,
777
888
  textAlignVertical: "top",
@@ -786,12 +897,12 @@ var MushiBottomSheet = ({
786
897
  {
787
898
  source: { uri: activeScreenshot },
788
899
  style: s.screenshotThumb,
789
- accessibilityLabel: "Attached screenshot"
900
+ accessibilityLabel: t.step3.screenshotPreviewAlt
790
901
  }
791
902
  ),
792
903
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotMeta, children: [
793
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
794
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotSub, { color: colors.sub }], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : "Helps the team see the issue" })
904
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.text }], children: t.step3.screenshotAttached.replace(" \u2713", "") }),
905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotSub, { color: colors.sub }], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : t.step3.screenshotSensitiveHint.split("\u2014")[0]?.trim() ?? t.step3.screenshotSensitiveHint })
795
906
  ] }),
796
907
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
797
908
  import_react_native2.TouchableOpacity,
@@ -801,7 +912,7 @@ var MushiBottomSheet = ({
801
912
  onClearScreenshot?.();
802
913
  },
803
914
  hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
804
- accessibilityLabel: "Remove screenshot",
915
+ accessibilityLabel: t.widget.close,
805
916
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
806
917
  }
807
918
  )
@@ -816,7 +927,7 @@ var MushiBottomSheet = ({
816
927
  s.submitBtn,
817
928
  { backgroundColor: canSubmit ? colors.accent : colors.disabled }
818
929
  ],
819
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: canSubmit ? colors.accentInk : colors.disabledText }], children: phase === "sending" ? "Sending\u2026" : "Submit report" })
930
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: canSubmit ? colors.accentInk : colors.disabledText }], children: phase === "sending" ? t.widget.submitting : t.widget.submit })
820
931
  }
821
932
  )
822
933
  ]
@@ -1022,6 +1133,27 @@ var s = import_react_native2.StyleSheet.create({
1022
1133
  borderRadius: 10,
1023
1134
  paddingVertical: 10,
1024
1135
  alignItems: "center"
1136
+ },
1137
+ assistantBubble: {
1138
+ maxWidth: "88%",
1139
+ borderRadius: 12,
1140
+ padding: 12,
1141
+ marginBottom: 10
1142
+ },
1143
+ assistantChip: {
1144
+ borderWidth: 1,
1145
+ borderRadius: 999,
1146
+ paddingHorizontal: 12,
1147
+ paddingVertical: 8,
1148
+ marginBottom: 8,
1149
+ alignSelf: "flex-start"
1150
+ },
1151
+ assistantComposer: {
1152
+ flexDirection: "row",
1153
+ alignItems: "flex-end",
1154
+ gap: 8,
1155
+ marginTop: 12,
1156
+ paddingBottom: 8
1025
1157
  }
1026
1158
  });
1027
1159
 
@@ -1030,7 +1162,7 @@ var import_react2 = require("react");
1030
1162
  var import_react_native3 = require("react-native");
1031
1163
  var import_core4 = require("@mushi-mushi/core");
1032
1164
  var import_jsx_runtime2 = require("react/jsx-runtime");
1033
- var FAB_SIZE = 56;
1165
+ var FAB_SIZE = import_core4.MUSHI_GEOMETRY.fabSize;
1034
1166
  var EDGE_PADDING = 12;
1035
1167
  function getScreenBounds() {
1036
1168
  const { width, height } = import_react_native3.Dimensions.get("window");
@@ -1213,13 +1345,114 @@ var s2 = import_react_native3.StyleSheet.create({
1213
1345
  }
1214
1346
  });
1215
1347
 
1348
+ // src/components/MushiBanner.tsx
1349
+ var import_react3 = require("react");
1350
+ var import_react_native4 = require("react-native");
1351
+ var import_core5 = require("@mushi-mushi/core");
1352
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1353
+ var MUSHI_BANNER_DEFAULT_HEIGHT = import_core5.MUSHI_GEOMETRY.bannerHeight;
1354
+ var MushiBanner = ({
1355
+ onPress,
1356
+ onDismiss,
1357
+ message = import_core5.MUSHI_COPY.bannerMessage,
1358
+ label = import_core5.MUSHI_COPY.bannerLabel,
1359
+ onLayoutHeight
1360
+ }) => {
1361
+ const handleLayout = (0, import_react3.useCallback)(
1362
+ (e) => {
1363
+ onLayoutHeight?.(e.nativeEvent.layout.height);
1364
+ },
1365
+ [onLayoutHeight]
1366
+ );
1367
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1368
+ import_react_native4.View,
1369
+ {
1370
+ style: s3.wrap,
1371
+ onLayout: handleLayout,
1372
+ accessibilityRole: "header",
1373
+ accessibilityLabel: "Mushi feedback banner",
1374
+ children: [
1375
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.TouchableOpacity, { style: s3.main, onPress, activeOpacity: 0.85, children: [
1376
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.eyebrow, children: label.toUpperCase() }),
1377
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.message, numberOfLines: 2, children: message }),
1378
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.cta, children: import_core5.MUSHI_COPY.bugCta })
1379
+ ] }),
1380
+ onDismiss ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1381
+ import_react_native4.TouchableOpacity,
1382
+ {
1383
+ style: s3.dismiss,
1384
+ onPress: onDismiss,
1385
+ accessibilityRole: "button",
1386
+ accessibilityLabel: "Dismiss banner",
1387
+ hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
1388
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.dismissText, children: "\xD7" })
1389
+ }
1390
+ ) : null
1391
+ ]
1392
+ }
1393
+ );
1394
+ };
1395
+ var s3 = import_react_native4.StyleSheet.create({
1396
+ wrap: {
1397
+ position: "absolute",
1398
+ left: 0,
1399
+ right: 0,
1400
+ bottom: 0,
1401
+ minHeight: import_core5.MUSHI_GEOMETRY.bannerHeight,
1402
+ flexDirection: "row",
1403
+ alignItems: "stretch",
1404
+ backgroundColor: import_core5.MUSHI_BANNER_NEON.bg,
1405
+ borderTopWidth: 1,
1406
+ borderTopColor: import_core5.MUSHI_BANNER_NEON.border,
1407
+ zIndex: 99998,
1408
+ elevation: 8,
1409
+ paddingBottom: import_react_native4.Platform.OS === "ios" ? 0 : 0
1410
+ },
1411
+ main: {
1412
+ flex: 1,
1413
+ flexDirection: "row",
1414
+ alignItems: "center",
1415
+ gap: 8,
1416
+ paddingHorizontal: 12,
1417
+ paddingVertical: 8
1418
+ },
1419
+ eyebrow: {
1420
+ fontSize: 10,
1421
+ fontWeight: "700",
1422
+ letterSpacing: 1.2,
1423
+ color: import_core5.MUSHI_BANNER_NEON.fg
1424
+ },
1425
+ message: {
1426
+ flex: 1,
1427
+ fontSize: 12,
1428
+ fontWeight: "500",
1429
+ color: import_core5.MUSHI_BANNER_NEON.fg
1430
+ },
1431
+ cta: {
1432
+ fontSize: 12,
1433
+ fontWeight: "700",
1434
+ color: import_core5.MUSHI_BANNER_NEON.fg,
1435
+ textDecorationLine: "underline"
1436
+ },
1437
+ dismiss: {
1438
+ justifyContent: "center",
1439
+ paddingHorizontal: 12
1440
+ },
1441
+ dismissText: {
1442
+ fontSize: 20,
1443
+ lineHeight: 22,
1444
+ color: import_core5.MUSHI_BANNER_NEON.fg,
1445
+ opacity: 0.7
1446
+ }
1447
+ });
1448
+
1216
1449
  // src/version.ts
1217
1450
  var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
1218
- var MUSHI_SDK_VERSION = "0.19.2" ? "0.19.2" : "0.17.0";
1451
+ var MUSHI_SDK_VERSION = "0.20.0" ? "0.20.0" : "0.17.0";
1219
1452
 
1220
1453
  // src/provider.tsx
1221
- var import_jsx_runtime3 = require("react/jsx-runtime");
1222
- var MushiContext = (0, import_react3.createContext)(null);
1454
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1455
+ var MushiContext = (0, import_react4.createContext)(null);
1223
1456
  var DEFAULT_SCREENSHOT_HINT = "Check the preview \u2014 remove it if any private info (balances, personal details) is visible.";
1224
1457
  function resolveScreenshotHint(v) {
1225
1458
  if (v === false) return null;
@@ -1258,26 +1491,28 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1258
1491
  );
1259
1492
  }
1260
1493
  }
1261
- const consoleRef = (0, import_react3.useRef)(null);
1262
- const networkRef = (0, import_react3.useRef)(null);
1263
- const queueRef = (0, import_react3.useRef)(null);
1264
- const apiClientRef = (0, import_react3.useRef)(null);
1265
- const sessionIdRef = (0, import_react3.useRef)(`sess_${(0, import_core5.newUuid)()}`);
1266
- const breadcrumbsRef = (0, import_react3.useRef)(
1267
- (0, import_core5.createBreadcrumbBuffer)({ max: config.capture?.maxBreadcrumbs ?? 50 })
1494
+ const consoleRef = (0, import_react4.useRef)(null);
1495
+ const networkRef = (0, import_react4.useRef)(null);
1496
+ const queueRef = (0, import_react4.useRef)(null);
1497
+ const apiClientRef = (0, import_react4.useRef)(null);
1498
+ const sessionIdRef = (0, import_react4.useRef)(`sess_${(0, import_core6.newUuid)()}`);
1499
+ const breadcrumbsRef = (0, import_react4.useRef)(
1500
+ (0, import_core6.createBreadcrumbBuffer)({ max: config.capture?.maxBreadcrumbs ?? 50 })
1268
1501
  );
1269
- const [sheetVisible, setSheetVisible] = (0, import_react3.useState)(false);
1270
- const [sheetScreenshot, setSheetScreenshot] = (0, import_react3.useState)(null);
1271
- const reporterTokenRef = (0, import_react3.useRef)(`rn-${config.projectId}-anon`);
1502
+ const [sheetVisible, setSheetVisible] = (0, import_react4.useState)(false);
1503
+ const [sheetPreferredTab, setSheetPreferredTab] = (0, import_react4.useState)("report");
1504
+ const [bannerDismissed, setBannerDismissed] = (0, import_react4.useState)(false);
1505
+ const [sheetScreenshot, setSheetScreenshot] = (0, import_react4.useState)(null);
1506
+ const reporterTokenRef = (0, import_react4.useRef)(`rn-${config.projectId}-anon`);
1272
1507
  let resolveReporterTokenReady = () => {
1273
1508
  };
1274
- const reporterTokenReadyRef = (0, import_react3.useRef)(
1509
+ const reporterTokenReadyRef = (0, import_react4.useRef)(
1275
1510
  new Promise((resolve) => {
1276
1511
  resolveReporterTokenReady = resolve;
1277
1512
  })
1278
1513
  );
1279
- const userTokenRef = (0, import_react3.useRef)(null);
1280
- const pageContextRef = (0, import_react3.useRef)(null);
1514
+ const userTokenRef = (0, import_react4.useRef)(null);
1515
+ const pageContextRef = (0, import_react4.useRef)(null);
1281
1516
  const rawEndpoint = config.apiEndpoint ?? config.endpoint;
1282
1517
  if (rawEndpoint !== void 0 && rawEndpoint.trim() === "") {
1283
1518
  throw new Error(
@@ -1287,15 +1522,15 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1287
1522
  if (config.endpoint && !config.apiEndpoint && __DEV__) {
1288
1523
  console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
1289
1524
  }
1290
- const apiEndpoint = rawEndpoint ?? import_core5.DEFAULT_API_ENDPOINT;
1291
- (0, import_react3.useEffect)(() => {
1525
+ const apiEndpoint = rawEndpoint ?? import_core6.DEFAULT_API_ENDPOINT;
1526
+ (0, import_react4.useEffect)(() => {
1292
1527
  if (config.capture?.console !== false) {
1293
1528
  consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
1294
1529
  }
1295
1530
  if (config.capture?.network !== false) {
1296
1531
  networkRef.current = setupNetworkCapture(config.capture?.maxNetworkEntries, apiEndpoint);
1297
1532
  }
1298
- apiClientRef.current = (0, import_core5.createApiClient)({
1533
+ apiClientRef.current = (0, import_core6.createApiClient)({
1299
1534
  projectId: config.projectId,
1300
1535
  apiKey: config.apiKey,
1301
1536
  apiEndpoint,
@@ -1332,14 +1567,14 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1332
1567
  if (existing && existing.startsWith("mushi_")) {
1333
1568
  reporterTokenRef.current = existing;
1334
1569
  } else {
1335
- const fresh = `mushi_${(0, import_core5.newUuid)()}`;
1570
+ const fresh = `mushi_${(0, import_core6.newUuid)()}`;
1336
1571
  reporterTokenRef.current = fresh;
1337
1572
  await saveReporterToken(fresh, secureStorage);
1338
1573
  }
1339
1574
  resolveReporterTokenReady();
1340
1575
  } catch {
1341
1576
  if (reporterTokenRef.current === `rn-${config.projectId}-anon`) {
1342
- reporterTokenRef.current = `mushi_${(0, import_core5.newUuid)()}`;
1577
+ reporterTokenRef.current = `mushi_${(0, import_core6.newUuid)()}`;
1343
1578
  }
1344
1579
  resolveReporterTokenReady();
1345
1580
  }
@@ -1350,7 +1585,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1350
1585
  networkRef.current?.restore();
1351
1586
  };
1352
1587
  }, []);
1353
- (0, import_react3.useEffect)(() => {
1588
+ (0, import_react4.useEffect)(() => {
1354
1589
  let unsubscribe;
1355
1590
  try {
1356
1591
  const NetInfo = require("@react-native-community/netinfo").default;
@@ -1364,7 +1599,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1364
1599
  }
1365
1600
  return () => unsubscribe?.();
1366
1601
  }, []);
1367
- const open = (0, import_react3.useCallback)(() => {
1602
+ const open = (0, import_react4.useCallback)(() => {
1603
+ setSheetPreferredTab("report");
1368
1604
  breadcrumbsRef.current.add({
1369
1605
  category: "lifecycle",
1370
1606
  level: "info",
@@ -1394,9 +1630,15 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1394
1630
  setSheetVisible(true);
1395
1631
  });
1396
1632
  }, [config.capture?.screenshot]);
1397
- const close = (0, import_react3.useCallback)(() => setSheetVisible(false), []);
1398
- const attachTo = (0, import_react3.useCallback)(() => ({ onPress: open }), [open]);
1399
- (0, import_react3.useEffect)(() => {
1633
+ const openAssistant = (0, import_react4.useCallback)(() => {
1634
+ if (!config.assistant?.enabled) return;
1635
+ setSheetPreferredTab("assistant");
1636
+ setSheetScreenshot(null);
1637
+ setSheetVisible(true);
1638
+ }, [config.assistant?.enabled]);
1639
+ const close = (0, import_react4.useCallback)(() => setSheetVisible(false), []);
1640
+ const attachTo = (0, import_react4.useCallback)(() => ({ onPress: open }), [open]);
1641
+ (0, import_react4.useEffect)(() => {
1400
1642
  const trigger2 = config.widget?.trigger ?? "button";
1401
1643
  if (trigger2 !== "shake" && trigger2 !== "both") return;
1402
1644
  let disposed = false;
@@ -1424,8 +1666,9 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1424
1666
  cleanup?.();
1425
1667
  };
1426
1668
  }, [config.widget?.trigger, config.widget?.shakeThreshold, open]);
1427
- const submitReport = (0, import_react3.useCallback)(
1669
+ const submitReport = (0, import_react4.useCallback)(
1428
1670
  async (data) => {
1671
+ await reporterTokenReadyRef.current;
1429
1672
  const deviceInfo = getDeviceInfo();
1430
1673
  breadcrumbsRef.current.add({
1431
1674
  category: "lifecycle",
@@ -1451,7 +1694,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1451
1694
  const merged = Object.keys(metadataRef.current).length > 0 ? { ...contextMeta, ...metadataRef.current } : Object.keys(contextMeta).length > 0 ? contextMeta : void 0;
1452
1695
  const breadcrumbs = breadcrumbsRef.current.getAll().map((c) => ({
1453
1696
  ...c,
1454
- message: (0, import_core5.scrubPii)(c.message)
1697
+ message: (0, import_core6.scrubPii)(c.message)
1455
1698
  }));
1456
1699
  const timeline = breadcrumbs.map((c) => ({
1457
1700
  ts: c.timestamp,
@@ -1460,10 +1703,10 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1460
1703
  }));
1461
1704
  const fingerprintHash = getDeviceFingerprintHash(deviceInfo);
1462
1705
  const report = {
1463
- id: (0, import_core5.newUuid)(),
1706
+ id: (0, import_core6.newUuid)(),
1464
1707
  projectId: config.projectId,
1465
1708
  category: data.category,
1466
- description: (0, import_core5.scrubPii)(data.description),
1709
+ description: (0, import_core6.scrubPii)(data.description),
1467
1710
  environment: {
1468
1711
  userAgent: deviceInfo.systemName ?? "ReactNative",
1469
1712
  platform: deviceInfo.platform ?? "mobile",
@@ -1476,7 +1719,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1476
1719
  },
1477
1720
  consoleLogs: (consoleRef.current?.getEntries() ?? []).map((entry) => ({
1478
1721
  ...entry,
1479
- message: (0, import_core5.scrubPii)(entry.message)
1722
+ message: (0, import_core6.scrubPii)(entry.message)
1480
1723
  })),
1481
1724
  networkLogs: networkRef.current?.getEntries().map((entry) => ({
1482
1725
  ...entry,
@@ -1500,7 +1743,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1500
1743
  if (!result.ok) {
1501
1744
  const isCredentialError = result.error?.code === "HTTP_401" || result.error?.code === "HTTP_403" || result.error?.code?.includes("UNAUTHORIZED") || result.error?.code?.includes("FORBIDDEN");
1502
1745
  if (isCredentialError) {
1503
- const where = apiEndpoint.replace(/\/$/, "") === import_core5.DEFAULT_API_ENDPOINT ? "Get the correct values at: https://kensaur.us/mushi-mushi/admin/projects" : "Get the correct values from your Mushi console's Projects page.";
1746
+ const where = apiEndpoint.replace(/\/$/, "") === import_core6.DEFAULT_API_ENDPOINT ? "Get the correct values at: https://kensaur.us/mushi-mushi/admin/projects" : "Get the correct values from your Mushi console's Projects page.";
1504
1747
  console.error(
1505
1748
  '[Mushi] Credentials rejected. Check your projectId and apiKey (must have "report:write" scope). ' + where
1506
1749
  );
@@ -1514,12 +1757,12 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1514
1757
  },
1515
1758
  [config.projectId, config.callbacks, apiEndpoint]
1516
1759
  );
1517
- const userRef = (0, import_react3.useRef)(null);
1518
- const metadataRef = (0, import_react3.useRef)({});
1519
- const screenRef = (0, import_react3.useRef)(null);
1520
- const activityQueueRef = (0, import_react3.useRef)([]);
1521
- const rewardsFlushRef = (0, import_react3.useRef)(null);
1522
- (0, import_react3.useEffect)(() => {
1760
+ const userRef = (0, import_react4.useRef)(null);
1761
+ const metadataRef = (0, import_react4.useRef)({});
1762
+ const screenRef = (0, import_react4.useRef)(null);
1763
+ const activityQueueRef = (0, import_react4.useRef)([]);
1764
+ const rewardsFlushRef = (0, import_react4.useRef)(null);
1765
+ (0, import_react4.useEffect)(() => {
1523
1766
  const rewards = config.rewards;
1524
1767
  if (!rewards?.enabled || !rewards.trackActivity) return;
1525
1768
  const flushMs = Math.max(3e4, rewards.flushIntervalMs ?? 3e5);
@@ -1539,7 +1782,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1539
1782
  if (rewardsFlushRef.current) clearInterval(rewardsFlushRef.current);
1540
1783
  };
1541
1784
  }, [config.rewards?.enabled, config.rewards?.trackActivity, config.rewards?.flushIntervalMs]);
1542
- const instance = (0, import_react3.useMemo)(
1785
+ const instance = (0, import_react4.useMemo)(
1543
1786
  () => ({
1544
1787
  open,
1545
1788
  close,
@@ -1568,7 +1811,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1568
1811
  identifyWithToken(token) {
1569
1812
  userTokenRef.current = token && typeof token === "string" ? token : null;
1570
1813
  if (userTokenRef.current) {
1571
- const claims = (0, import_core5.parseIdentityToken)(userTokenRef.current);
1814
+ const claims = (0, import_core6.parseIdentityToken)(userTokenRef.current);
1572
1815
  if (claims?.sub) {
1573
1816
  userRef.current = {
1574
1817
  id: claims.sub,
@@ -1669,15 +1912,36 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1669
1912
  confirmedBugs: 0,
1670
1913
  totalReports: 0
1671
1914
  };
1915
+ },
1916
+ openAssistant,
1917
+ async askAssistant(message, threadId) {
1918
+ const client = apiClientRef.current;
1919
+ if (!client || !config.assistant?.enabled) return null;
1920
+ const res = await client.askAssistant({
1921
+ message,
1922
+ threadId: threadId ?? null,
1923
+ context: pageContextRef.current
1924
+ });
1925
+ return res.ok ? res.data : null;
1672
1926
  }
1673
1927
  }),
1674
- [open, close, attachTo, submitReport, config.rewards?.enabled]
1928
+ [open, close, attachTo, submitReport, openAssistant, config.rewards?.enabled, config.assistant?.enabled]
1675
1929
  );
1676
1930
  const trigger = config.widget?.trigger ?? "button";
1677
- const showFab = trigger === "button" || trigger === "both" || trigger === "auto" || trigger === "edge-tab";
1678
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MushiContext.Provider, { value: instance, children: [
1931
+ const showBanner = trigger === "banner" && !bannerDismissed;
1932
+ const showFab = !showBanner && (trigger === "button" || trigger === "both" || trigger === "auto" || trigger === "edge-tab");
1933
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(MushiContext.Provider, { value: instance, children: [
1679
1934
  children,
1680
- showFab && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1935
+ showBanner ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1936
+ MushiBanner,
1937
+ {
1938
+ onPress: open,
1939
+ onDismiss: () => setBannerDismissed(true),
1940
+ message: config.widget?.banner?.message,
1941
+ label: config.widget?.banner?.label
1942
+ }
1943
+ ) : null,
1944
+ showFab && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1681
1945
  MushiFloatingButton,
1682
1946
  {
1683
1947
  onPress: open,
@@ -1685,20 +1949,26 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1685
1949
  inset: config.widget?.inset
1686
1950
  }
1687
1951
  ),
1688
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1952
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1689
1953
  MushiBottomSheet,
1690
1954
  {
1691
1955
  visible: sheetVisible,
1692
1956
  onClose: close,
1957
+ preferredTab: sheetPreferredTab,
1693
1958
  screenshotDataUrl: sheetScreenshot ?? void 0,
1694
1959
  onClearScreenshot: () => setSheetScreenshot(null),
1695
- screenshotSensitiveHint: resolveScreenshotHint(config.widget?.screenshotSensitiveHint)
1960
+ screenshotSensitiveHint: resolveScreenshotHint(config.widget?.screenshotSensitiveHint),
1961
+ assistantEnabled: Boolean(config.assistant?.enabled),
1962
+ assistantLabel: config.assistant?.label,
1963
+ assistantGreeting: config.assistant?.greeting,
1964
+ assistantSuggestions: config.assistant?.suggestions,
1965
+ inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0
1696
1966
  }
1697
1967
  )
1698
1968
  ] });
1699
1969
  }
1700
1970
  function useMushiContext() {
1701
- return (0, import_react3.useContext)(MushiContext);
1971
+ return (0, import_react4.useContext)(MushiContext);
1702
1972
  }
1703
1973
 
1704
1974
  // src/hooks/useMushi.ts
@@ -1707,10 +1977,10 @@ function useMushi() {
1707
1977
  }
1708
1978
 
1709
1979
  // src/hooks/useMushiReport.ts
1710
- var import_react4 = require("react");
1980
+ var import_react5 = require("react");
1711
1981
  function useMushiReport() {
1712
1982
  const mushi = useMushiContext();
1713
- const submitReport = (0, import_react4.useCallback)(
1983
+ const submitReport = (0, import_react5.useCallback)(
1714
1984
  async (data) => {
1715
1985
  if (!mushi) throw new Error("MushiProvider not found");
1716
1986
  await mushi.submitReport(data);
@@ -1721,22 +1991,22 @@ function useMushiReport() {
1721
1991
  }
1722
1992
 
1723
1993
  // src/hooks/useMushiWidget.ts
1724
- var import_react5 = require("react");
1994
+ var import_react6 = require("react");
1725
1995
  function useMushiWidget() {
1726
1996
  const mushi = useMushiContext();
1727
1997
  return {
1728
- open: (0, import_react5.useCallback)(() => mushi?.open(), [mushi]),
1729
- close: (0, import_react5.useCallback)(() => mushi?.close(), [mushi])
1998
+ open: (0, import_react6.useCallback)(() => mushi?.open(), [mushi]),
1999
+ close: (0, import_react6.useCallback)(() => mushi?.close(), [mushi])
1730
2000
  };
1731
2001
  }
1732
2002
 
1733
2003
  // src/components/MushiTrigger.tsx
1734
- var import_react6 = require("react");
2004
+ var import_react7 = require("react");
1735
2005
  function MushiTrigger({ children }) {
1736
2006
  const mushi = useMushiContext();
1737
- if (!(0, import_react6.isValidElement)(children)) return children;
2007
+ if (!(0, import_react7.isValidElement)(children)) return children;
1738
2008
  const originalOnPress = children.props.onPress;
1739
- return (0, import_react6.cloneElement)(children, {
2009
+ return (0, import_react7.cloneElement)(children, {
1740
2010
  onPress: () => {
1741
2011
  originalOnPress?.();
1742
2012
  mushi?.open();
@@ -1745,10 +2015,10 @@ function MushiTrigger({ children }) {
1745
2015
  }
1746
2016
 
1747
2017
  // src/capture/navigation-capture.ts
1748
- var import_react7 = require("react");
2018
+ var import_react8 = require("react");
1749
2019
  function useNavigationCapture(navigationRef) {
1750
- const historyRef = (0, import_react7.useRef)([]);
1751
- (0, import_react7.useEffect)(() => {
2020
+ const historyRef = (0, import_react8.useRef)([]);
2021
+ (0, import_react8.useEffect)(() => {
1752
2022
  if (!navigationRef?.addListener || !navigationRef?.getCurrentRoute) return;
1753
2023
  const unsubscribe = navigationRef.addListener("state", () => {
1754
2024
  const route = navigationRef.getCurrentRoute?.();
@@ -1764,6 +2034,8 @@ function useNavigationCapture(navigationRef) {
1764
2034
  // Annotate the CommonJS export names for ESM import in node:
1765
2035
  0 && (module.exports = {
1766
2036
  AsyncStorageQueue,
2037
+ MUSHI_BANNER_DEFAULT_HEIGHT,
2038
+ MushiBanner,
1767
2039
  MushiBottomSheet,
1768
2040
  MushiFloatingButton,
1769
2041
  MushiProvider,