@mushi-mushi/react-native 0.19.1 → 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");
@@ -263,9 +265,8 @@ async function encryptQueueBlob(plaintext, secureStorage) {
263
265
  combined.set(new Uint8Array(cipher), iv.length);
264
266
  return ENCRYPTED_PREFIX + bytesToBase64(combined);
265
267
  }
266
- async function decryptQueueBlob(stored, secureStorage) {
268
+ async function decryptQueueBlob(stored, _secureStorage) {
267
269
  if (!stored.startsWith(ENCRYPTED_PREFIX)) return stored;
268
- if (!secureStorage) return stored;
269
270
  const secure = await loadSecureStore();
270
271
  if (!secure || !hasWebCrypto()) return stored;
271
272
  const key = await getOrCreateQueueKey(secure);
@@ -477,32 +478,64 @@ function sleep(ms) {
477
478
  var import_react = require("react");
478
479
  var import_react_native2 = require("react-native");
479
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
480
510
  var import_jsx_runtime = require("react/jsx-runtime");
481
511
  var { height: SCREEN_HEIGHT } = import_react_native2.Dimensions.get("window");
482
512
  var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
483
513
  var DISMISS_THRESHOLD = 80;
484
- var NEON = {
485
- accent: "#0FFF50",
486
- accentHover: "#00E646",
487
- ink: "#0a1a0a",
488
- 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}"
489
521
  };
490
- var CATEGORIES = [
491
- { key: "bug", emoji: "\u{1F41B}", label: "Bug" },
492
- { key: "slow", emoji: "\u{1F422}", label: "Slow" },
493
- { key: "visual", emoji: "\u{1F3A8}", label: "Visual" },
494
- { key: "confusing", emoji: "\u{1F615}", label: "Confusing" },
495
- { key: "other", emoji: "\u{1F4AC}", label: "Other" }
496
- ];
497
522
  var VERIFIABLE_STATUSES = /* @__PURE__ */ new Set(["fixed", "resolved", "verified"]);
498
523
  var MushiBottomSheet = ({
499
524
  visible,
500
525
  onClose,
526
+ preferredTab = "report",
501
527
  screenshotDataUrl,
502
528
  onClearScreenshot,
503
- screenshotSensitiveHint
529
+ screenshotSensitiveHint,
530
+ assistantEnabled = false,
531
+ assistantLabel = import_core3.MUSHI_COPY.assistantTab,
532
+ assistantGreeting,
533
+ assistantSuggestions = [],
534
+ inboxPollIntervalMs = 0
504
535
  }) => {
505
536
  const mushi = useMushiContext();
537
+ const t = (0, import_i18n.getLocale)();
538
+ const greeting = assistantGreeting ?? t.assistant.defaultGreeting;
506
539
  const scheme = (0, import_react_native2.useColorScheme)();
507
540
  const dark = scheme === "dark";
508
541
  const translateY = (0, import_react.useRef)(new import_react_native2.Animated.Value(SHEET_HEIGHT)).current;
@@ -516,6 +549,11 @@ var MushiBottomSheet = ({
516
549
  const [selectedReportId, setSelectedReportId] = (0, import_react.useState)(null);
517
550
  const [threadComments, setThreadComments] = (0, import_react.useState)([]);
518
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)([]);
519
557
  const [screenshotAttached, setScreenshotAttached] = (0, import_react.useState)(true);
520
558
  const loadInbox = (0, import_react.useCallback)(async () => {
521
559
  if (!mushi?.listMyReports) return;
@@ -530,9 +568,17 @@ var MushiBottomSheet = ({
530
568
  (0, import_react.useEffect)(() => {
531
569
  if (visible) {
532
570
  setScreenshotAttached(true);
533
- if (sheetTab === "inbox") void loadInbox();
571
+ setSheetTab(preferredTab);
572
+ if (preferredTab === "inbox") void loadInbox();
534
573
  }
535
- }, [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]);
536
582
  const openThread = (0, import_react.useCallback)(async (reportId) => {
537
583
  setSelectedReportId(reportId);
538
584
  if (!mushi?.listMyComments) return;
@@ -551,6 +597,29 @@ var MushiBottomSheet = ({
551
597
  await loadInbox();
552
598
  await openThread(selectedReportId);
553
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]);
554
623
  const resetForm = (0, import_react.useCallback)(() => {
555
624
  setCategory(null);
556
625
  setDescription("");
@@ -635,7 +704,8 @@ var MushiBottomSheet = ({
635
704
  };
636
705
  const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
637
706
  const pal = (0, import_core3.mushiPalette)(dark ? "dark" : "light");
638
- 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"] : []];
639
709
  const canSubmit = !!category && description.trim().length > 0 && phase === "form";
640
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)(
641
711
  import_react_native2.KeyboardAvoidingView,
@@ -660,11 +730,11 @@ var MushiBottomSheet = ({
660
730
  ],
661
731
  children: [
662
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 }] }) }),
663
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.brandHeader, { backgroundColor: NEON.accent, borderBottomColor: NEON.border }], children: [
664
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.brandEyebrow, { color: NEON.ink }], children: "MUSHI \xB7 BETA" }),
665
- /* @__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 })
666
736
  ] }),
667
- /* @__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)(
668
738
  import_react_native2.TouchableOpacity,
669
739
  {
670
740
  onPress: () => {
@@ -673,12 +743,68 @@ var MushiBottomSheet = ({
673
743
  else setSelectedReportId(null);
674
744
  },
675
745
  style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
676
- 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 })
677
747
  },
678
748
  tab
679
749
  )) }),
680
- 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: [
681
- /* @__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
+ ] }) }),
682
808
  threadComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
683
809
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
684
810
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text }, children: c.body })
@@ -686,42 +812,26 @@ var MushiBottomSheet = ({
686
812
  VERIFIABLE_STATUSES.has(
687
813
  inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
688
814
  ) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.verifyRow, children: [
689
- /* @__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" }) }),
690
- /* @__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 }) })
691
817
  ] }),
692
818
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
693
819
  import_react_native2.TextInput,
694
820
  {
695
821
  style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
696
- placeholder: "Reply\u2026",
822
+ placeholder: t.flows.thread.replyPlaceholder,
697
823
  placeholderTextColor: colors.sub,
698
824
  value: replyText,
699
825
  onChangeText: setReplyText
700
826
  }
701
827
  ),
702
- /* @__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 }) })
703
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: [
704
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) }),
705
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: r.status })
706
- ] }, r.id)) }) : sheetTab === "community" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
707
- /* @__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." }),
708
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
709
- import_react_native2.TextInput,
710
- {
711
- style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
712
- placeholder: "you@example.com",
713
- placeholderTextColor: colors.sub,
714
- keyboardType: "email-address",
715
- autoCapitalize: "none",
716
- autoCorrect: false,
717
- textContentType: "emailAddress"
718
- }
719
- ),
720
- /* @__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" }) }),
721
- /* @__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." })
722
- ] }) : 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: [
723
833
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentEmoji], children: "\u2705" }),
724
- /* @__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 })
725
835
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
726
836
  import_react_native2.ScrollView,
727
837
  {
@@ -729,13 +839,13 @@ var MushiBottomSheet = ({
729
839
  keyboardShouldPersistTaps: "handled",
730
840
  contentContainerStyle: { paddingBottom: 24 },
731
841
  children: [
732
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.stepLabel, { color: colors.sub }], children: "What kind of issue?" }),
733
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.catRow, children: CATEGORIES.map((c) => {
734
- 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;
735
845
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
736
846
  import_react_native2.TouchableOpacity,
737
847
  {
738
- onPress: () => setCategory(c.key),
848
+ onPress: () => setCategory(key),
739
849
  activeOpacity: 0.7,
740
850
  style: [
741
851
  s.catBtn,
@@ -745,7 +855,7 @@ var MushiBottomSheet = ({
745
855
  }
746
856
  ],
747
857
  children: [
748
- /* @__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] }),
749
859
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
750
860
  import_react_native2.Text,
751
861
  {
@@ -753,12 +863,12 @@ var MushiBottomSheet = ({
753
863
  s.catLabel,
754
864
  { color: active ? colors.accentInk : colors.text }
755
865
  ],
756
- children: c.label
866
+ children: t.step1.categories[key]
757
867
  }
758
868
  )
759
869
  ]
760
870
  },
761
- c.key
871
+ key
762
872
  );
763
873
  }) }),
764
874
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -772,7 +882,7 @@ var MushiBottomSheet = ({
772
882
  borderColor: colors.border
773
883
  }
774
884
  ],
775
- placeholder: "What happened?",
885
+ placeholder: t.step3.descriptionPlaceholder,
776
886
  placeholderTextColor: colors.sub,
777
887
  multiline: true,
778
888
  textAlignVertical: "top",
@@ -787,12 +897,12 @@ var MushiBottomSheet = ({
787
897
  {
788
898
  source: { uri: activeScreenshot },
789
899
  style: s.screenshotThumb,
790
- accessibilityLabel: "Attached screenshot"
900
+ accessibilityLabel: t.step3.screenshotPreviewAlt
791
901
  }
792
902
  ),
793
903
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotMeta, children: [
794
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
795
- /* @__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 })
796
906
  ] }),
797
907
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
798
908
  import_react_native2.TouchableOpacity,
@@ -802,7 +912,7 @@ var MushiBottomSheet = ({
802
912
  onClearScreenshot?.();
803
913
  },
804
914
  hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
805
- accessibilityLabel: "Remove screenshot",
915
+ accessibilityLabel: t.widget.close,
806
916
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
807
917
  }
808
918
  )
@@ -817,7 +927,7 @@ var MushiBottomSheet = ({
817
927
  s.submitBtn,
818
928
  { backgroundColor: canSubmit ? colors.accent : colors.disabled }
819
929
  ],
820
- 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 })
821
931
  }
822
932
  )
823
933
  ]
@@ -1023,6 +1133,27 @@ var s = import_react_native2.StyleSheet.create({
1023
1133
  borderRadius: 10,
1024
1134
  paddingVertical: 10,
1025
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
1026
1157
  }
1027
1158
  });
1028
1159
 
@@ -1031,7 +1162,7 @@ var import_react2 = require("react");
1031
1162
  var import_react_native3 = require("react-native");
1032
1163
  var import_core4 = require("@mushi-mushi/core");
1033
1164
  var import_jsx_runtime2 = require("react/jsx-runtime");
1034
- var FAB_SIZE = 56;
1165
+ var FAB_SIZE = import_core4.MUSHI_GEOMETRY.fabSize;
1035
1166
  var EDGE_PADDING = 12;
1036
1167
  function getScreenBounds() {
1037
1168
  const { width, height } = import_react_native3.Dimensions.get("window");
@@ -1214,13 +1345,114 @@ var s2 = import_react_native3.StyleSheet.create({
1214
1345
  }
1215
1346
  });
1216
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
+
1217
1449
  // src/version.ts
1218
1450
  var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
1219
- var MUSHI_SDK_VERSION = "0.19.1" ? "0.19.1" : "0.17.0";
1451
+ var MUSHI_SDK_VERSION = "0.20.0" ? "0.20.0" : "0.17.0";
1220
1452
 
1221
1453
  // src/provider.tsx
1222
- var import_jsx_runtime3 = require("react/jsx-runtime");
1223
- var MushiContext = (0, import_react3.createContext)(null);
1454
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1455
+ var MushiContext = (0, import_react4.createContext)(null);
1224
1456
  var DEFAULT_SCREENSHOT_HINT = "Check the preview \u2014 remove it if any private info (balances, personal details) is visible.";
1225
1457
  function resolveScreenshotHint(v) {
1226
1458
  if (v === false) return null;
@@ -1259,26 +1491,28 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1259
1491
  );
1260
1492
  }
1261
1493
  }
1262
- const consoleRef = (0, import_react3.useRef)(null);
1263
- const networkRef = (0, import_react3.useRef)(null);
1264
- const queueRef = (0, import_react3.useRef)(null);
1265
- const apiClientRef = (0, import_react3.useRef)(null);
1266
- const sessionIdRef = (0, import_react3.useRef)(`sess_${(0, import_core5.newUuid)()}`);
1267
- const breadcrumbsRef = (0, import_react3.useRef)(
1268
- (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 })
1269
1501
  );
1270
- const [sheetVisible, setSheetVisible] = (0, import_react3.useState)(false);
1271
- const [sheetScreenshot, setSheetScreenshot] = (0, import_react3.useState)(null);
1272
- 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`);
1273
1507
  let resolveReporterTokenReady = () => {
1274
1508
  };
1275
- const reporterTokenReadyRef = (0, import_react3.useRef)(
1509
+ const reporterTokenReadyRef = (0, import_react4.useRef)(
1276
1510
  new Promise((resolve) => {
1277
1511
  resolveReporterTokenReady = resolve;
1278
1512
  })
1279
1513
  );
1280
- const userTokenRef = (0, import_react3.useRef)(null);
1281
- const pageContextRef = (0, import_react3.useRef)(null);
1514
+ const userTokenRef = (0, import_react4.useRef)(null);
1515
+ const pageContextRef = (0, import_react4.useRef)(null);
1282
1516
  const rawEndpoint = config.apiEndpoint ?? config.endpoint;
1283
1517
  if (rawEndpoint !== void 0 && rawEndpoint.trim() === "") {
1284
1518
  throw new Error(
@@ -1288,15 +1522,15 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1288
1522
  if (config.endpoint && !config.apiEndpoint && __DEV__) {
1289
1523
  console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
1290
1524
  }
1291
- const apiEndpoint = rawEndpoint ?? import_core5.DEFAULT_API_ENDPOINT;
1292
- (0, import_react3.useEffect)(() => {
1525
+ const apiEndpoint = rawEndpoint ?? import_core6.DEFAULT_API_ENDPOINT;
1526
+ (0, import_react4.useEffect)(() => {
1293
1527
  if (config.capture?.console !== false) {
1294
1528
  consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
1295
1529
  }
1296
1530
  if (config.capture?.network !== false) {
1297
1531
  networkRef.current = setupNetworkCapture(config.capture?.maxNetworkEntries, apiEndpoint);
1298
1532
  }
1299
- apiClientRef.current = (0, import_core5.createApiClient)({
1533
+ apiClientRef.current = (0, import_core6.createApiClient)({
1300
1534
  projectId: config.projectId,
1301
1535
  apiKey: config.apiKey,
1302
1536
  apiEndpoint,
@@ -1333,14 +1567,14 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1333
1567
  if (existing && existing.startsWith("mushi_")) {
1334
1568
  reporterTokenRef.current = existing;
1335
1569
  } else {
1336
- const fresh = `mushi_${(0, import_core5.newUuid)()}`;
1570
+ const fresh = `mushi_${(0, import_core6.newUuid)()}`;
1337
1571
  reporterTokenRef.current = fresh;
1338
1572
  await saveReporterToken(fresh, secureStorage);
1339
1573
  }
1340
1574
  resolveReporterTokenReady();
1341
1575
  } catch {
1342
1576
  if (reporterTokenRef.current === `rn-${config.projectId}-anon`) {
1343
- reporterTokenRef.current = `mushi_${(0, import_core5.newUuid)()}`;
1577
+ reporterTokenRef.current = `mushi_${(0, import_core6.newUuid)()}`;
1344
1578
  }
1345
1579
  resolveReporterTokenReady();
1346
1580
  }
@@ -1351,7 +1585,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1351
1585
  networkRef.current?.restore();
1352
1586
  };
1353
1587
  }, []);
1354
- (0, import_react3.useEffect)(() => {
1588
+ (0, import_react4.useEffect)(() => {
1355
1589
  let unsubscribe;
1356
1590
  try {
1357
1591
  const NetInfo = require("@react-native-community/netinfo").default;
@@ -1365,7 +1599,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1365
1599
  }
1366
1600
  return () => unsubscribe?.();
1367
1601
  }, []);
1368
- const open = (0, import_react3.useCallback)(() => {
1602
+ const open = (0, import_react4.useCallback)(() => {
1603
+ setSheetPreferredTab("report");
1369
1604
  breadcrumbsRef.current.add({
1370
1605
  category: "lifecycle",
1371
1606
  level: "info",
@@ -1395,9 +1630,15 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1395
1630
  setSheetVisible(true);
1396
1631
  });
1397
1632
  }, [config.capture?.screenshot]);
1398
- const close = (0, import_react3.useCallback)(() => setSheetVisible(false), []);
1399
- const attachTo = (0, import_react3.useCallback)(() => ({ onPress: open }), [open]);
1400
- (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)(() => {
1401
1642
  const trigger2 = config.widget?.trigger ?? "button";
1402
1643
  if (trigger2 !== "shake" && trigger2 !== "both") return;
1403
1644
  let disposed = false;
@@ -1425,8 +1666,9 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1425
1666
  cleanup?.();
1426
1667
  };
1427
1668
  }, [config.widget?.trigger, config.widget?.shakeThreshold, open]);
1428
- const submitReport = (0, import_react3.useCallback)(
1669
+ const submitReport = (0, import_react4.useCallback)(
1429
1670
  async (data) => {
1671
+ await reporterTokenReadyRef.current;
1430
1672
  const deviceInfo = getDeviceInfo();
1431
1673
  breadcrumbsRef.current.add({
1432
1674
  category: "lifecycle",
@@ -1452,7 +1694,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1452
1694
  const merged = Object.keys(metadataRef.current).length > 0 ? { ...contextMeta, ...metadataRef.current } : Object.keys(contextMeta).length > 0 ? contextMeta : void 0;
1453
1695
  const breadcrumbs = breadcrumbsRef.current.getAll().map((c) => ({
1454
1696
  ...c,
1455
- message: (0, import_core5.scrubPii)(c.message)
1697
+ message: (0, import_core6.scrubPii)(c.message)
1456
1698
  }));
1457
1699
  const timeline = breadcrumbs.map((c) => ({
1458
1700
  ts: c.timestamp,
@@ -1461,10 +1703,10 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1461
1703
  }));
1462
1704
  const fingerprintHash = getDeviceFingerprintHash(deviceInfo);
1463
1705
  const report = {
1464
- id: (0, import_core5.newUuid)(),
1706
+ id: (0, import_core6.newUuid)(),
1465
1707
  projectId: config.projectId,
1466
1708
  category: data.category,
1467
- description: (0, import_core5.scrubPii)(data.description),
1709
+ description: (0, import_core6.scrubPii)(data.description),
1468
1710
  environment: {
1469
1711
  userAgent: deviceInfo.systemName ?? "ReactNative",
1470
1712
  platform: deviceInfo.platform ?? "mobile",
@@ -1477,7 +1719,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1477
1719
  },
1478
1720
  consoleLogs: (consoleRef.current?.getEntries() ?? []).map((entry) => ({
1479
1721
  ...entry,
1480
- message: (0, import_core5.scrubPii)(entry.message)
1722
+ message: (0, import_core6.scrubPii)(entry.message)
1481
1723
  })),
1482
1724
  networkLogs: networkRef.current?.getEntries().map((entry) => ({
1483
1725
  ...entry,
@@ -1501,8 +1743,9 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1501
1743
  if (!result.ok) {
1502
1744
  const isCredentialError = result.error?.code === "HTTP_401" || result.error?.code === "HTTP_403" || result.error?.code?.includes("UNAUTHORIZED") || result.error?.code?.includes("FORBIDDEN");
1503
1745
  if (isCredentialError) {
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
- '[Mushi] Credentials rejected. Check your projectId and apiKey (must have "report:write" scope). Get the correct values at: https://kensaur.us/mushi-mushi/admin/projects'
1748
+ '[Mushi] Credentials rejected. Check your projectId and apiKey (must have "report:write" scope). ' + where
1506
1749
  );
1507
1750
  return;
1508
1751
  }
@@ -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,