@mushi-mushi/react-native 0.23.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,15 +1,9 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
1
 
8
2
  // src/provider.tsx
9
3
  import {
10
4
  createContext,
11
5
  useContext,
12
- useEffect as useEffect3,
6
+ useEffect as useEffect4,
13
7
  useRef as useRef3,
14
8
  useState as useState2,
15
9
  useCallback as useCallback4,
@@ -24,6 +18,180 @@ import {
24
18
  parseIdentityToken,
25
19
  scrubPii as scrubPii3
26
20
  } from "@mushi-mushi/core";
21
+ import {
22
+ reporterChannels as reporterChannels2
23
+ } from "@mushi-mushi/core/reporter-channels";
24
+ import { reporterShouldShowToast, reporterToastMessage, resolveReporterLocale as resolveReporterLocale3 } from "@mushi-mushi/core/reporter-ui";
25
+
26
+ // src/storage/reporter-flags.ts
27
+ async function readReporterFlag(projectId, flag) {
28
+ try {
29
+ const store = (await import("@react-native-async-storage/async-storage")).default;
30
+ return await store.getItem(`@mushi:${flag}:${projectId}`);
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ async function writeReporterFlag(projectId, flag, value) {
36
+ try {
37
+ const store = (await import("@react-native-async-storage/async-storage")).default;
38
+ await store.setItem(`@mushi:${flag}:${projectId}`, value);
39
+ } catch {
40
+ }
41
+ }
42
+
43
+ // src/components/MushiUpdateToast.tsx
44
+ import { useEffect } from "react";
45
+ import { Text, TouchableOpacity, View, StyleSheet, useColorScheme } from "react-native";
46
+ import { MUSHI_SHADOW_INK } from "@mushi-mushi/core";
47
+ import { reporterCopy, resolveReporterLocale } from "@mushi-mushi/core/reporter-ui";
48
+
49
+ // src/theme.ts
50
+ import { mushiPalette, MUSHI_CONTROL_DISABLED, MUSHI_RADIUS } from "@mushi-mushi/core";
51
+ function hexToRgb(hex) {
52
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})([0-9a-f]{2})?$/i.exec(hex.trim());
53
+ if (!m) return null;
54
+ const h = m[1].length === 3 ? m[1].split("").map((c) => c + c).join("") : m[1];
55
+ return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
56
+ }
57
+ function luminance([r, g, b]) {
58
+ const lin = (c) => {
59
+ const s5 = c / 255;
60
+ return s5 <= 0.03928 ? s5 / 12.92 : ((s5 + 0.055) / 1.055) ** 2.4;
61
+ };
62
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
63
+ }
64
+ function contrastingText(bg, fallback) {
65
+ const rgb = hexToRgb(bg);
66
+ if (!rgb) return fallback;
67
+ const l = luminance(rgb);
68
+ return 1.05 / (l + 0.05) >= (l + 0.05) / 0.05 ? "#FFFFFF" : "#000000";
69
+ }
70
+ function resolveRNTheme(dark, override = {}) {
71
+ const pal = mushiPalette(dark ? "dark" : "light");
72
+ const base = {
73
+ bg: dark ? pal.paper : pal.paperRaised,
74
+ fg: pal.ink,
75
+ muted: pal.inkMuted,
76
+ surface: dark ? pal.paperRaised : pal.paper,
77
+ border: pal.ruleStrong,
78
+ accent: pal.ink,
79
+ accentFg: dark ? pal.paper : pal.paperRaised,
80
+ success: pal.ok,
81
+ error: pal.danger,
82
+ fontFamily: void 0,
83
+ radius: MUSHI_RADIUS.card
84
+ };
85
+ const defined = Object.fromEntries(
86
+ Object.entries(override).filter(([, v]) => v !== void 0 && v !== null && v !== "")
87
+ );
88
+ const merged = { ...base, ...defined };
89
+ if (defined.accent && !defined.accentFg) merged.accentFg = contrastingText(defined.accent, base.accentFg);
90
+ return {
91
+ ...merged,
92
+ backdrop: dark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.35)",
93
+ disabled: dark ? MUSHI_CONTROL_DISABLED.dark : MUSHI_CONTROL_DISABLED.light,
94
+ disabledFg: pal.inkFaint
95
+ };
96
+ }
97
+
98
+ // src/components/MushiUpdateToast.tsx
99
+ import { jsx, jsxs } from "react/jsx-runtime";
100
+ var AUTO_HIDE_MS = 8e3;
101
+ var MushiUpdateToast = ({ message, onView, onDismiss, theme }) => {
102
+ const colors = resolveRNTheme(useColorScheme() === "dark", theme);
103
+ const copy = reporterCopy(
104
+ resolveReporterLocale(typeof navigator !== "undefined" ? navigator.language : void 0)
105
+ ).ui;
106
+ const font = colors.fontFamily ? { fontFamily: colors.fontFamily } : null;
107
+ useEffect(() => {
108
+ const timer = setTimeout(onDismiss, AUTO_HIDE_MS);
109
+ return () => clearTimeout(timer);
110
+ }, [onDismiss]);
111
+ return /* @__PURE__ */ jsxs(
112
+ View,
113
+ {
114
+ style: [s.toast, { backgroundColor: colors.bg, borderColor: colors.border, borderRadius: colors.radius }],
115
+ accessibilityLiveRegion: "polite",
116
+ accessibilityRole: "alert",
117
+ children: [
118
+ /* @__PURE__ */ jsx(Text, { style: [s.text, { color: colors.fg }, font], numberOfLines: 2, children: message }),
119
+ /* @__PURE__ */ jsx(
120
+ TouchableOpacity,
121
+ {
122
+ onPress: onView,
123
+ accessibilityRole: "button",
124
+ style: [s.btn, { backgroundColor: colors.accent, borderRadius: colors.radius }],
125
+ children: /* @__PURE__ */ jsx(Text, { style: [{ color: colors.accentFg, fontWeight: "600" }, font], children: copy.view })
126
+ }
127
+ ),
128
+ /* @__PURE__ */ jsx(
129
+ TouchableOpacity,
130
+ {
131
+ onPress: onDismiss,
132
+ accessibilityRole: "button",
133
+ accessibilityLabel: copy.done,
134
+ hitSlop: { top: 10, bottom: 10, left: 10, right: 10 },
135
+ children: /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 18 }, font], children: "\xD7" })
136
+ }
137
+ )
138
+ ]
139
+ }
140
+ );
141
+ };
142
+ var s = StyleSheet.create({
143
+ toast: {
144
+ position: "absolute",
145
+ left: 16,
146
+ right: 16,
147
+ bottom: 32,
148
+ flexDirection: "row",
149
+ alignItems: "center",
150
+ gap: 10,
151
+ paddingVertical: 10,
152
+ paddingHorizontal: 14,
153
+ borderWidth: StyleSheet.hairlineWidth,
154
+ shadowColor: MUSHI_SHADOW_INK,
155
+ shadowOpacity: 0.18,
156
+ shadowRadius: 12,
157
+ shadowOffset: { width: 0, height: 6 },
158
+ elevation: 6
159
+ },
160
+ text: { flex: 1, fontSize: 14 },
161
+ btn: { paddingHorizontal: 12, paddingVertical: 6 }
162
+ });
163
+
164
+ // src/optional-modules.ts
165
+ function unwrap(mod, key) {
166
+ if (!mod || typeof mod !== "object" && typeof mod !== "function") return null;
167
+ if (key in mod) return mod;
168
+ const def = mod.default;
169
+ return def && (typeof def === "object" || typeof def === "function") && key in def ? def : null;
170
+ }
171
+ function resolveNetInfo(injected) {
172
+ if (injected) return unwrap(injected, "addEventListener");
173
+ try {
174
+ return unwrap(require("@react-native-community/netinfo"), "addEventListener");
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
179
+ function resolveViewShot(injected) {
180
+ if (injected) return unwrap(injected, "captureScreen");
181
+ try {
182
+ return unwrap(require("react-native-view-shot"), "captureScreen");
183
+ } catch {
184
+ return null;
185
+ }
186
+ }
187
+ function resolveExpoSensors(injected) {
188
+ if (injected) return unwrap(injected, "Accelerometer");
189
+ try {
190
+ return unwrap(require("expo-sensors"), "Accelerometer");
191
+ } catch {
192
+ return null;
193
+ }
194
+ }
27
195
 
28
196
  // src/capture/console-capture.ts
29
197
  import { scrubPii } from "@mushi-mushi/core";
@@ -456,86 +624,50 @@ function sleep(ms) {
456
624
  import {
457
625
  useRef,
458
626
  useState,
459
- useEffect,
627
+ useEffect as useEffect2,
460
628
  useCallback
461
629
  } from "react";
462
630
  import {
463
631
  Modal,
464
- View,
465
- Text,
632
+ View as View2,
633
+ Text as Text2,
466
634
  TextInput,
467
635
  Image,
468
- TouchableOpacity,
636
+ TouchableOpacity as TouchableOpacity2,
469
637
  Animated,
470
638
  PanResponder,
471
- StyleSheet,
639
+ StyleSheet as StyleSheet2,
472
640
  Dimensions as Dimensions2,
473
641
  Platform as Platform2,
474
642
  KeyboardAvoidingView,
475
643
  ScrollView,
476
- useColorScheme
644
+ AccessibilityInfo,
645
+ useColorScheme as useColorScheme2
477
646
  } from "react-native";
478
647
  import {
479
648
  MUSHI_COPY,
480
649
  MUSHI_INVERSE,
481
650
  MUSHI_RADIUS as MUSHI_RADIUS2,
482
- MUSHI_SHADOW_INK,
651
+ MUSHI_SHADOW_INK as MUSHI_SHADOW_INK2,
483
652
  MUSHI_SPACING,
484
653
  MUSHI_TYPE
485
654
  } from "@mushi-mushi/core";
486
- import { reporterCopy, reporterStatus, resolveReporterLocale } from "@mushi-mushi/core/reporter-ui";
655
+ import {
656
+ REPORTER_CATEGORIES,
657
+ isPlausibleReporterEmail,
658
+ isReporterConversation,
659
+ reporterCanSend,
660
+ reporterCategoryLabel,
661
+ reporterChipToReport,
662
+ reporterCopy as reporterCopy2,
663
+ reporterStatus,
664
+ reporterTimelineEntryText,
665
+ resolveReporterLocale as resolveReporterLocale2
666
+ } from "@mushi-mushi/core/reporter-ui";
487
667
  import { getLocale } from "@mushi-mushi/web/i18n";
488
668
 
489
- // src/theme.ts
490
- import { mushiPalette, MUSHI_CONTROL_DISABLED, MUSHI_RADIUS } from "@mushi-mushi/core";
491
- function hexToRgb(hex) {
492
- const m = /^#([0-9a-f]{3}|[0-9a-f]{6})([0-9a-f]{2})?$/i.exec(hex.trim());
493
- if (!m) return null;
494
- const h = m[1].length === 3 ? m[1].split("").map((c) => c + c).join("") : m[1];
495
- return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
496
- }
497
- function luminance([r, g, b]) {
498
- const lin = (c) => {
499
- const s4 = c / 255;
500
- return s4 <= 0.03928 ? s4 / 12.92 : ((s4 + 0.055) / 1.055) ** 2.4;
501
- };
502
- return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
503
- }
504
- function contrastingText(bg, fallback) {
505
- const rgb = hexToRgb(bg);
506
- if (!rgb) return fallback;
507
- const l = luminance(rgb);
508
- return 1.05 / (l + 0.05) >= (l + 0.05) / 0.05 ? "#FFFFFF" : "#000000";
509
- }
510
- function resolveRNTheme(dark, override = {}) {
511
- const pal = mushiPalette(dark ? "dark" : "light");
512
- const base = {
513
- bg: dark ? pal.paper : pal.paperRaised,
514
- fg: pal.ink,
515
- muted: pal.inkMuted,
516
- surface: dark ? pal.paperRaised : pal.paper,
517
- border: pal.ruleStrong,
518
- accent: pal.ink,
519
- accentFg: dark ? pal.paper : pal.paperRaised,
520
- success: pal.ok,
521
- error: pal.danger,
522
- fontFamily: void 0,
523
- radius: MUSHI_RADIUS.card
524
- };
525
- const defined = Object.fromEntries(
526
- Object.entries(override).filter(([, v]) => v !== void 0 && v !== null && v !== "")
527
- );
528
- const merged = { ...base, ...defined };
529
- if (defined.accent && !defined.accentFg) merged.accentFg = contrastingText(defined.accent, base.accentFg);
530
- return {
531
- ...merged,
532
- backdrop: dark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.35)",
533
- disabled: dark ? MUSHI_CONTROL_DISABLED.dark : MUSHI_CONTROL_DISABLED.light,
534
- disabledFg: pal.inkFaint
535
- };
536
- }
537
-
538
669
  // src/reporter-thread.ts
670
+ import { reporterChannels } from "@mushi-mushi/core/reporter-channels";
539
671
  var THREAD_LOAD_TIMEOUT_MS = 12e3;
540
672
  function settleWithin(p, ms) {
541
673
  return new Promise((resolve) => {
@@ -563,6 +695,9 @@ function loadReporterThread(load, reportId, timeoutMs = THREAD_LOAD_TIMEOUT_MS)
563
695
  }
564
696
  async function markReporterReportRead(client, reporterToken, reportId) {
565
697
  try {
698
+ const v2 = await reporterChannels(client).markReportRead(reportId, reporterToken);
699
+ if (v2.ok) return v2.data?.marked_read ?? 0;
700
+ if (v2.error?.status !== 404) return 0;
566
701
  const res = await client.listNotifications(reporterToken, { limit: 100 });
567
702
  if (!res.ok) return 0;
568
703
  const rows = res.data?.notifications ?? [];
@@ -577,21 +712,59 @@ async function markReporterReportRead(client, reporterToken, reportId) {
577
712
  return 0;
578
713
  }
579
714
  }
715
+ function createReporterUpdateHub(fetchUpdates) {
716
+ const listeners = /* @__PURE__ */ new Set();
717
+ async function emit() {
718
+ let updates = null;
719
+ try {
720
+ updates = await fetchUpdates();
721
+ } catch {
722
+ return null;
723
+ }
724
+ if (updates) {
725
+ for (const cb of listeners) {
726
+ try {
727
+ cb(updates);
728
+ } catch {
729
+ }
730
+ }
731
+ }
732
+ return updates;
733
+ }
734
+ return {
735
+ subscribe(cb) {
736
+ listeners.add(cb);
737
+ void emit();
738
+ return () => {
739
+ listeners.delete(cb);
740
+ };
741
+ },
742
+ emit,
743
+ listenerCount: () => listeners.size
744
+ };
745
+ }
580
746
 
581
747
  // src/components/MushiBottomSheet.tsx
582
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
748
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
583
749
  var { height: SCREEN_HEIGHT } = Dimensions2.get("window");
584
750
  var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
585
751
  var DISMISS_THRESHOLD = 80;
586
- var MIN_DESCRIPTION = 20;
587
- var CATEGORY_KEYS = ["bug", "slow", "visual", "confusing", "other"];
588
752
  var CATEGORY_EMOJI = {
589
753
  bug: "\u{1F41B}",
590
754
  slow: "\u{1F422}",
591
755
  visual: "\u{1F3A8}",
592
756
  confusing: "\u{1F615}",
593
- other: "\u{1F4AC}"
757
+ idea: "\u{1F4A1}"
594
758
  };
759
+ function commentsToTimeline(comments) {
760
+ return comments.map((c) => ({
761
+ kind: c.author_kind === "reporter" ? "reporter_comment" : "comment",
762
+ at: c.created_at,
763
+ text: c.body,
764
+ body: c.body,
765
+ comment_id: c.id
766
+ }));
767
+ }
595
768
  var MushiBottomSheet = ({
596
769
  visible,
597
770
  onClose,
@@ -604,16 +777,18 @@ var MushiBottomSheet = ({
604
777
  assistantGreeting,
605
778
  assistantSuggestions = [],
606
779
  inboxPollIntervalMs = 0,
607
- theme
780
+ theme,
781
+ minDescriptionLength,
782
+ emailOptIn = true
608
783
  }) => {
609
784
  const mushi = useMushiContext();
610
785
  const t = getLocale();
611
- const locale = resolveReporterLocale(
786
+ const locale = resolveReporterLocale2(
612
787
  typeof navigator !== "undefined" ? navigator.language : void 0
613
788
  );
614
- const rc = reporterCopy(locale);
789
+ const rc = reporterCopy2(locale);
615
790
  const greeting = assistantGreeting ?? t.assistant.defaultGreeting;
616
- const scheme = useColorScheme();
791
+ const scheme = useColorScheme2();
617
792
  const dark = scheme === "dark";
618
793
  const translateY = useRef(new Animated.Value(SHEET_HEIGHT)).current;
619
794
  const backdropOpacity = useRef(new Animated.Value(0)).current;
@@ -627,7 +802,7 @@ var MushiBottomSheet = ({
627
802
  const [inboxLoading, setInboxLoading] = useState(false);
628
803
  const [selectedReportId, setSelectedReportId] = useState(null);
629
804
  const selectedRef = useRef(null);
630
- const [threadComments, setThreadComments] = useState([]);
805
+ const [threadItems, setThreadItems] = useState([]);
631
806
  const [threadStatus, setThreadStatus] = useState("idle");
632
807
  const [replyText, setReplyText] = useState("");
633
808
  const [replyState, setReplyState] = useState("idle");
@@ -639,6 +814,18 @@ var MushiBottomSheet = ({
639
814
  const [assistantError, setAssistantError] = useState(null);
640
815
  const [assistantTurns, setAssistantTurns] = useState([]);
641
816
  const [screenshotAttached, setScreenshotAttached] = useState(true);
817
+ const [emailState, setEmailState] = useState("hidden");
818
+ const [emailInput, setEmailInput] = useState("");
819
+ const [reduceMotion, setReduceMotion] = useState(false);
820
+ useEffect2(() => {
821
+ let alive = true;
822
+ AccessibilityInfo?.isReduceMotionEnabled?.().then((on) => alive && setReduceMotion(Boolean(on))).catch(() => void 0);
823
+ const sub = AccessibilityInfo?.addEventListener?.("reduceMotionChanged", (on) => setReduceMotion(Boolean(on)));
824
+ return () => {
825
+ alive = false;
826
+ sub?.remove?.();
827
+ };
828
+ }, []);
642
829
  const loadInbox = useCallback(
643
830
  async (opts = {}) => {
644
831
  if (!mushi?.listMyReports) return;
@@ -654,14 +841,14 @@ var MushiBottomSheet = ({
654
841
  },
655
842
  [mushi, locale]
656
843
  );
657
- useEffect(() => {
844
+ useEffect2(() => {
658
845
  if (visible) {
659
846
  setScreenshotAttached(true);
660
847
  setSheetTab(preferredTab);
661
848
  if (preferredTab === "inbox") void loadInbox();
662
849
  }
663
850
  }, [visible, preferredTab, loadInbox]);
664
- useEffect(() => {
851
+ useEffect2(() => {
665
852
  if (!visible || sheetTab !== "inbox" || inboxPollIntervalMs <= 0) return;
666
853
  const timer = setInterval(() => {
667
854
  void loadInbox({ silent: true });
@@ -673,19 +860,26 @@ var MushiBottomSheet = ({
673
860
  selectedRef.current = reportId;
674
861
  setSelectedReportId(reportId);
675
862
  if (!opts.silent) {
676
- setThreadComments([]);
863
+ setThreadItems([]);
677
864
  setThreadStatus("loading");
678
865
  setReplyState("idle");
679
866
  setFeedbackError(false);
680
867
  }
681
- const load = mushi?.loadMyThread ?? mushi?.listMyComments;
682
- const comments = load ? await loadReporterThread(load, reportId) : null;
868
+ let items = null;
869
+ const detail = mushi?.loadMyReportDetail ? await settleWithin(mushi.loadMyReportDetail(reportId), THREAD_LOAD_TIMEOUT_MS) : "unsupported";
870
+ if (detail && detail !== "unsupported") {
871
+ items = detail.timeline;
872
+ } else if (detail === "unsupported") {
873
+ const load = mushi?.loadMyThread ?? mushi?.listMyComments;
874
+ const comments = load ? await loadReporterThread(load, reportId) : null;
875
+ items = comments ? commentsToTimeline(comments) : null;
876
+ }
683
877
  if (selectedRef.current !== reportId) return;
684
- if (comments === null) {
878
+ if (items === null) {
685
879
  setThreadStatus((prev) => opts.silent && prev === "ready" ? "ready" : "error");
686
880
  return;
687
881
  }
688
- setThreadComments(comments);
882
+ setThreadItems(items);
689
883
  setThreadStatus("ready");
690
884
  if (!opts.silent && mushi?.markReportRead) {
691
885
  mushi.markReportRead(reportId).then((marked) => {
@@ -716,7 +910,10 @@ var MushiBottomSheet = ({
716
910
  setReplyText("");
717
911
  setReplyState("idle");
718
912
  if (selectedRef.current === reportId) {
719
- setThreadComments((prev) => [...prev, comment]);
913
+ setThreadItems((prev) => [
914
+ ...prev,
915
+ { kind: "reporter_comment", at: comment.created_at, text: comment.body, body: comment.body, comment_id: comment.id }
916
+ ]);
720
917
  void openThread(reportId, { silent: true });
721
918
  }
722
919
  } catch {
@@ -774,8 +971,15 @@ var MushiBottomSheet = ({
774
971
  setCategory(null);
775
972
  setDescription("");
776
973
  setPhase("form");
974
+ setEmailState("hidden");
975
+ setEmailInput("");
777
976
  }, []);
778
977
  const animateIn = useCallback(() => {
978
+ if (reduceMotion) {
979
+ translateY.setValue(0);
980
+ backdropOpacity.setValue(1);
981
+ return;
982
+ }
779
983
  Animated.parallel([
780
984
  Animated.spring(translateY, {
781
985
  toValue: 0,
@@ -789,18 +993,18 @@ var MushiBottomSheet = ({
789
993
  useNativeDriver: true
790
994
  })
791
995
  ]).start();
792
- }, [translateY, backdropOpacity]);
996
+ }, [translateY, backdropOpacity, reduceMotion]);
793
997
  const animateOut = useCallback(
794
998
  (cb) => {
795
999
  Animated.parallel([
796
1000
  Animated.timing(translateY, {
797
1001
  toValue: SHEET_HEIGHT,
798
- duration: 220,
1002
+ duration: reduceMotion ? 0 : 220,
799
1003
  useNativeDriver: true
800
1004
  }),
801
1005
  Animated.timing(backdropOpacity, {
802
1006
  toValue: 0,
803
- duration: 200,
1007
+ duration: reduceMotion ? 0 : 200,
804
1008
  useNativeDriver: true
805
1009
  })
806
1010
  ]).start(() => {
@@ -808,9 +1012,9 @@ var MushiBottomSheet = ({
808
1012
  cb?.();
809
1013
  });
810
1014
  },
811
- [translateY, backdropOpacity, resetForm]
1015
+ [translateY, backdropOpacity, resetForm, reduceMotion]
812
1016
  );
813
- useEffect(() => {
1017
+ useEffect2(() => {
814
1018
  if (visible) animateIn();
815
1019
  }, [visible, animateIn]);
816
1020
  const handleClose = useCallback(() => {
@@ -837,20 +1041,43 @@ var MushiBottomSheet = ({
837
1041
  }
838
1042
  })
839
1043
  ).current;
840
- const handleSubmit = async () => {
841
- if (!category || !description.trim() || !mushi) return;
842
- const trimmed = description.trim();
843
- if (trimmed.length < MIN_DESCRIPTION) {
844
- setSubmitError("Please write at least 20 characters so we can understand the issue.");
1044
+ const offerEmail = useCallback(async () => {
1045
+ if (!emailOptIn || !mushi?.getNotificationPrefs) return;
1046
+ try {
1047
+ const prefs = await mushi.getNotificationPrefs();
1048
+ if (prefs?.available.email && !prefs.email_verified && !prefs.email_pending) {
1049
+ setEmailInput(mushi.emailPrefill?.() ?? "");
1050
+ setEmailState("offer");
1051
+ }
1052
+ } catch {
1053
+ }
1054
+ }, [emailOptIn, mushi]);
1055
+ const submitEmail = useCallback(async () => {
1056
+ const email = emailInput.trim();
1057
+ if (!isPlausibleReporterEmail(email)) {
1058
+ setEmailState("invalid");
845
1059
  return;
846
1060
  }
1061
+ if (!mushi?.setNotificationPrefs) return;
1062
+ setEmailState("sending");
1063
+ try {
1064
+ const res = await mushi.setNotificationPrefs({ email });
1065
+ setEmailState(res.ok ? "sent" : res.error?.status === 422 ? "invalid" : "failed");
1066
+ } catch {
1067
+ setEmailState("failed");
1068
+ }
1069
+ }, [emailInput, mushi]);
1070
+ const handleSubmit = async () => {
1071
+ const hasAttachment = Boolean(screenshotDataUrl && screenshotAttached);
1072
+ if (!mushi || !reporterCanSend(description, hasAttachment, minDescriptionLength)) return;
1073
+ const trimmed = description.trim();
847
1074
  setSubmitError(null);
848
1075
  setPhase("sending");
849
1076
  try {
850
1077
  const outcome = await mushi.submitReport({
851
- category,
1078
+ ...reporterChipToReport(category),
852
1079
  description: trimmed,
853
- screenshotDataUrl: screenshotDataUrl && screenshotAttached ? screenshotDataUrl : void 0
1080
+ screenshotDataUrl: hasAttachment ? screenshotDataUrl : void 0
854
1081
  });
855
1082
  if (!outcome.ok) {
856
1083
  if (outcome.failureKind === "credentials" || outcome.failureKind === "quota") {
@@ -868,7 +1095,7 @@ var MushiBottomSheet = ({
868
1095
  return;
869
1096
  }
870
1097
  setPhase("sent");
871
- setTimeout(handleClose, 1400);
1098
+ void offerEmail();
872
1099
  } catch {
873
1100
  setSubmitError("Something went wrong. Please try again.");
874
1101
  setPhase("error");
@@ -879,94 +1106,106 @@ var MushiBottomSheet = ({
879
1106
  const font = colors.fontFamily ? { fontFamily: colors.fontFamily } : null;
880
1107
  const radius = colors.radius;
881
1108
  const sheetTabs = ["report", "inbox", ...assistantEnabled ? ["assistant"] : []];
882
- const canSubmit = !!category && description.trim().length >= MIN_DESCRIPTION && (phase === "form" || phase === "error");
883
- const submitHint = canSubmit || phase === "sending" ? void 0 : !category ? t.step1.heading : rc.ui.addWords;
1109
+ const canSubmit = reporterCanSend(description, Boolean(activeScreenshot), minDescriptionLength) && (phase === "form" || phase === "error");
1110
+ const submitHint = canSubmit || phase === "sending" ? void 0 : rc.ui.addWords;
884
1111
  const selectedReport = selectedReportId ? inboxReports.find((r) => r.id === selectedReportId) ?? null : null;
885
1112
  const selectedView = selectedReport ? reporterStatus(selectedReport, locale) : null;
886
1113
  const headerTitle = sheetTab === "assistant" ? assistantLabel : sheetTab === "inbox" ? t.flows.reports.title : t.widget.title;
887
- const renderThread = () => /* @__PURE__ */ jsxs(Fragment, { children: [
888
- /* @__PURE__ */ jsx(
889
- TouchableOpacity,
1114
+ const renderThread = () => /* @__PURE__ */ jsxs2(Fragment, { children: [
1115
+ /* @__PURE__ */ jsx2(
1116
+ TouchableOpacity2,
890
1117
  {
891
1118
  onPress: closeThread,
892
1119
  accessibilityRole: "button",
893
1120
  accessibilityLabel: t.widget.back,
894
1121
  hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
895
- children: /* @__PURE__ */ jsxs(Text, { style: [{ color: colors.accent, marginBottom: 8 }, font], children: [
1122
+ children: /* @__PURE__ */ jsxs2(Text2, { style: [{ color: colors.accent, marginBottom: 8 }, font], children: [
896
1123
  "\u2190 ",
897
1124
  t.widget.back
898
1125
  ] })
899
1126
  }
900
1127
  ),
901
- selectedReport && selectedView ? /* @__PURE__ */ jsxs(View, { style: [s.headerCard, { backgroundColor: colors.surface, borderRadius: radius, borderColor: colors.border }], children: [
902
- /* @__PURE__ */ jsx(Text, { style: [s.pill, { color: colors.fg, borderColor: colors.border }, font], children: selectedView.label }),
903
- /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 12, marginTop: 4 }, font], children: selectedView.detail }),
904
- /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, marginTop: 8 }, font], numberOfLines: 4, children: selectedReport.title ?? selectedReport.summary ?? selectedReport.description ?? "" })
1128
+ selectedReport && selectedView ? /* @__PURE__ */ jsxs2(View2, { style: [s2.headerCard, { backgroundColor: colors.surface, borderRadius: radius, borderColor: colors.border }], children: [
1129
+ /* @__PURE__ */ jsx2(Text2, { style: [s2.pill, { color: colors.fg, borderColor: colors.border }, font], children: selectedView.label }),
1130
+ /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.muted, fontSize: 12, marginTop: 4 }, font], children: selectedView.detail }),
1131
+ /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.fg, marginTop: 8 }, font], numberOfLines: 4, children: selectedReport.title ?? selectedReport.summary ?? selectedReport.description ?? "" })
905
1132
  ] }) : null,
906
- threadStatus === "loading" ? /* @__PURE__ */ jsx(View, { accessibilityLabel: t.flows.reports.loading, accessibilityLiveRegion: "polite", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(View, { style: [s.skeleton, { backgroundColor: colors.surface, width: `${90 - i * 18}%` }] }, i)) }) : threadStatus === "error" ? /* @__PURE__ */ jsxs(View, { style: s.errorRow, accessibilityLiveRegion: "polite", children: [
907
- /* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, flex: 1 }, font], accessibilityRole: "alert", children: rc.ui.loadError }),
908
- /* @__PURE__ */ jsx(
909
- TouchableOpacity,
1133
+ threadStatus === "loading" ? /* @__PURE__ */ jsx2(View2, { accessibilityLabel: t.flows.reports.loading, accessibilityLiveRegion: "polite", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx2(View2, { style: [s2.skeleton, { backgroundColor: colors.surface, width: `${90 - i * 18}%` }] }, i)) }) : threadStatus === "error" ? /* @__PURE__ */ jsxs2(View2, { style: s2.errorRow, accessibilityLiveRegion: "polite", children: [
1134
+ /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.error, flex: 1 }, font], accessibilityRole: "alert", children: rc.ui.loadError }),
1135
+ /* @__PURE__ */ jsx2(
1136
+ TouchableOpacity2,
910
1137
  {
911
1138
  onPress: () => selectedReportId && void openThread(selectedReportId),
912
1139
  accessibilityRole: "button",
913
- style: [s.retryBtn, { borderColor: colors.border, borderRadius: radius }],
914
- children: /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontWeight: "600" }, font], children: rc.ui.retry })
1140
+ style: [s2.retryBtn, { borderColor: colors.border, borderRadius: radius }],
1141
+ children: /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.fg, fontWeight: "600" }, font], children: rc.ui.retry })
915
1142
  }
916
1143
  )
917
- ] }) : threadComments.length === 0 ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, marginBottom: 8 }, font], children: rc.ui.noReplies }) : threadComments.map((c) => /* @__PURE__ */ jsxs(
918
- View,
919
- {
920
- style: [
921
- s.threadBubble,
1144
+ ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
1145
+ threadItems.map(
1146
+ (item, idx) => isReporterConversation(item) ? /* @__PURE__ */ jsxs2(
1147
+ View2,
922
1148
  {
923
- backgroundColor: colors.surface,
924
- alignSelf: c.author_kind === "reporter" ? "flex-end" : "flex-start"
925
- }
926
- ],
927
- children: [
928
- /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: c.author_kind === "reporter" ? rc.ui.you : rc.ui.developer }),
929
- /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg }, font], children: c.body })
930
- ]
931
- },
932
- c.id
933
- )),
934
- replyState === "sending" ? /* @__PURE__ */ jsxs(View, { style: [s.threadBubble, { backgroundColor: colors.surface, alignSelf: "flex-end", opacity: 0.7 }], children: [
935
- /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: rc.ui.sending }),
936
- /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg }, font], children: replyText.trim() })
1149
+ style: [
1150
+ s2.threadBubble,
1151
+ {
1152
+ backgroundColor: colors.surface,
1153
+ alignSelf: item.kind === "reporter_comment" ? "flex-end" : "flex-start"
1154
+ }
1155
+ ],
1156
+ children: [
1157
+ /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.muted, fontSize: 11 }, font], children: item.kind === "reporter_comment" ? rc.ui.you : rc.ui.developer }),
1158
+ /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.fg }, font], children: reporterTimelineEntryText(item, locale) })
1159
+ ]
1160
+ },
1161
+ `${item.kind}-${item.comment_id ?? idx}`
1162
+ ) : /* @__PURE__ */ jsx2(
1163
+ Text2,
1164
+ {
1165
+ style: [{ color: colors.muted, fontSize: 12, textAlign: "center", marginVertical: 6 }, font],
1166
+ children: reporterTimelineEntryText(item, locale)
1167
+ },
1168
+ `${item.kind}-${item.at}-${idx}`
1169
+ )
1170
+ ),
1171
+ !threadItems.some(isReporterConversation) ? /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.muted, marginBottom: 8 }, font], children: rc.ui.noReplies }) : null
1172
+ ] }),
1173
+ replyState === "sending" ? /* @__PURE__ */ jsxs2(View2, { style: [s2.threadBubble, { backgroundColor: colors.surface, alignSelf: "flex-end", opacity: 0.7 }], children: [
1174
+ /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.muted, fontSize: 11 }, font], children: rc.ui.sending }),
1175
+ /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.fg }, font], children: replyText.trim() })
937
1176
  ] }) : null,
938
- selectedView?.canVerify ? /* @__PURE__ */ jsxs(View, { style: s.verifyRow, children: [
939
- /* @__PURE__ */ jsx(
940
- TouchableOpacity,
1177
+ selectedView?.canVerify ? /* @__PURE__ */ jsxs2(View2, { style: s2.verifyRow, children: [
1178
+ /* @__PURE__ */ jsx2(
1179
+ TouchableOpacity2,
941
1180
  {
942
- style: [s.verifyBtn, { backgroundColor: colors.accent, borderRadius: radius }],
1181
+ style: [s2.verifyBtn, { backgroundColor: colors.accent, borderRadius: radius }],
943
1182
  onPress: () => void submitFeedback("confirms"),
944
1183
  disabled: feedbackSending,
945
1184
  accessibilityRole: "button",
946
1185
  accessibilityState: { disabled: feedbackSending, busy: feedbackSending },
947
- children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentFg }, font], children: rc.ui.yes })
1186
+ children: /* @__PURE__ */ jsx2(Text2, { style: [s2.submitText, { color: colors.accentFg }, font], children: rc.ui.yes })
948
1187
  }
949
1188
  ),
950
- /* @__PURE__ */ jsx(
951
- TouchableOpacity,
1189
+ /* @__PURE__ */ jsx2(
1190
+ TouchableOpacity2,
952
1191
  {
953
- style: [s.verifyBtn, { backgroundColor: colors.border, borderRadius: radius }],
1192
+ style: [s2.verifyBtn, { backgroundColor: colors.border, borderRadius: radius }],
954
1193
  onPress: () => void submitFeedback("not_fixed"),
955
1194
  disabled: feedbackSending,
956
1195
  accessibilityRole: "button",
957
1196
  accessibilityState: { disabled: feedbackSending, busy: feedbackSending },
958
- children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.fg }, font], children: rc.ui.notYet })
1197
+ children: /* @__PURE__ */ jsx2(Text2, { style: [s2.submitText, { color: colors.fg }, font], children: rc.ui.notYet })
959
1198
  }
960
1199
  )
961
1200
  ] }) : null,
962
- feedbackError ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, marginBottom: 8 }, font], accessibilityRole: "alert", children: rc.ui.sendFailed }) : null,
963
- /* @__PURE__ */ jsxs(View, { style: s.composer, children: [
964
- /* @__PURE__ */ jsx(
1201
+ feedbackError ? /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.error, marginBottom: 8 }, font], accessibilityRole: "alert", children: rc.ui.sendFailed }) : null,
1202
+ /* @__PURE__ */ jsxs2(View2, { style: s2.composer, children: [
1203
+ /* @__PURE__ */ jsx2(
965
1204
  TextInput,
966
1205
  {
967
1206
  style: [
968
- s.input,
969
- s.composerInput,
1207
+ s2.input,
1208
+ s2.composerInput,
970
1209
  { backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius },
971
1210
  font
972
1211
  ],
@@ -983,11 +1222,11 @@ var MushiBottomSheet = ({
983
1222
  accessibilityLabel: t.flows.thread.replyPlaceholder
984
1223
  }
985
1224
  ),
986
- /* @__PURE__ */ jsx(
987
- TouchableOpacity,
1225
+ /* @__PURE__ */ jsx2(
1226
+ TouchableOpacity2,
988
1227
  {
989
1228
  style: [
990
- s.sendBtn,
1229
+ s2.sendBtn,
991
1230
  {
992
1231
  backgroundColor: replyText.trim() && replyState !== "sending" ? colors.accent : colors.disabled,
993
1232
  borderRadius: radius
@@ -998,11 +1237,11 @@ var MushiBottomSheet = ({
998
1237
  accessibilityRole: "button",
999
1238
  accessibilityLabel: replyState === "failed" ? rc.ui.retry : t.flows.thread.send,
1000
1239
  accessibilityState: { disabled: !replyText.trim() || replyState === "sending", busy: replyState === "sending" },
1001
- children: /* @__PURE__ */ jsx(
1002
- Text,
1240
+ children: /* @__PURE__ */ jsx2(
1241
+ Text2,
1003
1242
  {
1004
1243
  style: [
1005
- s.submitText,
1244
+ s2.submitText,
1006
1245
  { color: replyText.trim() && replyState !== "sending" ? colors.accentFg : colors.disabledFg },
1007
1246
  font
1008
1247
  ],
@@ -1012,7 +1251,7 @@ var MushiBottomSheet = ({
1012
1251
  }
1013
1252
  )
1014
1253
  ] }),
1015
- replyState === "failed" ? /* @__PURE__ */ jsxs(Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: [
1254
+ replyState === "failed" ? /* @__PURE__ */ jsxs2(Text2, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: [
1016
1255
  rc.ui.sendFailed,
1017
1256
  " \xB7 ",
1018
1257
  rc.ui.retry
@@ -1020,50 +1259,50 @@ var MushiBottomSheet = ({
1020
1259
  ] });
1021
1260
  const renderInboxList = () => {
1022
1261
  if (inboxLoading && !inboxLoaded) {
1023
- return /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted }, font], children: t.flows.reports.loading });
1262
+ return /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.muted }, font], children: t.flows.reports.loading });
1024
1263
  }
1025
1264
  if (inboxReports.length === 0) {
1026
- return /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted }, font], children: rc.ui.empty });
1265
+ return /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.muted }, font], children: rc.ui.empty });
1027
1266
  }
1028
1267
  return inboxReports.map((r) => {
1029
1268
  const view = reporterStatus(r, locale);
1030
1269
  const unread = (r.unread_count ?? 0) > 0;
1031
1270
  const title = (r.title ?? r.summary ?? r.description ?? "Report").slice(0, 80);
1032
- return /* @__PURE__ */ jsxs(
1033
- TouchableOpacity,
1271
+ return /* @__PURE__ */ jsxs2(
1272
+ TouchableOpacity2,
1034
1273
  {
1035
- style: [s.inboxRow, { borderColor: colors.border }],
1274
+ style: [s2.inboxRow, { borderColor: colors.border }],
1036
1275
  onPress: () => void openThread(r.id),
1037
1276
  accessibilityRole: "button",
1038
1277
  accessibilityLabel: `${view.label}. ${title}${unread ? `. ${r.unread_count} new` : ""}`,
1039
1278
  children: [
1040
- /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontWeight: unread ? "700" : "600" }, font], numberOfLines: 1, children: title }),
1041
- /* @__PURE__ */ jsxs(Text, { style: [{ color: unread ? colors.fg : colors.muted, fontSize: 12, marginTop: 2 }, font], numberOfLines: 1, children: [
1279
+ /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.fg, fontWeight: unread ? "700" : "600" }, font], numberOfLines: 1, children: title }),
1280
+ /* @__PURE__ */ jsxs2(Text2, { style: [{ color: unread ? colors.fg : colors.muted, fontSize: 12, marginTop: 2 }, font], numberOfLines: 1, children: [
1042
1281
  view.label,
1043
1282
  view.othersNote ? ` \xB7 ${view.othersNote}` : ""
1044
1283
  ] }),
1045
- unread && r.last_event_preview ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontSize: 12, fontWeight: "700", marginTop: 2 }, font], numberOfLines: 1, children: r.last_event_preview }) : null
1284
+ unread && r.last_event_preview ? /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.fg, fontSize: 12, fontWeight: "700", marginTop: 2 }, font], numberOfLines: 1, children: r.last_event_preview }) : null
1046
1285
  ]
1047
1286
  },
1048
1287
  r.id
1049
1288
  );
1050
1289
  });
1051
1290
  };
1052
- return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, onRequestClose: handleClose, children: /* @__PURE__ */ jsxs(
1291
+ return /* @__PURE__ */ jsx2(Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, onRequestClose: handleClose, children: /* @__PURE__ */ jsxs2(
1053
1292
  KeyboardAvoidingView,
1054
1293
  {
1055
- style: s.flex,
1294
+ style: s2.flex,
1056
1295
  behavior: Platform2.OS === "ios" ? "padding" : "height",
1057
1296
  keyboardVerticalOffset: Platform2.OS === "ios" ? 0 : 20,
1058
1297
  children: [
1059
- /* @__PURE__ */ jsx(
1298
+ /* @__PURE__ */ jsx2(
1060
1299
  Animated.View,
1061
1300
  {
1062
- style: [s.backdrop, { backgroundColor: colors.backdrop, opacity: backdropOpacity }],
1063
- children: /* @__PURE__ */ jsx(
1064
- TouchableOpacity,
1301
+ style: [s2.backdrop, { backgroundColor: colors.backdrop, opacity: backdropOpacity }],
1302
+ children: /* @__PURE__ */ jsx2(
1303
+ TouchableOpacity2,
1065
1304
  {
1066
- style: s.flex,
1305
+ style: s2.flex,
1067
1306
  activeOpacity: 1,
1068
1307
  onPress: handleClose,
1069
1308
  accessibilityRole: "button",
@@ -1072,21 +1311,21 @@ var MushiBottomSheet = ({
1072
1311
  )
1073
1312
  }
1074
1313
  ),
1075
- /* @__PURE__ */ jsxs(
1314
+ /* @__PURE__ */ jsxs2(
1076
1315
  Animated.View,
1077
1316
  {
1078
1317
  accessibilityViewIsModal: true,
1079
1318
  onAccessibilityEscape: handleClose,
1080
1319
  style: [
1081
- s.sheet,
1320
+ s2.sheet,
1082
1321
  { backgroundColor: colors.bg, transform: [{ translateY }] }
1083
1322
  ],
1084
1323
  children: [
1085
- /* @__PURE__ */ jsx(
1086
- View,
1324
+ /* @__PURE__ */ jsx2(
1325
+ View2,
1087
1326
  {
1088
1327
  ...panResponder.panHandlers,
1089
- style: s.handleArea,
1328
+ style: s2.handleArea,
1090
1329
  accessible: true,
1091
1330
  accessibilityRole: "adjustable",
1092
1331
  accessibilityLabel: t.widget.close,
@@ -1094,57 +1333,57 @@ var MushiBottomSheet = ({
1094
1333
  onAccessibilityAction: (e) => {
1095
1334
  if (e.nativeEvent.actionName === "decrement" || e.nativeEvent.actionName === "escape") handleClose();
1096
1335
  },
1097
- children: /* @__PURE__ */ jsx(View, { style: [s.handle, { backgroundColor: colors.muted }] })
1336
+ children: /* @__PURE__ */ jsx2(View2, { style: [s2.handle, { backgroundColor: colors.muted }] })
1098
1337
  }
1099
1338
  ),
1100
- /* @__PURE__ */ jsx(View, { style: [s.header, { borderBottomColor: colors.border }], children: /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: [s.headerTitle, { color: colors.fg }, font], children: headerTitle }) }),
1101
- /* @__PURE__ */ jsx(View, { style: [s.tabRow, { borderBottomColor: colors.border }], accessibilityRole: "tablist", children: sheetTabs.map((tab) => /* @__PURE__ */ jsx(
1102
- TouchableOpacity,
1339
+ /* @__PURE__ */ jsx2(View2, { style: [s2.header, { borderBottomColor: colors.border }], children: /* @__PURE__ */ jsx2(Text2, { accessibilityRole: "header", style: [s2.headerTitle, { color: colors.fg }, font], children: headerTitle }) }),
1340
+ /* @__PURE__ */ jsx2(View2, { style: [s2.tabRow, { borderBottomColor: colors.border }], accessibilityRole: "tablist", children: sheetTabs.map((tab) => /* @__PURE__ */ jsx2(
1341
+ TouchableOpacity2,
1103
1342
  {
1104
1343
  onPress: () => {
1105
1344
  setSheetTab(tab);
1106
1345
  if (tab === "inbox") void loadInbox({ silent: inboxLoaded });
1107
1346
  else closeThread();
1108
1347
  },
1109
- style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
1348
+ style: [s2.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
1110
1349
  accessibilityRole: "tab",
1111
1350
  accessibilityState: { selected: sheetTab === tab },
1112
- children: /* @__PURE__ */ jsx(Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.fg : colors.muted }, font], children: tab === "report" ? t.widget.trigger : tab === "inbox" ? t.flows.reports.title : assistantLabel })
1351
+ children: /* @__PURE__ */ jsx2(Text2, { style: [s2.tabLabel, { color: sheetTab === tab ? colors.fg : colors.muted }, font], children: tab === "report" ? t.widget.trigger : tab === "inbox" ? t.flows.reports.title : assistantLabel })
1113
1352
  },
1114
1353
  tab
1115
1354
  )) }),
1116
- sheetTab === "assistant" ? /* @__PURE__ */ jsxs(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
1117
- assistantTurns.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1118
- /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, marginBottom: 12, lineHeight: 20 }, font], children: greeting }),
1119
- assistantSuggestions.map((chip) => /* @__PURE__ */ jsx(
1120
- TouchableOpacity,
1355
+ sheetTab === "assistant" ? /* @__PURE__ */ jsxs2(ScrollView, { style: s2.body, keyboardShouldPersistTaps: "handled", children: [
1356
+ assistantTurns.length === 0 ? /* @__PURE__ */ jsxs2(Fragment, { children: [
1357
+ /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.muted, marginBottom: 12, lineHeight: 20 }, font], children: greeting }),
1358
+ assistantSuggestions.map((chip) => /* @__PURE__ */ jsx2(
1359
+ TouchableOpacity2,
1121
1360
  {
1122
- style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.surface }],
1361
+ style: [s2.assistantChip, { borderColor: colors.border, backgroundColor: colors.surface }],
1123
1362
  onPress: () => void sendAssistant(chip),
1124
1363
  accessibilityRole: "button",
1125
- children: /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontSize: 13 }, font], children: chip })
1364
+ children: /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.fg, fontSize: 13 }, font], children: chip })
1126
1365
  },
1127
1366
  chip
1128
1367
  ))
1129
- ] }) : assistantTurns.map((turn, idx) => /* @__PURE__ */ jsxs(
1130
- View,
1368
+ ] }) : assistantTurns.map((turn, idx) => /* @__PURE__ */ jsxs2(
1369
+ View2,
1131
1370
  {
1132
1371
  style: [
1133
- s.assistantBubble,
1372
+ s2.assistantBubble,
1134
1373
  {
1135
1374
  alignSelf: turn.role === "user" ? "flex-end" : "flex-start",
1136
1375
  backgroundColor: turn.role === "user" ? colors.accent : colors.surface
1137
1376
  }
1138
1377
  ],
1139
1378
  children: [
1140
- /* @__PURE__ */ jsx(Text, { style: [{ color: turn.role === "user" ? colors.accentFg : colors.fg }, font], children: turn.text }),
1141
- turn.options?.map((opt) => /* @__PURE__ */ jsx(
1142
- TouchableOpacity,
1379
+ /* @__PURE__ */ jsx2(Text2, { style: [{ color: turn.role === "user" ? colors.accentFg : colors.fg }, font], children: turn.text }),
1380
+ turn.options?.map((opt) => /* @__PURE__ */ jsx2(
1381
+ TouchableOpacity2,
1143
1382
  {
1144
1383
  onPress: () => void sendAssistant(opt),
1145
1384
  style: { marginTop: 8 },
1146
1385
  accessibilityRole: "button",
1147
- children: /* @__PURE__ */ jsx(Text, { style: [{ color: colors.accent, fontSize: 12 }, font], children: opt })
1386
+ children: /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.accent, fontSize: 12 }, font], children: opt })
1148
1387
  },
1149
1388
  opt
1150
1389
  ))
@@ -1152,13 +1391,13 @@ var MushiBottomSheet = ({
1152
1391
  },
1153
1392
  `${turn.role}-${idx}`
1154
1393
  )),
1155
- assistantSending ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, marginTop: 8 }, font], accessibilityLiveRegion: "polite", children: t.assistant.thinking }) : null,
1156
- assistantError ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, marginTop: 8 }, font], accessibilityRole: "alert", children: assistantError }) : null,
1157
- /* @__PURE__ */ jsxs(View, { style: s.assistantComposer, children: [
1158
- /* @__PURE__ */ jsx(
1394
+ assistantSending ? /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.muted, marginTop: 8 }, font], accessibilityLiveRegion: "polite", children: t.assistant.thinking }) : null,
1395
+ assistantError ? /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.error, marginTop: 8 }, font], accessibilityRole: "alert", children: assistantError }) : null,
1396
+ /* @__PURE__ */ jsxs2(View2, { style: s2.assistantComposer, children: [
1397
+ /* @__PURE__ */ jsx2(
1159
1398
  TextInput,
1160
1399
  {
1161
- style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius }, font],
1400
+ style: [s2.input, { flex: 1, minHeight: 44, backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius }, font],
1162
1401
  placeholder: t.assistant.inputPlaceholder,
1163
1402
  placeholderTextColor: colors.muted,
1164
1403
  value: assistantInput,
@@ -1166,71 +1405,101 @@ var MushiBottomSheet = ({
1166
1405
  editable: !assistantSending
1167
1406
  }
1168
1407
  ),
1169
- /* @__PURE__ */ jsx(
1170
- TouchableOpacity,
1408
+ /* @__PURE__ */ jsx2(
1409
+ TouchableOpacity2,
1171
1410
  {
1172
- style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16, borderRadius: radius }],
1411
+ style: [s2.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16, borderRadius: radius }],
1173
1412
  onPress: () => void sendAssistant(assistantInput),
1174
1413
  disabled: !assistantInput.trim() || assistantSending,
1175
1414
  accessibilityRole: "button",
1176
1415
  accessibilityLabel: t.flows.thread.send,
1177
1416
  accessibilityState: { disabled: !assistantInput.trim() || assistantSending, busy: assistantSending },
1178
- children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentFg }], children: "\u2191" })
1417
+ children: /* @__PURE__ */ jsx2(Text2, { style: [s2.submitText, { color: colors.accentFg }], children: "\u2191" })
1179
1418
  }
1180
1419
  )
1181
1420
  ] })
1182
- ] }) : sheetTab === "inbox" ? /* @__PURE__ */ jsx(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", contentContainerStyle: { paddingBottom: 24 }, children: selectedReportId ? renderThread() : renderInboxList() }) : phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, accessibilityLiveRegion: "polite", children: [
1183
- /* @__PURE__ */ jsx(Text, { style: [s.sentEmoji], children: "\u2705" }),
1184
- /* @__PURE__ */ jsx(Text, { style: [s.sentText, { color: colors.fg }, font], children: t.widget.submitted })
1185
- ] }) : /* @__PURE__ */ jsxs(
1421
+ ] }) : sheetTab === "inbox" ? /* @__PURE__ */ jsx2(ScrollView, { style: s2.body, keyboardShouldPersistTaps: "handled", contentContainerStyle: { paddingBottom: 24 }, children: selectedReportId ? renderThread() : renderInboxList() }) : phase === "sent" ? /* @__PURE__ */ jsxs2(ScrollView, { style: s2.body, keyboardShouldPersistTaps: "handled", contentContainerStyle: { paddingBottom: 24 }, children: [
1422
+ /* @__PURE__ */ jsxs2(View2, { style: s2.sentWrap, accessibilityLiveRegion: "polite", children: [
1423
+ /* @__PURE__ */ jsx2(Text2, { style: [s2.sentEmoji], importantForAccessibility: "no", children: "\u2705" }),
1424
+ /* @__PURE__ */ jsx2(Text2, { style: [s2.sentText, { color: colors.fg }, font], children: rc.ui.receipt })
1425
+ ] }),
1426
+ /* @__PURE__ */ jsxs2(View2, { style: s2.verifyRow, children: [
1427
+ /* @__PURE__ */ jsx2(
1428
+ TouchableOpacity2,
1429
+ {
1430
+ style: [s2.verifyBtn, { backgroundColor: colors.accent, borderRadius: radius }],
1431
+ onPress: () => {
1432
+ resetForm();
1433
+ setSheetTab("inbox");
1434
+ void loadInbox();
1435
+ },
1436
+ accessibilityRole: "button",
1437
+ children: /* @__PURE__ */ jsx2(Text2, { style: [s2.submitText, { color: colors.accentFg }, font], children: rc.ui.trackIt })
1438
+ }
1439
+ ),
1440
+ /* @__PURE__ */ jsx2(
1441
+ TouchableOpacity2,
1442
+ {
1443
+ style: [s2.verifyBtn, { backgroundColor: colors.border, borderRadius: radius }],
1444
+ onPress: handleClose,
1445
+ accessibilityRole: "button",
1446
+ children: /* @__PURE__ */ jsx2(Text2, { style: [s2.submitText, { color: colors.fg }, font], children: rc.ui.done })
1447
+ }
1448
+ )
1449
+ ] }),
1450
+ emailState === "sent" ? /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.fg, marginTop: 12 }, font], accessibilityLiveRegion: "polite", children: rc.ui.emailCheckInbox }) : emailState !== "hidden" ? /* @__PURE__ */ jsxs2(View2, { style: { marginTop: 12 }, children: [
1451
+ /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.fg, marginBottom: 6 }, font], children: rc.ui.emailOptIn }),
1452
+ /* @__PURE__ */ jsxs2(View2, { style: s2.composer, children: [
1453
+ /* @__PURE__ */ jsx2(
1454
+ TextInput,
1455
+ {
1456
+ style: [
1457
+ s2.input,
1458
+ s2.composerInput,
1459
+ { backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius },
1460
+ font
1461
+ ],
1462
+ value: emailInput,
1463
+ onChangeText: (v) => {
1464
+ setEmailInput(v.slice(0, 254));
1465
+ if (emailState === "invalid" || emailState === "failed") setEmailState("offer");
1466
+ },
1467
+ placeholder: rc.ui.emailPlaceholder,
1468
+ placeholderTextColor: colors.muted,
1469
+ keyboardType: "email-address",
1470
+ autoCapitalize: "none",
1471
+ autoComplete: "email",
1472
+ textContentType: "emailAddress",
1473
+ editable: emailState !== "sending",
1474
+ accessibilityLabel: rc.ui.emailOptIn
1475
+ }
1476
+ ),
1477
+ /* @__PURE__ */ jsx2(
1478
+ TouchableOpacity2,
1479
+ {
1480
+ style: [s2.sendBtn, { backgroundColor: emailInput.trim() ? colors.accent : colors.disabled, borderRadius: radius }],
1481
+ onPress: () => void submitEmail(),
1482
+ disabled: !emailInput.trim() || emailState === "sending",
1483
+ accessibilityRole: "button",
1484
+ accessibilityState: { disabled: !emailInput.trim() || emailState === "sending", busy: emailState === "sending" },
1485
+ children: /* @__PURE__ */ jsx2(Text2, { style: [s2.submitText, { color: emailInput.trim() ? colors.accentFg : colors.disabledFg }, font], children: emailState === "sending" ? rc.ui.sending : rc.ui.emailSubmit })
1486
+ }
1487
+ )
1488
+ ] }),
1489
+ emailState === "invalid" || emailState === "failed" ? /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: emailState === "invalid" ? rc.ui.emailInvalid : rc.ui.emailFailed }) : null
1490
+ ] }) : null
1491
+ ] }) : /* @__PURE__ */ jsxs2(
1186
1492
  ScrollView,
1187
1493
  {
1188
- style: s.body,
1494
+ style: s2.body,
1189
1495
  keyboardShouldPersistTaps: "handled",
1190
1496
  contentContainerStyle: { paddingBottom: 24 },
1191
1497
  children: [
1192
- /* @__PURE__ */ jsx(Text, { style: [s.stepLabel, { color: colors.muted }, font], children: t.step1.heading }),
1193
- /* @__PURE__ */ jsx(View, { style: s.catRow, accessibilityRole: "radiogroup", children: CATEGORY_KEYS.map((key) => {
1194
- const active = category === key;
1195
- return /* @__PURE__ */ jsxs(
1196
- TouchableOpacity,
1197
- {
1198
- onPress: () => setCategory(key),
1199
- activeOpacity: 0.7,
1200
- accessibilityRole: "radio",
1201
- accessibilityState: { selected: active, checked: active },
1202
- accessibilityLabel: t.step1.categories[key],
1203
- style: [
1204
- s.catBtn,
1205
- {
1206
- backgroundColor: active ? colors.accent : colors.surface,
1207
- borderColor: active ? colors.accent : colors.border
1208
- }
1209
- ],
1210
- children: [
1211
- /* @__PURE__ */ jsx(Text, { style: s.catEmoji, importantForAccessibility: "no", children: CATEGORY_EMOJI[key] }),
1212
- /* @__PURE__ */ jsx(
1213
- Text,
1214
- {
1215
- numberOfLines: 1,
1216
- style: [
1217
- s.catLabel,
1218
- { color: active ? colors.accentFg : colors.fg },
1219
- font
1220
- ],
1221
- children: t.step1.categories[key]
1222
- }
1223
- )
1224
- ]
1225
- },
1226
- key
1227
- );
1228
- }) }),
1229
- /* @__PURE__ */ jsx(
1498
+ /* @__PURE__ */ jsx2(
1230
1499
  TextInput,
1231
1500
  {
1232
1501
  style: [
1233
- s.input,
1502
+ s2.input,
1234
1503
  {
1235
1504
  backgroundColor: colors.surface,
1236
1505
  color: colors.fg,
@@ -1254,22 +1523,59 @@ var MushiBottomSheet = ({
1254
1523
  maxLength: 4e3
1255
1524
  }
1256
1525
  ),
1257
- submitError ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: submitError }) : null,
1258
- activeScreenshot ? /* @__PURE__ */ jsxs(View, { style: s.screenshotRow, children: [
1259
- /* @__PURE__ */ jsx(
1526
+ submitError ? /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: submitError }) : null,
1527
+ /* @__PURE__ */ jsx2(View2, { style: [s2.catRow, { marginTop: 12 }], accessibilityRole: "radiogroup", children: REPORTER_CATEGORIES.map((key) => {
1528
+ const active = category === key;
1529
+ const label = reporterCategoryLabel(key, locale);
1530
+ return /* @__PURE__ */ jsxs2(
1531
+ TouchableOpacity2,
1532
+ {
1533
+ onPress: () => setCategory(active ? null : key),
1534
+ activeOpacity: 0.7,
1535
+ accessibilityRole: "radio",
1536
+ accessibilityState: { selected: active, checked: active },
1537
+ accessibilityLabel: label,
1538
+ style: [
1539
+ s2.catBtn,
1540
+ {
1541
+ backgroundColor: active ? colors.accent : colors.surface,
1542
+ borderColor: active ? colors.accent : colors.border
1543
+ }
1544
+ ],
1545
+ children: [
1546
+ /* @__PURE__ */ jsx2(Text2, { style: s2.catEmoji, importantForAccessibility: "no", children: CATEGORY_EMOJI[key] }),
1547
+ /* @__PURE__ */ jsx2(
1548
+ Text2,
1549
+ {
1550
+ numberOfLines: 1,
1551
+ style: [
1552
+ s2.catLabel,
1553
+ { color: active ? colors.accentFg : colors.fg },
1554
+ font
1555
+ ],
1556
+ children: label
1557
+ }
1558
+ )
1559
+ ]
1560
+ },
1561
+ key
1562
+ );
1563
+ }) }),
1564
+ activeScreenshot ? /* @__PURE__ */ jsxs2(View2, { style: s2.screenshotRow, children: [
1565
+ /* @__PURE__ */ jsx2(
1260
1566
  Image,
1261
1567
  {
1262
1568
  source: { uri: activeScreenshot },
1263
- style: [s.screenshotThumb, { backgroundColor: colors.fg }],
1569
+ style: [s2.screenshotThumb, { backgroundColor: colors.fg }],
1264
1570
  accessibilityLabel: t.step3.screenshotPreviewAlt
1265
1571
  }
1266
1572
  ),
1267
- /* @__PURE__ */ jsxs(View, { style: s.screenshotMeta, children: [
1268
- /* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.fg }, font], children: t.step3.screenshotAttached.replace(" \u2713", "") }),
1269
- /* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.muted }, font], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : t.step3.screenshotSensitiveHint.split("\u2014")[0]?.trim() ?? t.step3.screenshotSensitiveHint })
1573
+ /* @__PURE__ */ jsxs2(View2, { style: s2.screenshotMeta, children: [
1574
+ /* @__PURE__ */ jsx2(Text2, { style: [s2.screenshotLabel, { color: colors.fg }, font], children: t.step3.screenshotAttached.replace(" \u2713", "") }),
1575
+ /* @__PURE__ */ jsx2(Text2, { style: [s2.screenshotSub, { color: colors.muted }, font], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : t.step3.screenshotSensitiveHint.split("\u2014")[0]?.trim() ?? t.step3.screenshotSensitiveHint })
1270
1576
  ] }),
1271
- /* @__PURE__ */ jsx(
1272
- TouchableOpacity,
1577
+ /* @__PURE__ */ jsx2(
1578
+ TouchableOpacity2,
1273
1579
  {
1274
1580
  onPress: () => {
1275
1581
  setScreenshotAttached(false);
@@ -1278,13 +1584,13 @@ var MushiBottomSheet = ({
1278
1584
  hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
1279
1585
  accessibilityRole: "button",
1280
1586
  accessibilityLabel: t.widget.close,
1281
- children: /* @__PURE__ */ jsx(Text, { style: [s.screenshotRemove, { color: colors.muted }], children: "\u2715" })
1587
+ children: /* @__PURE__ */ jsx2(Text2, { style: [s2.screenshotRemove, { color: colors.muted }], children: "\u2715" })
1282
1588
  }
1283
1589
  )
1284
1590
  ] }) : null,
1285
- submitHint ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 12, marginBottom: 6 }, font], children: submitHint }) : null,
1286
- /* @__PURE__ */ jsx(
1287
- TouchableOpacity,
1591
+ submitHint ? /* @__PURE__ */ jsx2(Text2, { style: [{ color: colors.muted, fontSize: 12, marginBottom: 6 }, font], children: submitHint }) : null,
1592
+ /* @__PURE__ */ jsx2(
1593
+ TouchableOpacity2,
1288
1594
  {
1289
1595
  onPress: handleSubmit,
1290
1596
  disabled: !canSubmit,
@@ -1294,10 +1600,10 @@ var MushiBottomSheet = ({
1294
1600
  accessibilityHint: submitHint,
1295
1601
  accessibilityState: { disabled: !canSubmit, busy: phase === "sending" },
1296
1602
  style: [
1297
- s.submitBtn,
1603
+ s2.submitBtn,
1298
1604
  { backgroundColor: canSubmit ? colors.accent : colors.disabled, borderRadius: radius }
1299
1605
  ],
1300
- children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: canSubmit ? colors.accentFg : colors.disabledFg }, font], children: phase === "sending" ? t.widget.submitting : t.widget.submit })
1606
+ children: /* @__PURE__ */ jsx2(Text2, { style: [s2.submitText, { color: canSubmit ? colors.accentFg : colors.disabledFg }, font], children: phase === "sending" ? t.widget.submitting : t.widget.submit })
1301
1607
  }
1302
1608
  )
1303
1609
  ]
@@ -1310,7 +1616,7 @@ var MushiBottomSheet = ({
1310
1616
  }
1311
1617
  ) });
1312
1618
  };
1313
- var s = StyleSheet.create({
1619
+ var s2 = StyleSheet2.create({
1314
1620
  flex: { flex: 1 },
1315
1621
  backdrop: {
1316
1622
  // Inlined StyleSheet.absoluteFillObject — RN 0.86 dropped it from the
@@ -1330,7 +1636,7 @@ var s = StyleSheet.create({
1330
1636
  borderTopLeftRadius: MUSHI_RADIUS2.sheet,
1331
1637
  borderTopRightRadius: MUSHI_RADIUS2.sheet,
1332
1638
  paddingBottom: Platform2.OS === "ios" ? 34 : MUSHI_SPACING.roomy,
1333
- shadowColor: MUSHI_SHADOW_INK,
1639
+ shadowColor: MUSHI_SHADOW_INK2,
1334
1640
  shadowOffset: { width: 0, height: -3 },
1335
1641
  shadowOpacity: 0.12,
1336
1642
  shadowRadius: 8,
@@ -1454,7 +1760,7 @@ var s = StyleSheet.create({
1454
1760
  },
1455
1761
  tabRow: {
1456
1762
  flexDirection: "row",
1457
- borderBottomWidth: StyleSheet.hairlineWidth,
1763
+ borderBottomWidth: StyleSheet2.hairlineWidth,
1458
1764
  marginBottom: 8
1459
1765
  },
1460
1766
  tabBtn: {
@@ -1472,10 +1778,10 @@ var s = StyleSheet.create({
1472
1778
  inboxRow: {
1473
1779
  paddingVertical: 12,
1474
1780
  minHeight: 56,
1475
- borderBottomWidth: StyleSheet.hairlineWidth
1781
+ borderBottomWidth: StyleSheet2.hairlineWidth
1476
1782
  },
1477
1783
  headerCard: {
1478
- borderWidth: StyleSheet.hairlineWidth,
1784
+ borderWidth: StyleSheet2.hairlineWidth,
1479
1785
  padding: 12,
1480
1786
  marginBottom: 12
1481
1787
  },
@@ -1565,17 +1871,17 @@ var s = StyleSheet.create({
1565
1871
  });
1566
1872
 
1567
1873
  // src/components/MushiFloatingButton.tsx
1568
- import { useRef as useRef2, useCallback as useCallback2, useEffect as useEffect2 } from "react";
1874
+ import { useRef as useRef2, useCallback as useCallback2, useEffect as useEffect3 } from "react";
1569
1875
  import {
1570
1876
  Animated as Animated2,
1571
1877
  PanResponder as PanResponder2,
1572
- StyleSheet as StyleSheet2,
1878
+ StyleSheet as StyleSheet3,
1573
1879
  Platform as Platform3,
1574
1880
  Dimensions as Dimensions3,
1575
- useColorScheme as useColorScheme2
1881
+ useColorScheme as useColorScheme3
1576
1882
  } from "react-native";
1577
1883
  import { mushiPalette as mushiPalette2, MUSHI_COPY as MUSHI_COPY2, MUSHI_GEOMETRY } from "@mushi-mushi/core";
1578
- import { jsx as jsx2 } from "react/jsx-runtime";
1884
+ import { jsx as jsx3 } from "react/jsx-runtime";
1579
1885
  var FAB_SIZE = MUSHI_GEOMETRY.fabSize;
1580
1886
  var EDGE_PADDING = 12;
1581
1887
  function getScreenBounds() {
@@ -1610,7 +1916,7 @@ var MushiFloatingButton = ({
1610
1916
  config,
1611
1917
  accent
1612
1918
  }) => {
1613
- const scheme = useColorScheme2();
1919
+ const scheme = useColorScheme3();
1614
1920
  const dark = scheme === "dark";
1615
1921
  const draggable = config?.draggable ?? false;
1616
1922
  const snapToEdge = config?.snapToEdge ?? true;
@@ -1628,7 +1934,7 @@ var MushiFloatingButton = ({
1628
1934
  const isDragging = useRef2(false);
1629
1935
  const pal = mushiPalette2(dark ? "dark" : "light");
1630
1936
  const accentColor = accent ?? pal.accent;
1631
- useEffect2(() => {
1937
+ useEffect3(() => {
1632
1938
  if (!persist) return;
1633
1939
  void loadStoredPos().then((pos) => {
1634
1940
  if (!pos) return;
@@ -1720,12 +2026,12 @@ var MushiFloatingButton = ({
1720
2026
  }).start();
1721
2027
  if (!isDragging.current) onPress();
1722
2028
  }, [scale, onPress]);
1723
- return /* @__PURE__ */ jsx2(
2029
+ return /* @__PURE__ */ jsx3(
1724
2030
  Animated2.View,
1725
2031
  {
1726
2032
  ...draggable ? panResponder.panHandlers : {},
1727
2033
  style: [
1728
- s2.fab,
2034
+ s3.fab,
1729
2035
  {
1730
2036
  left: posX,
1731
2037
  bottom: Animated2.add(new Animated2.Value(baseBottom), Animated2.multiply(posY, -1)),
@@ -1737,11 +2043,11 @@ var MushiFloatingButton = ({
1737
2043
  onStartShouldSetResponder: () => !draggable,
1738
2044
  onResponderGrant: handlePressIn,
1739
2045
  onResponderRelease: handlePressOut,
1740
- children: /* @__PURE__ */ jsx2(Animated2.Text, { style: s2.emoji, children: MUSHI_COPY2.triggerText })
2046
+ children: /* @__PURE__ */ jsx3(Animated2.Text, { style: s3.emoji, children: MUSHI_COPY2.triggerText })
1741
2047
  }
1742
2048
  );
1743
2049
  };
1744
- var s2 = StyleSheet2.create({
2050
+ var s3 = StyleSheet3.create({
1745
2051
  fab: {
1746
2052
  position: "absolute",
1747
2053
  width: FAB_SIZE,
@@ -1763,14 +2069,14 @@ var s2 = StyleSheet2.create({
1763
2069
  // src/components/MushiBanner.tsx
1764
2070
  import { useCallback as useCallback3 } from "react";
1765
2071
  import {
1766
- View as View2,
1767
- Text as Text2,
1768
- TouchableOpacity as TouchableOpacity2,
1769
- StyleSheet as StyleSheet3,
2072
+ View as View3,
2073
+ Text as Text3,
2074
+ TouchableOpacity as TouchableOpacity3,
2075
+ StyleSheet as StyleSheet4,
1770
2076
  Platform as Platform4
1771
2077
  } from "react-native";
1772
2078
  import { MUSHI_BANNER_NEON, MUSHI_COPY as MUSHI_COPY3, MUSHI_GEOMETRY as MUSHI_GEOMETRY2, MUSHI_SPACING as MUSHI_SPACING2, MUSHI_TYPE as MUSHI_TYPE2, MUSHI_Z } from "@mushi-mushi/core";
1773
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
2079
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1774
2080
  var MUSHI_BANNER_DEFAULT_HEIGHT = MUSHI_GEOMETRY2.bannerHeight;
1775
2081
  var MushiBanner = ({
1776
2082
  onPress,
@@ -1785,35 +2091,35 @@ var MushiBanner = ({
1785
2091
  },
1786
2092
  [onLayoutHeight]
1787
2093
  );
1788
- return /* @__PURE__ */ jsxs2(
1789
- View2,
2094
+ return /* @__PURE__ */ jsxs3(
2095
+ View3,
1790
2096
  {
1791
- style: s3.wrap,
2097
+ style: s4.wrap,
1792
2098
  onLayout: handleLayout,
1793
2099
  accessibilityRole: "header",
1794
2100
  accessibilityLabel: "Mushi feedback banner",
1795
2101
  children: [
1796
- /* @__PURE__ */ jsxs2(TouchableOpacity2, { style: s3.main, onPress, activeOpacity: 0.85, children: [
1797
- /* @__PURE__ */ jsx3(Text2, { style: s3.eyebrow, children: label.toUpperCase() }),
1798
- /* @__PURE__ */ jsx3(Text2, { style: s3.message, numberOfLines: 2, children: message }),
1799
- /* @__PURE__ */ jsx3(Text2, { style: s3.cta, children: MUSHI_COPY3.bugCta })
2102
+ /* @__PURE__ */ jsxs3(TouchableOpacity3, { style: s4.main, onPress, activeOpacity: 0.85, children: [
2103
+ /* @__PURE__ */ jsx4(Text3, { style: s4.eyebrow, children: label.toUpperCase() }),
2104
+ /* @__PURE__ */ jsx4(Text3, { style: s4.message, numberOfLines: 2, children: message }),
2105
+ /* @__PURE__ */ jsx4(Text3, { style: s4.cta, children: MUSHI_COPY3.bugCta })
1800
2106
  ] }),
1801
- onDismiss ? /* @__PURE__ */ jsx3(
1802
- TouchableOpacity2,
2107
+ onDismiss ? /* @__PURE__ */ jsx4(
2108
+ TouchableOpacity3,
1803
2109
  {
1804
- style: s3.dismiss,
2110
+ style: s4.dismiss,
1805
2111
  onPress: onDismiss,
1806
2112
  accessibilityRole: "button",
1807
2113
  accessibilityLabel: "Dismiss banner",
1808
2114
  hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
1809
- children: /* @__PURE__ */ jsx3(Text2, { style: s3.dismissText, children: "\xD7" })
2115
+ children: /* @__PURE__ */ jsx4(Text3, { style: s4.dismissText, children: "\xD7" })
1810
2116
  }
1811
2117
  ) : null
1812
2118
  ]
1813
2119
  }
1814
2120
  );
1815
2121
  };
1816
- var s3 = StyleSheet3.create({
2122
+ var s4 = StyleSheet4.create({
1817
2123
  wrap: {
1818
2124
  position: "absolute",
1819
2125
  left: 0,
@@ -2193,10 +2499,10 @@ function createRNSessionTracker(opts) {
2193
2499
 
2194
2500
  // src/version.ts
2195
2501
  var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
2196
- var MUSHI_SDK_VERSION = "0.23.0" ? "0.23.0" : "0.17.0";
2502
+ var MUSHI_SDK_VERSION = "0.24.1" ? "0.24.1" : "0.17.0";
2197
2503
 
2198
2504
  // src/provider.tsx
2199
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2505
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2200
2506
  var MushiContext = createContext(null);
2201
2507
  function nativeUserAgent() {
2202
2508
  const os = Platform5?.OS ?? "native";
@@ -2273,7 +2579,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2273
2579
  console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
2274
2580
  }
2275
2581
  const apiEndpoint = rawEndpoint ?? DEFAULT_API_ENDPOINT;
2276
- useEffect3(() => {
2582
+ useEffect4(() => {
2277
2583
  if (config.capture?.console !== false) {
2278
2584
  consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
2279
2585
  }
@@ -2335,11 +2641,10 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2335
2641
  networkRef.current?.restore();
2336
2642
  };
2337
2643
  }, []);
2338
- useEffect3(() => {
2644
+ useEffect4(() => {
2339
2645
  let unsubscribe;
2340
2646
  try {
2341
- const NetInfo = __require("@react-native-community/netinfo").default;
2342
- unsubscribe = NetInfo.addEventListener((state) => {
2647
+ unsubscribe = resolveNetInfo(config.netInfo)?.addEventListener((state) => {
2343
2648
  if (state.isConnected && state.isInternetReachable) {
2344
2649
  queueRef.current?.flush().catch(() => {
2345
2650
  });
@@ -2348,7 +2653,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2348
2653
  } catch {
2349
2654
  }
2350
2655
  return () => unsubscribe?.();
2351
- }, []);
2656
+ }, [config.netInfo]);
2352
2657
  const open = useCallback4(() => {
2353
2658
  setSheetPreferredTab("report");
2354
2659
  breadcrumbsRef.current.add({
@@ -2362,11 +2667,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2362
2667
  setSheetVisible(true);
2363
2668
  return;
2364
2669
  }
2365
- let vshot = null;
2366
- try {
2367
- vshot = __require("react-native-view-shot");
2368
- } catch {
2369
- }
2670
+ const vshot = resolveViewShot(config.viewShot);
2370
2671
  if (!vshot) {
2371
2672
  setSheetScreenshot(null);
2372
2673
  setSheetVisible(true);
@@ -2379,7 +2680,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2379
2680
  }).finally(() => {
2380
2681
  setSheetVisible(true);
2381
2682
  });
2382
- }, [config.capture?.screenshot]);
2683
+ }, [config.capture?.screenshot, config.viewShot]);
2383
2684
  const openAssistant = useCallback4(() => {
2384
2685
  if (!config.assistant?.enabled) return;
2385
2686
  setSheetPreferredTab("assistant");
@@ -2388,7 +2689,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2388
2689
  }, [config.assistant?.enabled]);
2389
2690
  const close = useCallback4(() => setSheetVisible(false), []);
2390
2691
  const attachTo = useCallback4(() => ({ onPress: open }), [open]);
2391
- useEffect3(() => {
2692
+ useEffect4(() => {
2392
2693
  const trigger2 = config.widget?.trigger ?? "button";
2393
2694
  if (trigger2 !== "shake" && trigger2 !== "both") return;
2394
2695
  let disposed = false;
@@ -2396,8 +2697,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2396
2697
  let lastShake = 0;
2397
2698
  const threshold = config.widget?.shakeThreshold ?? 2.7;
2398
2699
  try {
2399
- const mod = __require("expo-sensors");
2400
- if (!disposed) {
2700
+ const mod = resolveExpoSensors(config.expoSensors);
2701
+ if (mod && !disposed) {
2401
2702
  mod.Accelerometer.setUpdateInterval(120);
2402
2703
  const sub = mod.Accelerometer.addListener((evt) => {
2403
2704
  const g = Math.sqrt(evt.x * evt.x + evt.y * evt.y + evt.z * evt.z);
@@ -2415,7 +2716,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2415
2716
  disposed = true;
2416
2717
  cleanup?.();
2417
2718
  };
2418
- }, [config.widget?.trigger, config.widget?.shakeThreshold, open]);
2719
+ }, [config.widget?.trigger, config.widget?.shakeThreshold, config.expoSensors, open]);
2419
2720
  const submitReport = useCallback4(
2420
2721
  async (data) => {
2421
2722
  await reporterTokenReadyRef.current;
@@ -2456,6 +2757,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2456
2757
  id: newUuid2(),
2457
2758
  projectId: config.projectId,
2458
2759
  category: data.category,
2760
+ ...data.userCategory ? { userCategory: data.userCategory } : {},
2459
2761
  description: scrubPii3(data.description),
2460
2762
  environment: {
2461
2763
  userAgent: deviceInfo.systemName ?? "ReactNative",
@@ -2515,6 +2817,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2515
2817
  };
2516
2818
  } else {
2517
2819
  config.callbacks?.onReportSubmitted?.(report.id);
2820
+ void writeReporterFlag(config.projectId, "has_reports", "1");
2518
2821
  return { ok: true };
2519
2822
  }
2520
2823
  },
@@ -2525,7 +2828,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2525
2828
  const screenRef = useRef3(null);
2526
2829
  const activityQueueRef = useRef3([]);
2527
2830
  const rewardsFlushRef = useRef3(null);
2528
- useEffect3(() => {
2831
+ useEffect4(() => {
2529
2832
  const rewards = config.rewards;
2530
2833
  if (!rewards?.enabled || !rewards.trackActivity) return;
2531
2834
  const flushMs = Math.max(3e4, rewards.flushIntervalMs ?? 3e5);
@@ -2547,7 +2850,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2547
2850
  }, [config.rewards?.enabled, config.rewards?.trackActivity, config.rewards?.flushIntervalMs]);
2548
2851
  const eventTrackerRef = useRef3(null);
2549
2852
  const sessionTrackerRef = useRef3(null);
2550
- useEffect3(() => {
2853
+ useEffect4(() => {
2551
2854
  const getRoute = () => screenRef.current?.route ?? screenRef.current?.name ?? null;
2552
2855
  const tracker = createRNEventTracker({
2553
2856
  projectId: config.projectId,
@@ -2602,6 +2905,70 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2602
2905
  config.analytics?.flushIntervalMs,
2603
2906
  config.trackSessions
2604
2907
  ]);
2908
+ const reporterUpdatesRef = useRef3(
2909
+ createReporterUpdateHub(async () => {
2910
+ const client = apiClientRef.current;
2911
+ if (!client) return null;
2912
+ await reporterTokenReadyRef.current;
2913
+ const res = await reporterChannels2(client).getUpdates(reporterTokenRef.current);
2914
+ return res.ok ? res.data ?? null : null;
2915
+ })
2916
+ );
2917
+ useEffect4(() => {
2918
+ let sub;
2919
+ try {
2920
+ if (typeof AppState?.addEventListener === "function") {
2921
+ sub = AppState.addEventListener("change", (state) => {
2922
+ if (state === "active" && reporterUpdatesRef.current.listenerCount() > 0) void reporterUpdatesRef.current.emit();
2923
+ });
2924
+ }
2925
+ } catch {
2926
+ }
2927
+ return () => sub?.remove();
2928
+ }, []);
2929
+ const [toast, setToast] = useState2(null);
2930
+ const toastShownRef = useRef3(false);
2931
+ const toastEnabled = config.notifications?.toast !== false;
2932
+ const maybeShowToast = useCallback4(async () => {
2933
+ if (!toastEnabled || toastShownRef.current) return;
2934
+ if (await readReporterFlag(config.projectId, "has_reports") !== "1") return;
2935
+ const updates = await reporterUpdatesRef.current.emit();
2936
+ if (!updates) return;
2937
+ const now = Date.now();
2938
+ const show = reporterShouldShowToast({
2939
+ enabled: toastEnabled,
2940
+ unreadTotal: updates.unread_total,
2941
+ shownThisSession: toastShownRef.current,
2942
+ lastShownAt: Number(await readReporterFlag(config.projectId, "toast_shown_at")) || null,
2943
+ now
2944
+ });
2945
+ if (!show) return;
2946
+ toastShownRef.current = true;
2947
+ void writeReporterFlag(config.projectId, "toast_shown_at", String(now));
2948
+ const locale = resolveReporterLocale3(
2949
+ typeof navigator !== "undefined" ? navigator.language : void 0
2950
+ );
2951
+ setToast({ message: reporterToastMessage(updates, locale), reportId: updates.latest[0]?.report_id ?? null });
2952
+ }, [toastEnabled, config.projectId]);
2953
+ useEffect4(() => {
2954
+ void reporterTokenReadyRef.current.then(() => maybeShowToast());
2955
+ let sub;
2956
+ try {
2957
+ if (typeof AppState?.addEventListener === "function") {
2958
+ sub = AppState.addEventListener("change", (state) => {
2959
+ if (state === "active") void maybeShowToast();
2960
+ });
2961
+ }
2962
+ } catch {
2963
+ }
2964
+ return () => sub?.remove();
2965
+ }, [maybeShowToast]);
2966
+ const openMyReports = useCallback4(() => {
2967
+ setToast(null);
2968
+ setSheetScreenshot(null);
2969
+ setSheetPreferredTab("inbox");
2970
+ setSheetVisible(true);
2971
+ }, []);
2605
2972
  const instance = useMemo(
2606
2973
  () => ({
2607
2974
  open,
@@ -2705,6 +3072,37 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2705
3072
  await reporterTokenReadyRef.current;
2706
3073
  return markReporterReportRead(client, reporterTokenRef.current, reportId);
2707
3074
  },
3075
+ getReporterUpdates: () => reporterUpdatesRef.current.emit(),
3076
+ async loadMyReportDetail(reportId) {
3077
+ const client = apiClientRef.current;
3078
+ if (!client) return null;
3079
+ try {
3080
+ await reporterTokenReadyRef.current;
3081
+ const res = await reporterChannels2(client).getReport(reportId, reporterTokenRef.current);
3082
+ if (res.ok) return res.data ?? null;
3083
+ return res.error?.status === 404 && res.error.code !== "NOT_FOUND" ? "unsupported" : null;
3084
+ } catch {
3085
+ return null;
3086
+ }
3087
+ },
3088
+ emailPrefill() {
3089
+ return config.notifications?.emailFromIdentity ? userRef.current?.email ?? null : null;
3090
+ },
3091
+ openMyReports,
3092
+ onReporterUpdate: (cb) => reporterUpdatesRef.current.subscribe(cb),
3093
+ async getNotificationPrefs() {
3094
+ const client = apiClientRef.current;
3095
+ if (!client) return null;
3096
+ await reporterTokenReadyRef.current;
3097
+ const res = await reporterChannels2(client).getPrefs(reporterTokenRef.current);
3098
+ return res.ok ? res.data ?? null : null;
3099
+ },
3100
+ async setNotificationPrefs(update) {
3101
+ const client = apiClientRef.current;
3102
+ if (!client) return { ok: false, error: { code: "NOT_INITIALIZED", message: "Mushi is not ready yet" } };
3103
+ await reporterTokenReadyRef.current;
3104
+ return reporterChannels2(client).setPrefs(reporterTokenRef.current, update);
3105
+ },
2708
3106
  async replyToReport(reportId, body) {
2709
3107
  const client = apiClientRef.current;
2710
3108
  if (!client) return null;
@@ -2775,14 +3173,14 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2775
3173
  return res.ok ? res.data : null;
2776
3174
  }
2777
3175
  }),
2778
- [open, close, attachTo, submitReport, openAssistant, config.rewards?.enabled, config.assistant?.enabled]
3176
+ [open, close, attachTo, submitReport, openAssistant, openMyReports, config.rewards?.enabled, config.assistant?.enabled, config.notifications?.emailFromIdentity]
2779
3177
  );
2780
3178
  const trigger = config.widget?.trigger ?? "button";
2781
3179
  const showBanner = trigger === "banner" && !bannerDismissed;
2782
3180
  const showFab = !showBanner && (trigger === "button" || trigger === "both" || trigger === "auto" || trigger === "edge-tab");
2783
- return /* @__PURE__ */ jsxs3(MushiContext.Provider, { value: instance, children: [
3181
+ return /* @__PURE__ */ jsxs4(MushiContext.Provider, { value: instance, children: [
2784
3182
  children,
2785
- showBanner ? /* @__PURE__ */ jsx4(
3183
+ showBanner ? /* @__PURE__ */ jsx5(
2786
3184
  MushiBanner,
2787
3185
  {
2788
3186
  onPress: open,
@@ -2791,7 +3189,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2791
3189
  label: config.widget?.banner?.label
2792
3190
  }
2793
3191
  ) : null,
2794
- showFab && /* @__PURE__ */ jsx4(
3192
+ showFab && /* @__PURE__ */ jsx5(
2795
3193
  MushiFloatingButton,
2796
3194
  {
2797
3195
  onPress: open,
@@ -2799,7 +3197,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2799
3197
  inset: config.widget?.inset
2800
3198
  }
2801
3199
  ),
2802
- /* @__PURE__ */ jsx4(
3200
+ /* @__PURE__ */ jsx5(
2803
3201
  MushiBottomSheet,
2804
3202
  {
2805
3203
  visible: sheetVisible,
@@ -2813,9 +3211,20 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2813
3211
  assistantGreeting: config.assistant?.greeting,
2814
3212
  assistantSuggestions: config.assistant?.suggestions,
2815
3213
  inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0,
3214
+ theme: config.widget?.theme,
3215
+ minDescriptionLength: config.widget?.minDescriptionLength,
3216
+ emailOptIn: config.notifications?.email !== false
3217
+ }
3218
+ ),
3219
+ toast ? /* @__PURE__ */ jsx5(
3220
+ MushiUpdateToast,
3221
+ {
3222
+ message: toast.message,
3223
+ onView: openMyReports,
3224
+ onDismiss: () => setToast(null),
2816
3225
  theme: config.widget?.theme
2817
3226
  }
2818
- )
3227
+ ) : null
2819
3228
  ] });
2820
3229
  }
2821
3230
  function useMushiContext() {
@@ -2866,10 +3275,10 @@ function MushiTrigger({ children }) {
2866
3275
  }
2867
3276
 
2868
3277
  // src/capture/navigation-capture.ts
2869
- import { useEffect as useEffect4, useRef as useRef4 } from "react";
3278
+ import { useEffect as useEffect5, useRef as useRef4 } from "react";
2870
3279
  function useNavigationCapture(navigationRef) {
2871
3280
  const historyRef = useRef4([]);
2872
- useEffect4(() => {
3281
+ useEffect5(() => {
2873
3282
  if (!navigationRef?.addListener || !navigationRef?.getCurrentRoute) return;
2874
3283
  const unsubscribe = navigationRef.addListener("state", () => {
2875
3284
  const route = navigationRef.getCurrentRoute?.();