@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.cjs CHANGED
@@ -48,12 +48,184 @@ __export(index_exports, {
48
48
  module.exports = __toCommonJS(index_exports);
49
49
 
50
50
  // src/provider.tsx
51
- var import_react4 = require("react");
52
- var import_react_native5 = require("react-native");
53
- var import_core9 = require("@mushi-mushi/core");
51
+ var import_react5 = require("react");
52
+ var import_react_native6 = require("react-native");
53
+ var import_core10 = require("@mushi-mushi/core");
54
+ var import_reporter_channels2 = require("@mushi-mushi/core/reporter-channels");
55
+ var import_reporter_ui3 = require("@mushi-mushi/core/reporter-ui");
54
56
 
55
- // src/capture/console-capture.ts
57
+ // src/storage/reporter-flags.ts
58
+ async function readReporterFlag(projectId, flag) {
59
+ try {
60
+ const store = (await import("@react-native-async-storage/async-storage")).default;
61
+ return await store.getItem(`@mushi:${flag}:${projectId}`);
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+ async function writeReporterFlag(projectId, flag, value) {
67
+ try {
68
+ const store = (await import("@react-native-async-storage/async-storage")).default;
69
+ await store.setItem(`@mushi:${flag}:${projectId}`, value);
70
+ } catch {
71
+ }
72
+ }
73
+
74
+ // src/components/MushiUpdateToast.tsx
75
+ var import_react = require("react");
76
+ var import_react_native = require("react-native");
77
+ var import_core2 = require("@mushi-mushi/core");
78
+ var import_reporter_ui = require("@mushi-mushi/core/reporter-ui");
79
+
80
+ // src/theme.ts
56
81
  var import_core = require("@mushi-mushi/core");
82
+ function hexToRgb(hex) {
83
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})([0-9a-f]{2})?$/i.exec(hex.trim());
84
+ if (!m) return null;
85
+ const h = m[1].length === 3 ? m[1].split("").map((c) => c + c).join("") : m[1];
86
+ return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
87
+ }
88
+ function luminance([r, g, b]) {
89
+ const lin = (c) => {
90
+ const s5 = c / 255;
91
+ return s5 <= 0.03928 ? s5 / 12.92 : ((s5 + 0.055) / 1.055) ** 2.4;
92
+ };
93
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
94
+ }
95
+ function contrastingText(bg, fallback) {
96
+ const rgb = hexToRgb(bg);
97
+ if (!rgb) return fallback;
98
+ const l = luminance(rgb);
99
+ return 1.05 / (l + 0.05) >= (l + 0.05) / 0.05 ? "#FFFFFF" : "#000000";
100
+ }
101
+ function resolveRNTheme(dark, override = {}) {
102
+ const pal = (0, import_core.mushiPalette)(dark ? "dark" : "light");
103
+ const base = {
104
+ bg: dark ? pal.paper : pal.paperRaised,
105
+ fg: pal.ink,
106
+ muted: pal.inkMuted,
107
+ surface: dark ? pal.paperRaised : pal.paper,
108
+ border: pal.ruleStrong,
109
+ accent: pal.ink,
110
+ accentFg: dark ? pal.paper : pal.paperRaised,
111
+ success: pal.ok,
112
+ error: pal.danger,
113
+ fontFamily: void 0,
114
+ radius: import_core.MUSHI_RADIUS.card
115
+ };
116
+ const defined = Object.fromEntries(
117
+ Object.entries(override).filter(([, v]) => v !== void 0 && v !== null && v !== "")
118
+ );
119
+ const merged = { ...base, ...defined };
120
+ if (defined.accent && !defined.accentFg) merged.accentFg = contrastingText(defined.accent, base.accentFg);
121
+ return {
122
+ ...merged,
123
+ backdrop: dark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.35)",
124
+ disabled: dark ? import_core.MUSHI_CONTROL_DISABLED.dark : import_core.MUSHI_CONTROL_DISABLED.light,
125
+ disabledFg: pal.inkFaint
126
+ };
127
+ }
128
+
129
+ // src/components/MushiUpdateToast.tsx
130
+ var import_jsx_runtime = require("react/jsx-runtime");
131
+ var AUTO_HIDE_MS = 8e3;
132
+ var MushiUpdateToast = ({ message, onView, onDismiss, theme }) => {
133
+ const colors = resolveRNTheme((0, import_react_native.useColorScheme)() === "dark", theme);
134
+ const copy = (0, import_reporter_ui.reporterCopy)(
135
+ (0, import_reporter_ui.resolveReporterLocale)(typeof navigator !== "undefined" ? navigator.language : void 0)
136
+ ).ui;
137
+ const font = colors.fontFamily ? { fontFamily: colors.fontFamily } : null;
138
+ (0, import_react.useEffect)(() => {
139
+ const timer = setTimeout(onDismiss, AUTO_HIDE_MS);
140
+ return () => clearTimeout(timer);
141
+ }, [onDismiss]);
142
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
143
+ import_react_native.View,
144
+ {
145
+ style: [s.toast, { backgroundColor: colors.bg, borderColor: colors.border, borderRadius: colors.radius }],
146
+ accessibilityLiveRegion: "polite",
147
+ accessibilityRole: "alert",
148
+ children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.Text, { style: [s.text, { color: colors.fg }, font], numberOfLines: 2, children: message }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
151
+ import_react_native.TouchableOpacity,
152
+ {
153
+ onPress: onView,
154
+ accessibilityRole: "button",
155
+ style: [s.btn, { backgroundColor: colors.accent, borderRadius: colors.radius }],
156
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.Text, { style: [{ color: colors.accentFg, fontWeight: "600" }, font], children: copy.view })
157
+ }
158
+ ),
159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
160
+ import_react_native.TouchableOpacity,
161
+ {
162
+ onPress: onDismiss,
163
+ accessibilityRole: "button",
164
+ accessibilityLabel: copy.done,
165
+ hitSlop: { top: 10, bottom: 10, left: 10, right: 10 },
166
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.Text, { style: [{ color: colors.muted, fontSize: 18 }, font], children: "\xD7" })
167
+ }
168
+ )
169
+ ]
170
+ }
171
+ );
172
+ };
173
+ var s = import_react_native.StyleSheet.create({
174
+ toast: {
175
+ position: "absolute",
176
+ left: 16,
177
+ right: 16,
178
+ bottom: 32,
179
+ flexDirection: "row",
180
+ alignItems: "center",
181
+ gap: 10,
182
+ paddingVertical: 10,
183
+ paddingHorizontal: 14,
184
+ borderWidth: import_react_native.StyleSheet.hairlineWidth,
185
+ shadowColor: import_core2.MUSHI_SHADOW_INK,
186
+ shadowOpacity: 0.18,
187
+ shadowRadius: 12,
188
+ shadowOffset: { width: 0, height: 6 },
189
+ elevation: 6
190
+ },
191
+ text: { flex: 1, fontSize: 14 },
192
+ btn: { paddingHorizontal: 12, paddingVertical: 6 }
193
+ });
194
+
195
+ // src/optional-modules.ts
196
+ function unwrap(mod, key) {
197
+ if (!mod || typeof mod !== "object" && typeof mod !== "function") return null;
198
+ if (key in mod) return mod;
199
+ const def = mod.default;
200
+ return def && (typeof def === "object" || typeof def === "function") && key in def ? def : null;
201
+ }
202
+ function resolveNetInfo(injected) {
203
+ if (injected) return unwrap(injected, "addEventListener");
204
+ try {
205
+ return unwrap(require("@react-native-community/netinfo"), "addEventListener");
206
+ } catch {
207
+ return null;
208
+ }
209
+ }
210
+ function resolveViewShot(injected) {
211
+ if (injected) return unwrap(injected, "captureScreen");
212
+ try {
213
+ return unwrap(require("react-native-view-shot"), "captureScreen");
214
+ } catch {
215
+ return null;
216
+ }
217
+ }
218
+ function resolveExpoSensors(injected) {
219
+ if (injected) return unwrap(injected, "Accelerometer");
220
+ try {
221
+ return unwrap(require("expo-sensors"), "Accelerometer");
222
+ } catch {
223
+ return null;
224
+ }
225
+ }
226
+
227
+ // src/capture/console-capture.ts
228
+ var import_core3 = require("@mushi-mushi/core");
57
229
  function setupConsoleCapture(maxEntries = 100) {
58
230
  const entries = [];
59
231
  const methods = ["log", "warn", "error"];
@@ -64,7 +236,7 @@ function setupConsoleCapture(maxEntries = 100) {
64
236
  console[method] = (...args) => {
65
237
  entries.push({
66
238
  level: method,
67
- message: (0, import_core.scrubPii)(args.map(String).join(" ").slice(0, 500)),
239
+ message: (0, import_core3.scrubPii)(args.map(String).join(" ").slice(0, 500)),
68
240
  timestamp: Date.now()
69
241
  });
70
242
  if (entries.length > maxEntries) entries.shift();
@@ -140,28 +312,28 @@ function stripQueryParams(url) {
140
312
  }
141
313
 
142
314
  // src/capture/device-info.ts
143
- var import_react_native = require("react-native");
315
+ var import_react_native2 = require("react-native");
144
316
  function getDeviceInfo() {
145
- const { width, height } = import_react_native.Dimensions.get("window");
317
+ const { width, height } = import_react_native2.Dimensions.get("window");
146
318
  const locale = (
147
319
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
148
- import_react_native.NativeModules.SettingsManager?.settings?.AppleLocale ?? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
149
- import_react_native.NativeModules.I18nManager?.localeIdentifier ?? "en"
320
+ import_react_native2.NativeModules.SettingsManager?.settings?.AppleLocale ?? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
321
+ import_react_native2.NativeModules.I18nManager?.localeIdentifier ?? "en"
150
322
  );
151
323
  let timezone = "UTC";
152
324
  try {
153
325
  timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
154
326
  } catch {
155
327
  }
156
- const appVersion = import_react_native.NativeModules.RNDeviceInfo?.appVersion ?? import_react_native.NativeModules.RNBuildConfig?.VERSION_NAME ?? String(import_react_native.Platform.Version);
328
+ const appVersion = import_react_native2.NativeModules.RNDeviceInfo?.appVersion ?? import_react_native2.NativeModules.RNBuildConfig?.VERSION_NAME ?? String(import_react_native2.Platform.Version);
157
329
  return {
158
- platform: import_react_native.Platform.OS,
159
- osVersion: import_react_native.Platform.Version,
330
+ platform: import_react_native2.Platform.OS,
331
+ osVersion: import_react_native2.Platform.Version,
160
332
  screenWidth: width,
161
333
  screenHeight: height,
162
- pixelRatio: import_react_native.PixelRatio.get(),
163
- fontScale: import_react_native.PixelRatio.getFontScale(),
164
- systemName: import_react_native.Platform.OS,
334
+ pixelRatio: import_react_native2.PixelRatio.get(),
335
+ fontScale: import_react_native2.PixelRatio.getFontScale(),
336
+ systemName: import_react_native2.Platform.OS,
165
337
  locale,
166
338
  timezone,
167
339
  appVersion
@@ -193,7 +365,7 @@ function getDeviceFingerprintHash(device) {
193
365
  }
194
366
 
195
367
  // src/storage/async-storage-queue.ts
196
- var import_core2 = require("@mushi-mushi/core");
368
+ var import_core4 = require("@mushi-mushi/core");
197
369
 
198
370
  // src/storage/secure-storage.ts
199
371
  var REPORTER_TOKEN_KEY = "@mushi:reporter_token";
@@ -393,19 +565,19 @@ var AsyncStorageQueue = class {
393
565
  scrubReportPii(report) {
394
566
  const next = { ...report };
395
567
  if (typeof next.description === "string") {
396
- next.description = (0, import_core2.scrubPii)(next.description);
568
+ next.description = (0, import_core4.scrubPii)(next.description);
397
569
  }
398
570
  if (typeof next.summary === "string") {
399
- next.summary = (0, import_core2.scrubPii)(next.summary);
571
+ next.summary = (0, import_core4.scrubPii)(next.summary);
400
572
  }
401
573
  if (Array.isArray(next.breadcrumbs)) {
402
574
  next.breadcrumbs = next.breadcrumbs.map(
403
- (b) => typeof b?.message === "string" ? { ...b, message: (0, import_core2.scrubPii)(b.message) } : b
575
+ (b) => typeof b?.message === "string" ? { ...b, message: (0, import_core4.scrubPii)(b.message) } : b
404
576
  );
405
577
  }
406
578
  if (Array.isArray(next.consoleLogs)) {
407
579
  next.consoleLogs = next.consoleLogs.map(
408
- (entry) => typeof entry?.message === "string" ? { ...entry, message: (0, import_core2.scrubPii)(entry.message) } : entry
580
+ (entry) => typeof entry?.message === "string" ? { ...entry, message: (0, import_core4.scrubPii)(entry.message) } : entry
409
581
  );
410
582
  }
411
583
  return next;
@@ -447,7 +619,7 @@ async function sendWithRetry(item, ctx, attempt = 0) {
447
619
  try {
448
620
  const res = await fetch(`${ctx.apiEndpoint}/v1/reports`, {
449
621
  method: "POST",
450
- headers: (0, import_core2.buildSdkIngestHeaders)({
622
+ headers: (0, import_core4.buildSdkIngestHeaders)({
451
623
  apiKey: ctx.apiKey,
452
624
  projectId: ctx.projectId,
453
625
  sdkPackage: ctx.sdkPackage,
@@ -456,7 +628,7 @@ async function sendWithRetry(item, ctx, attempt = 0) {
456
628
  internalKind
457
629
  }),
458
630
  body: JSON.stringify(item.report),
459
- [import_core2.MUSHI_INTERNAL_INIT_MARKER]: internalKind
631
+ [import_core4.MUSHI_INTERNAL_INIT_MARKER]: internalKind
460
632
  });
461
633
  if ((res.status === 429 || res.status >= 500) && attempt < 3) {
462
634
  await sleep(delay);
@@ -476,62 +648,14 @@ function sleep(ms) {
476
648
  }
477
649
 
478
650
  // src/components/MushiBottomSheet.tsx
479
- var import_react = require("react");
480
- var import_react_native2 = require("react-native");
481
- var import_core4 = require("@mushi-mushi/core");
482
- var import_reporter_ui = require("@mushi-mushi/core/reporter-ui");
651
+ var import_react2 = require("react");
652
+ var import_react_native3 = require("react-native");
653
+ var import_core5 = require("@mushi-mushi/core");
654
+ var import_reporter_ui2 = require("@mushi-mushi/core/reporter-ui");
483
655
  var import_i18n = require("@mushi-mushi/web/i18n");
484
656
 
485
- // src/theme.ts
486
- var import_core3 = require("@mushi-mushi/core");
487
- function hexToRgb(hex) {
488
- const m = /^#([0-9a-f]{3}|[0-9a-f]{6})([0-9a-f]{2})?$/i.exec(hex.trim());
489
- if (!m) return null;
490
- const h = m[1].length === 3 ? m[1].split("").map((c) => c + c).join("") : m[1];
491
- return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
492
- }
493
- function luminance([r, g, b]) {
494
- const lin = (c) => {
495
- const s4 = c / 255;
496
- return s4 <= 0.03928 ? s4 / 12.92 : ((s4 + 0.055) / 1.055) ** 2.4;
497
- };
498
- return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
499
- }
500
- function contrastingText(bg, fallback) {
501
- const rgb = hexToRgb(bg);
502
- if (!rgb) return fallback;
503
- const l = luminance(rgb);
504
- return 1.05 / (l + 0.05) >= (l + 0.05) / 0.05 ? "#FFFFFF" : "#000000";
505
- }
506
- function resolveRNTheme(dark, override = {}) {
507
- const pal = (0, import_core3.mushiPalette)(dark ? "dark" : "light");
508
- const base = {
509
- bg: dark ? pal.paper : pal.paperRaised,
510
- fg: pal.ink,
511
- muted: pal.inkMuted,
512
- surface: dark ? pal.paperRaised : pal.paper,
513
- border: pal.ruleStrong,
514
- accent: pal.ink,
515
- accentFg: dark ? pal.paper : pal.paperRaised,
516
- success: pal.ok,
517
- error: pal.danger,
518
- fontFamily: void 0,
519
- radius: import_core3.MUSHI_RADIUS.card
520
- };
521
- const defined = Object.fromEntries(
522
- Object.entries(override).filter(([, v]) => v !== void 0 && v !== null && v !== "")
523
- );
524
- const merged = { ...base, ...defined };
525
- if (defined.accent && !defined.accentFg) merged.accentFg = contrastingText(defined.accent, base.accentFg);
526
- return {
527
- ...merged,
528
- backdrop: dark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.35)",
529
- disabled: dark ? import_core3.MUSHI_CONTROL_DISABLED.dark : import_core3.MUSHI_CONTROL_DISABLED.light,
530
- disabledFg: pal.inkFaint
531
- };
532
- }
533
-
534
657
  // src/reporter-thread.ts
658
+ var import_reporter_channels = require("@mushi-mushi/core/reporter-channels");
535
659
  var THREAD_LOAD_TIMEOUT_MS = 12e3;
536
660
  function settleWithin(p, ms) {
537
661
  return new Promise((resolve) => {
@@ -559,6 +683,9 @@ function loadReporterThread(load, reportId, timeoutMs = THREAD_LOAD_TIMEOUT_MS)
559
683
  }
560
684
  async function markReporterReportRead(client, reporterToken, reportId) {
561
685
  try {
686
+ const v2 = await (0, import_reporter_channels.reporterChannels)(client).markReportRead(reportId, reporterToken);
687
+ if (v2.ok) return v2.data?.marked_read ?? 0;
688
+ if (v2.error?.status !== 404) return 0;
562
689
  const res = await client.listNotifications(reporterToken, { limit: 100 });
563
690
  if (!res.ok) return 0;
564
691
  const rows = res.data?.notifications ?? [];
@@ -573,21 +700,59 @@ async function markReporterReportRead(client, reporterToken, reportId) {
573
700
  return 0;
574
701
  }
575
702
  }
703
+ function createReporterUpdateHub(fetchUpdates) {
704
+ const listeners = /* @__PURE__ */ new Set();
705
+ async function emit() {
706
+ let updates = null;
707
+ try {
708
+ updates = await fetchUpdates();
709
+ } catch {
710
+ return null;
711
+ }
712
+ if (updates) {
713
+ for (const cb of listeners) {
714
+ try {
715
+ cb(updates);
716
+ } catch {
717
+ }
718
+ }
719
+ }
720
+ return updates;
721
+ }
722
+ return {
723
+ subscribe(cb) {
724
+ listeners.add(cb);
725
+ void emit();
726
+ return () => {
727
+ listeners.delete(cb);
728
+ };
729
+ },
730
+ emit,
731
+ listenerCount: () => listeners.size
732
+ };
733
+ }
576
734
 
577
735
  // src/components/MushiBottomSheet.tsx
578
- var import_jsx_runtime = require("react/jsx-runtime");
579
- var { height: SCREEN_HEIGHT } = import_react_native2.Dimensions.get("window");
736
+ var import_jsx_runtime2 = require("react/jsx-runtime");
737
+ var { height: SCREEN_HEIGHT } = import_react_native3.Dimensions.get("window");
580
738
  var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
581
739
  var DISMISS_THRESHOLD = 80;
582
- var MIN_DESCRIPTION = 20;
583
- var CATEGORY_KEYS = ["bug", "slow", "visual", "confusing", "other"];
584
740
  var CATEGORY_EMOJI = {
585
741
  bug: "\u{1F41B}",
586
742
  slow: "\u{1F422}",
587
743
  visual: "\u{1F3A8}",
588
744
  confusing: "\u{1F615}",
589
- other: "\u{1F4AC}"
745
+ idea: "\u{1F4A1}"
590
746
  };
747
+ function commentsToTimeline(comments) {
748
+ return comments.map((c) => ({
749
+ kind: c.author_kind === "reporter" ? "reporter_comment" : "comment",
750
+ at: c.created_at,
751
+ text: c.body,
752
+ body: c.body,
753
+ comment_id: c.id
754
+ }));
755
+ }
591
756
  var MushiBottomSheet = ({
592
757
  visible,
593
758
  onClose,
@@ -596,52 +761,66 @@ var MushiBottomSheet = ({
596
761
  onClearScreenshot,
597
762
  screenshotSensitiveHint,
598
763
  assistantEnabled = false,
599
- assistantLabel = import_core4.MUSHI_COPY.assistantTab,
764
+ assistantLabel = import_core5.MUSHI_COPY.assistantTab,
600
765
  assistantGreeting,
601
766
  assistantSuggestions = [],
602
767
  inboxPollIntervalMs = 0,
603
- theme
768
+ theme,
769
+ minDescriptionLength,
770
+ emailOptIn = true
604
771
  }) => {
605
772
  const mushi = useMushiContext();
606
773
  const t = (0, import_i18n.getLocale)();
607
- const locale = (0, import_reporter_ui.resolveReporterLocale)(
774
+ const locale = (0, import_reporter_ui2.resolveReporterLocale)(
608
775
  typeof navigator !== "undefined" ? navigator.language : void 0
609
776
  );
610
- const rc = (0, import_reporter_ui.reporterCopy)(locale);
777
+ const rc = (0, import_reporter_ui2.reporterCopy)(locale);
611
778
  const greeting = assistantGreeting ?? t.assistant.defaultGreeting;
612
- const scheme = (0, import_react_native2.useColorScheme)();
779
+ const scheme = (0, import_react_native3.useColorScheme)();
613
780
  const dark = scheme === "dark";
614
- const translateY = (0, import_react.useRef)(new import_react_native2.Animated.Value(SHEET_HEIGHT)).current;
615
- const backdropOpacity = (0, import_react.useRef)(new import_react_native2.Animated.Value(0)).current;
616
- const [category, setCategory] = (0, import_react.useState)(null);
617
- const [description, setDescription] = (0, import_react.useState)("");
618
- const [phase, setPhase] = (0, import_react.useState)("form");
619
- const [submitError, setSubmitError] = (0, import_react.useState)(null);
620
- const [sheetTab, setSheetTab] = (0, import_react.useState)("report");
621
- const [inboxReports, setInboxReports] = (0, import_react.useState)([]);
622
- const [inboxLoaded, setInboxLoaded] = (0, import_react.useState)(false);
623
- const [inboxLoading, setInboxLoading] = (0, import_react.useState)(false);
624
- const [selectedReportId, setSelectedReportId] = (0, import_react.useState)(null);
625
- const selectedRef = (0, import_react.useRef)(null);
626
- const [threadComments, setThreadComments] = (0, import_react.useState)([]);
627
- const [threadStatus, setThreadStatus] = (0, import_react.useState)("idle");
628
- const [replyText, setReplyText] = (0, import_react.useState)("");
629
- const [replyState, setReplyState] = (0, import_react.useState)("idle");
630
- const [feedbackSending, setFeedbackSending] = (0, import_react.useState)(false);
631
- const [feedbackError, setFeedbackError] = (0, import_react.useState)(false);
632
- const [assistantInput, setAssistantInput] = (0, import_react.useState)("");
633
- const [assistantThreadId, setAssistantThreadId] = (0, import_react.useState)(null);
634
- const [assistantSending, setAssistantSending] = (0, import_react.useState)(false);
635
- const [assistantError, setAssistantError] = (0, import_react.useState)(null);
636
- const [assistantTurns, setAssistantTurns] = (0, import_react.useState)([]);
637
- const [screenshotAttached, setScreenshotAttached] = (0, import_react.useState)(true);
638
- const loadInbox = (0, import_react.useCallback)(
781
+ const translateY = (0, import_react2.useRef)(new import_react_native3.Animated.Value(SHEET_HEIGHT)).current;
782
+ const backdropOpacity = (0, import_react2.useRef)(new import_react_native3.Animated.Value(0)).current;
783
+ const [category, setCategory] = (0, import_react2.useState)(null);
784
+ const [description, setDescription] = (0, import_react2.useState)("");
785
+ const [phase, setPhase] = (0, import_react2.useState)("form");
786
+ const [submitError, setSubmitError] = (0, import_react2.useState)(null);
787
+ const [sheetTab, setSheetTab] = (0, import_react2.useState)("report");
788
+ const [inboxReports, setInboxReports] = (0, import_react2.useState)([]);
789
+ const [inboxLoaded, setInboxLoaded] = (0, import_react2.useState)(false);
790
+ const [inboxLoading, setInboxLoading] = (0, import_react2.useState)(false);
791
+ const [selectedReportId, setSelectedReportId] = (0, import_react2.useState)(null);
792
+ const selectedRef = (0, import_react2.useRef)(null);
793
+ const [threadItems, setThreadItems] = (0, import_react2.useState)([]);
794
+ const [threadStatus, setThreadStatus] = (0, import_react2.useState)("idle");
795
+ const [replyText, setReplyText] = (0, import_react2.useState)("");
796
+ const [replyState, setReplyState] = (0, import_react2.useState)("idle");
797
+ const [feedbackSending, setFeedbackSending] = (0, import_react2.useState)(false);
798
+ const [feedbackError, setFeedbackError] = (0, import_react2.useState)(false);
799
+ const [assistantInput, setAssistantInput] = (0, import_react2.useState)("");
800
+ const [assistantThreadId, setAssistantThreadId] = (0, import_react2.useState)(null);
801
+ const [assistantSending, setAssistantSending] = (0, import_react2.useState)(false);
802
+ const [assistantError, setAssistantError] = (0, import_react2.useState)(null);
803
+ const [assistantTurns, setAssistantTurns] = (0, import_react2.useState)([]);
804
+ const [screenshotAttached, setScreenshotAttached] = (0, import_react2.useState)(true);
805
+ const [emailState, setEmailState] = (0, import_react2.useState)("hidden");
806
+ const [emailInput, setEmailInput] = (0, import_react2.useState)("");
807
+ const [reduceMotion, setReduceMotion] = (0, import_react2.useState)(false);
808
+ (0, import_react2.useEffect)(() => {
809
+ let alive = true;
810
+ import_react_native3.AccessibilityInfo?.isReduceMotionEnabled?.().then((on) => alive && setReduceMotion(Boolean(on))).catch(() => void 0);
811
+ const sub = import_react_native3.AccessibilityInfo?.addEventListener?.("reduceMotionChanged", (on) => setReduceMotion(Boolean(on)));
812
+ return () => {
813
+ alive = false;
814
+ sub?.remove?.();
815
+ };
816
+ }, []);
817
+ const loadInbox = (0, import_react2.useCallback)(
639
818
  async (opts = {}) => {
640
819
  if (!mushi?.listMyReports) return;
641
820
  if (!opts.silent) setInboxLoading(true);
642
821
  try {
643
822
  const rows = await mushi.listMyReports();
644
- setInboxReports(rows.filter((r) => !(0, import_reporter_ui.reporterStatus)(r, locale).hidden));
823
+ setInboxReports(rows.filter((r) => !(0, import_reporter_ui2.reporterStatus)(r, locale).hidden));
645
824
  setInboxLoaded(true);
646
825
  } catch {
647
826
  } finally {
@@ -650,38 +829,45 @@ var MushiBottomSheet = ({
650
829
  },
651
830
  [mushi, locale]
652
831
  );
653
- (0, import_react.useEffect)(() => {
832
+ (0, import_react2.useEffect)(() => {
654
833
  if (visible) {
655
834
  setScreenshotAttached(true);
656
835
  setSheetTab(preferredTab);
657
836
  if (preferredTab === "inbox") void loadInbox();
658
837
  }
659
838
  }, [visible, preferredTab, loadInbox]);
660
- (0, import_react.useEffect)(() => {
839
+ (0, import_react2.useEffect)(() => {
661
840
  if (!visible || sheetTab !== "inbox" || inboxPollIntervalMs <= 0) return;
662
841
  const timer = setInterval(() => {
663
842
  void loadInbox({ silent: true });
664
843
  }, inboxPollIntervalMs);
665
844
  return () => clearInterval(timer);
666
845
  }, [visible, sheetTab, inboxPollIntervalMs, loadInbox]);
667
- const openThread = (0, import_react.useCallback)(
846
+ const openThread = (0, import_react2.useCallback)(
668
847
  async (reportId, opts = {}) => {
669
848
  selectedRef.current = reportId;
670
849
  setSelectedReportId(reportId);
671
850
  if (!opts.silent) {
672
- setThreadComments([]);
851
+ setThreadItems([]);
673
852
  setThreadStatus("loading");
674
853
  setReplyState("idle");
675
854
  setFeedbackError(false);
676
855
  }
677
- const load = mushi?.loadMyThread ?? mushi?.listMyComments;
678
- const comments = load ? await loadReporterThread(load, reportId) : null;
856
+ let items = null;
857
+ const detail = mushi?.loadMyReportDetail ? await settleWithin(mushi.loadMyReportDetail(reportId), THREAD_LOAD_TIMEOUT_MS) : "unsupported";
858
+ if (detail && detail !== "unsupported") {
859
+ items = detail.timeline;
860
+ } else if (detail === "unsupported") {
861
+ const load = mushi?.loadMyThread ?? mushi?.listMyComments;
862
+ const comments = load ? await loadReporterThread(load, reportId) : null;
863
+ items = comments ? commentsToTimeline(comments) : null;
864
+ }
679
865
  if (selectedRef.current !== reportId) return;
680
- if (comments === null) {
866
+ if (items === null) {
681
867
  setThreadStatus((prev) => opts.silent && prev === "ready" ? "ready" : "error");
682
868
  return;
683
869
  }
684
- setThreadComments(comments);
870
+ setThreadItems(items);
685
871
  setThreadStatus("ready");
686
872
  if (!opts.silent && mushi?.markReportRead) {
687
873
  mushi.markReportRead(reportId).then((marked) => {
@@ -693,12 +879,12 @@ var MushiBottomSheet = ({
693
879
  },
694
880
  [mushi]
695
881
  );
696
- const closeThread = (0, import_react.useCallback)(() => {
882
+ const closeThread = (0, import_react2.useCallback)(() => {
697
883
  selectedRef.current = null;
698
884
  setSelectedReportId(null);
699
885
  setThreadStatus("idle");
700
886
  }, []);
701
- const sendReply = (0, import_react.useCallback)(async () => {
887
+ const sendReply = (0, import_react2.useCallback)(async () => {
702
888
  const body = replyText.trim();
703
889
  const reportId = selectedReportId;
704
890
  if (!mushi?.replyToReport || !reportId || !body || replyState === "sending") return;
@@ -712,14 +898,17 @@ var MushiBottomSheet = ({
712
898
  setReplyText("");
713
899
  setReplyState("idle");
714
900
  if (selectedRef.current === reportId) {
715
- setThreadComments((prev) => [...prev, comment]);
901
+ setThreadItems((prev) => [
902
+ ...prev,
903
+ { kind: "reporter_comment", at: comment.created_at, text: comment.body, body: comment.body, comment_id: comment.id }
904
+ ]);
716
905
  void openThread(reportId, { silent: true });
717
906
  }
718
907
  } catch {
719
908
  setReplyState("failed");
720
909
  }
721
910
  }, [mushi, selectedReportId, replyText, replyState, openThread]);
722
- const submitFeedback = (0, import_react.useCallback)(
911
+ const submitFeedback = (0, import_react2.useCallback)(
723
912
  async (signal) => {
724
913
  const reportId = selectedReportId;
725
914
  if (!mushi?.submitFeedbackSignal || !reportId || feedbackSending) return;
@@ -741,7 +930,7 @@ var MushiBottomSheet = ({
741
930
  },
742
931
  [mushi, selectedReportId, feedbackSending, loadInbox, openThread]
743
932
  );
744
- const sendAssistant = (0, import_react.useCallback)(async (message) => {
933
+ const sendAssistant = (0, import_react2.useCallback)(async (message) => {
745
934
  if (!mushi?.askAssistant || !message.trim() || assistantSending) return;
746
935
  const trimmed = message.trim();
747
936
  setAssistantInput("");
@@ -766,37 +955,44 @@ var MushiBottomSheet = ({
766
955
  setAssistantSending(false);
767
956
  }
768
957
  }, [mushi, assistantSending, assistantThreadId]);
769
- const resetForm = (0, import_react.useCallback)(() => {
958
+ const resetForm = (0, import_react2.useCallback)(() => {
770
959
  setCategory(null);
771
960
  setDescription("");
772
961
  setPhase("form");
962
+ setEmailState("hidden");
963
+ setEmailInput("");
773
964
  }, []);
774
- const animateIn = (0, import_react.useCallback)(() => {
775
- import_react_native2.Animated.parallel([
776
- import_react_native2.Animated.spring(translateY, {
965
+ const animateIn = (0, import_react2.useCallback)(() => {
966
+ if (reduceMotion) {
967
+ translateY.setValue(0);
968
+ backdropOpacity.setValue(1);
969
+ return;
970
+ }
971
+ import_react_native3.Animated.parallel([
972
+ import_react_native3.Animated.spring(translateY, {
777
973
  toValue: 0,
778
974
  useNativeDriver: true,
779
975
  damping: 20,
780
976
  stiffness: 200
781
977
  }),
782
- import_react_native2.Animated.timing(backdropOpacity, {
978
+ import_react_native3.Animated.timing(backdropOpacity, {
783
979
  toValue: 1,
784
980
  duration: 250,
785
981
  useNativeDriver: true
786
982
  })
787
983
  ]).start();
788
- }, [translateY, backdropOpacity]);
789
- const animateOut = (0, import_react.useCallback)(
984
+ }, [translateY, backdropOpacity, reduceMotion]);
985
+ const animateOut = (0, import_react2.useCallback)(
790
986
  (cb) => {
791
- import_react_native2.Animated.parallel([
792
- import_react_native2.Animated.timing(translateY, {
987
+ import_react_native3.Animated.parallel([
988
+ import_react_native3.Animated.timing(translateY, {
793
989
  toValue: SHEET_HEIGHT,
794
- duration: 220,
990
+ duration: reduceMotion ? 0 : 220,
795
991
  useNativeDriver: true
796
992
  }),
797
- import_react_native2.Animated.timing(backdropOpacity, {
993
+ import_react_native3.Animated.timing(backdropOpacity, {
798
994
  toValue: 0,
799
- duration: 200,
995
+ duration: reduceMotion ? 0 : 200,
800
996
  useNativeDriver: true
801
997
  })
802
998
  ]).start(() => {
@@ -804,16 +1000,16 @@ var MushiBottomSheet = ({
804
1000
  cb?.();
805
1001
  });
806
1002
  },
807
- [translateY, backdropOpacity, resetForm]
1003
+ [translateY, backdropOpacity, resetForm, reduceMotion]
808
1004
  );
809
- (0, import_react.useEffect)(() => {
1005
+ (0, import_react2.useEffect)(() => {
810
1006
  if (visible) animateIn();
811
1007
  }, [visible, animateIn]);
812
- const handleClose = (0, import_react.useCallback)(() => {
1008
+ const handleClose = (0, import_react2.useCallback)(() => {
813
1009
  animateOut(onClose);
814
1010
  }, [animateOut, onClose]);
815
- const panResponder = (0, import_react.useRef)(
816
- import_react_native2.PanResponder.create({
1011
+ const panResponder = (0, import_react2.useRef)(
1012
+ import_react_native3.PanResponder.create({
817
1013
  onStartShouldSetPanResponder: () => true,
818
1014
  onMoveShouldSetPanResponder: (_, g) => g.dy > 4,
819
1015
  onPanResponderMove: (_, g) => {
@@ -823,7 +1019,7 @@ var MushiBottomSheet = ({
823
1019
  if (g.dy > DISMISS_THRESHOLD) {
824
1020
  handleClose();
825
1021
  } else {
826
- import_react_native2.Animated.spring(translateY, {
1022
+ import_react_native3.Animated.spring(translateY, {
827
1023
  toValue: 0,
828
1024
  useNativeDriver: true,
829
1025
  damping: 20,
@@ -833,20 +1029,43 @@ var MushiBottomSheet = ({
833
1029
  }
834
1030
  })
835
1031
  ).current;
836
- const handleSubmit = async () => {
837
- if (!category || !description.trim() || !mushi) return;
838
- const trimmed = description.trim();
839
- if (trimmed.length < MIN_DESCRIPTION) {
840
- setSubmitError("Please write at least 20 characters so we can understand the issue.");
1032
+ const offerEmail = (0, import_react2.useCallback)(async () => {
1033
+ if (!emailOptIn || !mushi?.getNotificationPrefs) return;
1034
+ try {
1035
+ const prefs = await mushi.getNotificationPrefs();
1036
+ if (prefs?.available.email && !prefs.email_verified && !prefs.email_pending) {
1037
+ setEmailInput(mushi.emailPrefill?.() ?? "");
1038
+ setEmailState("offer");
1039
+ }
1040
+ } catch {
1041
+ }
1042
+ }, [emailOptIn, mushi]);
1043
+ const submitEmail = (0, import_react2.useCallback)(async () => {
1044
+ const email = emailInput.trim();
1045
+ if (!(0, import_reporter_ui2.isPlausibleReporterEmail)(email)) {
1046
+ setEmailState("invalid");
841
1047
  return;
842
1048
  }
1049
+ if (!mushi?.setNotificationPrefs) return;
1050
+ setEmailState("sending");
1051
+ try {
1052
+ const res = await mushi.setNotificationPrefs({ email });
1053
+ setEmailState(res.ok ? "sent" : res.error?.status === 422 ? "invalid" : "failed");
1054
+ } catch {
1055
+ setEmailState("failed");
1056
+ }
1057
+ }, [emailInput, mushi]);
1058
+ const handleSubmit = async () => {
1059
+ const hasAttachment = Boolean(screenshotDataUrl && screenshotAttached);
1060
+ if (!mushi || !(0, import_reporter_ui2.reporterCanSend)(description, hasAttachment, minDescriptionLength)) return;
1061
+ const trimmed = description.trim();
843
1062
  setSubmitError(null);
844
1063
  setPhase("sending");
845
1064
  try {
846
1065
  const outcome = await mushi.submitReport({
847
- category,
1066
+ ...(0, import_reporter_ui2.reporterChipToReport)(category),
848
1067
  description: trimmed,
849
- screenshotDataUrl: screenshotDataUrl && screenshotAttached ? screenshotDataUrl : void 0
1068
+ screenshotDataUrl: hasAttachment ? screenshotDataUrl : void 0
850
1069
  });
851
1070
  if (!outcome.ok) {
852
1071
  if (outcome.failureKind === "credentials" || outcome.failureKind === "quota") {
@@ -864,7 +1083,7 @@ var MushiBottomSheet = ({
864
1083
  return;
865
1084
  }
866
1085
  setPhase("sent");
867
- setTimeout(handleClose, 1400);
1086
+ void offerEmail();
868
1087
  } catch {
869
1088
  setSubmitError("Something went wrong. Please try again.");
870
1089
  setPhase("error");
@@ -875,94 +1094,106 @@ var MushiBottomSheet = ({
875
1094
  const font = colors.fontFamily ? { fontFamily: colors.fontFamily } : null;
876
1095
  const radius = colors.radius;
877
1096
  const sheetTabs = ["report", "inbox", ...assistantEnabled ? ["assistant"] : []];
878
- const canSubmit = !!category && description.trim().length >= MIN_DESCRIPTION && (phase === "form" || phase === "error");
879
- const submitHint = canSubmit || phase === "sending" ? void 0 : !category ? t.step1.heading : rc.ui.addWords;
1097
+ const canSubmit = (0, import_reporter_ui2.reporterCanSend)(description, Boolean(activeScreenshot), minDescriptionLength) && (phase === "form" || phase === "error");
1098
+ const submitHint = canSubmit || phase === "sending" ? void 0 : rc.ui.addWords;
880
1099
  const selectedReport = selectedReportId ? inboxReports.find((r) => r.id === selectedReportId) ?? null : null;
881
- const selectedView = selectedReport ? (0, import_reporter_ui.reporterStatus)(selectedReport, locale) : null;
1100
+ const selectedView = selectedReport ? (0, import_reporter_ui2.reporterStatus)(selectedReport, locale) : null;
882
1101
  const headerTitle = sheetTab === "assistant" ? assistantLabel : sheetTab === "inbox" ? t.flows.reports.title : t.widget.title;
883
- const renderThread = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
884
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
885
- import_react_native2.TouchableOpacity,
1102
+ const renderThread = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1103
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1104
+ import_react_native3.TouchableOpacity,
886
1105
  {
887
1106
  onPress: closeThread,
888
1107
  accessibilityRole: "button",
889
1108
  accessibilityLabel: t.widget.back,
890
1109
  hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
891
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.Text, { style: [{ color: colors.accent, marginBottom: 8 }, font], children: [
1110
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.Text, { style: [{ color: colors.accent, marginBottom: 8 }, font], children: [
892
1111
  "\u2190 ",
893
1112
  t.widget.back
894
1113
  ] })
895
1114
  }
896
1115
  ),
897
- selectedReport && selectedView ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.headerCard, { backgroundColor: colors.surface, borderRadius: radius, borderColor: colors.border }], children: [
898
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.pill, { color: colors.fg, borderColor: colors.border }, font], children: selectedView.label }),
899
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, fontSize: 12, marginTop: 4 }, font], children: selectedView.detail }),
900
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg, marginTop: 8 }, font], numberOfLines: 4, children: selectedReport.title ?? selectedReport.summary ?? selectedReport.description ?? "" })
1116
+ selectedReport && selectedView ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: [s2.headerCard, { backgroundColor: colors.surface, borderRadius: radius, borderColor: colors.border }], children: [
1117
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.pill, { color: colors.fg, borderColor: colors.border }, font], children: selectedView.label }),
1118
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.muted, fontSize: 12, marginTop: 4 }, font], children: selectedView.detail }),
1119
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.fg, marginTop: 8 }, font], numberOfLines: 4, children: selectedReport.title ?? selectedReport.summary ?? selectedReport.description ?? "" })
901
1120
  ] }) : null,
902
- threadStatus === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { accessibilityLabel: t.flows.reports.loading, accessibilityLiveRegion: "polite", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.skeleton, { backgroundColor: colors.surface, width: `${90 - i * 18}%` }] }, i)) }) : threadStatus === "error" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.errorRow, accessibilityLiveRegion: "polite", children: [
903
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.error, flex: 1 }, font], accessibilityRole: "alert", children: rc.ui.loadError }),
904
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
905
- import_react_native2.TouchableOpacity,
1121
+ threadStatus === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { accessibilityLabel: t.flows.reports.loading, accessibilityLiveRegion: "polite", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [s2.skeleton, { backgroundColor: colors.surface, width: `${90 - i * 18}%` }] }, i)) }) : threadStatus === "error" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: s2.errorRow, accessibilityLiveRegion: "polite", children: [
1122
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.error, flex: 1 }, font], accessibilityRole: "alert", children: rc.ui.loadError }),
1123
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1124
+ import_react_native3.TouchableOpacity,
906
1125
  {
907
1126
  onPress: () => selectedReportId && void openThread(selectedReportId),
908
1127
  accessibilityRole: "button",
909
- style: [s.retryBtn, { borderColor: colors.border, borderRadius: radius }],
910
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg, fontWeight: "600" }, font], children: rc.ui.retry })
1128
+ style: [s2.retryBtn, { borderColor: colors.border, borderRadius: radius }],
1129
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.fg, fontWeight: "600" }, font], children: rc.ui.retry })
911
1130
  }
912
1131
  )
913
- ] }) : threadComments.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, marginBottom: 8 }, font], children: rc.ui.noReplies }) : threadComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
914
- import_react_native2.View,
915
- {
916
- style: [
917
- s.threadBubble,
1132
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1133
+ threadItems.map(
1134
+ (item, idx) => (0, import_reporter_ui2.isReporterConversation)(item) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1135
+ import_react_native3.View,
918
1136
  {
919
- backgroundColor: colors.surface,
920
- alignSelf: c.author_kind === "reporter" ? "flex-end" : "flex-start"
921
- }
922
- ],
923
- children: [
924
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: c.author_kind === "reporter" ? rc.ui.you : rc.ui.developer }),
925
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg }, font], children: c.body })
926
- ]
927
- },
928
- c.id
929
- )),
930
- replyState === "sending" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.threadBubble, { backgroundColor: colors.surface, alignSelf: "flex-end", opacity: 0.7 }], children: [
931
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: rc.ui.sending }),
932
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg }, font], children: replyText.trim() })
1137
+ style: [
1138
+ s2.threadBubble,
1139
+ {
1140
+ backgroundColor: colors.surface,
1141
+ alignSelf: item.kind === "reporter_comment" ? "flex-end" : "flex-start"
1142
+ }
1143
+ ],
1144
+ children: [
1145
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: item.kind === "reporter_comment" ? rc.ui.you : rc.ui.developer }),
1146
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.fg }, font], children: (0, import_reporter_ui2.reporterTimelineEntryText)(item, locale) })
1147
+ ]
1148
+ },
1149
+ `${item.kind}-${item.comment_id ?? idx}`
1150
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1151
+ import_react_native3.Text,
1152
+ {
1153
+ style: [{ color: colors.muted, fontSize: 12, textAlign: "center", marginVertical: 6 }, font],
1154
+ children: (0, import_reporter_ui2.reporterTimelineEntryText)(item, locale)
1155
+ },
1156
+ `${item.kind}-${item.at}-${idx}`
1157
+ )
1158
+ ),
1159
+ !threadItems.some(import_reporter_ui2.isReporterConversation) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.muted, marginBottom: 8 }, font], children: rc.ui.noReplies }) : null
1160
+ ] }),
1161
+ replyState === "sending" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: [s2.threadBubble, { backgroundColor: colors.surface, alignSelf: "flex-end", opacity: 0.7 }], children: [
1162
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: rc.ui.sending }),
1163
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.fg }, font], children: replyText.trim() })
933
1164
  ] }) : null,
934
- selectedView?.canVerify ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.verifyRow, children: [
935
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
936
- import_react_native2.TouchableOpacity,
1165
+ selectedView?.canVerify ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: s2.verifyRow, children: [
1166
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1167
+ import_react_native3.TouchableOpacity,
937
1168
  {
938
- style: [s.verifyBtn, { backgroundColor: colors.accent, borderRadius: radius }],
1169
+ style: [s2.verifyBtn, { backgroundColor: colors.accent, borderRadius: radius }],
939
1170
  onPress: () => void submitFeedback("confirms"),
940
1171
  disabled: feedbackSending,
941
1172
  accessibilityRole: "button",
942
1173
  accessibilityState: { disabled: feedbackSending, busy: feedbackSending },
943
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentFg }, font], children: rc.ui.yes })
1174
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.submitText, { color: colors.accentFg }, font], children: rc.ui.yes })
944
1175
  }
945
1176
  ),
946
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
947
- import_react_native2.TouchableOpacity,
1177
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1178
+ import_react_native3.TouchableOpacity,
948
1179
  {
949
- style: [s.verifyBtn, { backgroundColor: colors.border, borderRadius: radius }],
1180
+ style: [s2.verifyBtn, { backgroundColor: colors.border, borderRadius: radius }],
950
1181
  onPress: () => void submitFeedback("not_fixed"),
951
1182
  disabled: feedbackSending,
952
1183
  accessibilityRole: "button",
953
1184
  accessibilityState: { disabled: feedbackSending, busy: feedbackSending },
954
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.fg }, font], children: rc.ui.notYet })
1185
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.submitText, { color: colors.fg }, font], children: rc.ui.notYet })
955
1186
  }
956
1187
  )
957
1188
  ] }) : null,
958
- feedbackError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.error, marginBottom: 8 }, font], accessibilityRole: "alert", children: rc.ui.sendFailed }) : null,
959
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.composer, children: [
960
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
961
- import_react_native2.TextInput,
1189
+ feedbackError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.error, marginBottom: 8 }, font], accessibilityRole: "alert", children: rc.ui.sendFailed }) : null,
1190
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: s2.composer, children: [
1191
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1192
+ import_react_native3.TextInput,
962
1193
  {
963
1194
  style: [
964
- s.input,
965
- s.composerInput,
1195
+ s2.input,
1196
+ s2.composerInput,
966
1197
  { backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius },
967
1198
  font
968
1199
  ],
@@ -979,11 +1210,11 @@ var MushiBottomSheet = ({
979
1210
  accessibilityLabel: t.flows.thread.replyPlaceholder
980
1211
  }
981
1212
  ),
982
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
983
- import_react_native2.TouchableOpacity,
1213
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1214
+ import_react_native3.TouchableOpacity,
984
1215
  {
985
1216
  style: [
986
- s.sendBtn,
1217
+ s2.sendBtn,
987
1218
  {
988
1219
  backgroundColor: replyText.trim() && replyState !== "sending" ? colors.accent : colors.disabled,
989
1220
  borderRadius: radius
@@ -994,11 +1225,11 @@ var MushiBottomSheet = ({
994
1225
  accessibilityRole: "button",
995
1226
  accessibilityLabel: replyState === "failed" ? rc.ui.retry : t.flows.thread.send,
996
1227
  accessibilityState: { disabled: !replyText.trim() || replyState === "sending", busy: replyState === "sending" },
997
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
998
- import_react_native2.Text,
1228
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1229
+ import_react_native3.Text,
999
1230
  {
1000
1231
  style: [
1001
- s.submitText,
1232
+ s2.submitText,
1002
1233
  { color: replyText.trim() && replyState !== "sending" ? colors.accentFg : colors.disabledFg },
1003
1234
  font
1004
1235
  ],
@@ -1008,7 +1239,7 @@ var MushiBottomSheet = ({
1008
1239
  }
1009
1240
  )
1010
1241
  ] }),
1011
- replyState === "failed" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: [
1242
+ replyState === "failed" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: [
1012
1243
  rc.ui.sendFailed,
1013
1244
  " \xB7 ",
1014
1245
  rc.ui.retry
@@ -1016,50 +1247,50 @@ var MushiBottomSheet = ({
1016
1247
  ] });
1017
1248
  const renderInboxList = () => {
1018
1249
  if (inboxLoading && !inboxLoaded) {
1019
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted }, font], children: t.flows.reports.loading });
1250
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.muted }, font], children: t.flows.reports.loading });
1020
1251
  }
1021
1252
  if (inboxReports.length === 0) {
1022
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted }, font], children: rc.ui.empty });
1253
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.muted }, font], children: rc.ui.empty });
1023
1254
  }
1024
1255
  return inboxReports.map((r) => {
1025
- const view = (0, import_reporter_ui.reporterStatus)(r, locale);
1256
+ const view = (0, import_reporter_ui2.reporterStatus)(r, locale);
1026
1257
  const unread = (r.unread_count ?? 0) > 0;
1027
1258
  const title = (r.title ?? r.summary ?? r.description ?? "Report").slice(0, 80);
1028
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1029
- import_react_native2.TouchableOpacity,
1259
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1260
+ import_react_native3.TouchableOpacity,
1030
1261
  {
1031
- style: [s.inboxRow, { borderColor: colors.border }],
1262
+ style: [s2.inboxRow, { borderColor: colors.border }],
1032
1263
  onPress: () => void openThread(r.id),
1033
1264
  accessibilityRole: "button",
1034
1265
  accessibilityLabel: `${view.label}. ${title}${unread ? `. ${r.unread_count} new` : ""}`,
1035
1266
  children: [
1036
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg, fontWeight: unread ? "700" : "600" }, font], numberOfLines: 1, children: title }),
1037
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.Text, { style: [{ color: unread ? colors.fg : colors.muted, fontSize: 12, marginTop: 2 }, font], numberOfLines: 1, children: [
1267
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.fg, fontWeight: unread ? "700" : "600" }, font], numberOfLines: 1, children: title }),
1268
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.Text, { style: [{ color: unread ? colors.fg : colors.muted, fontSize: 12, marginTop: 2 }, font], numberOfLines: 1, children: [
1038
1269
  view.label,
1039
1270
  view.othersNote ? ` \xB7 ${view.othersNote}` : ""
1040
1271
  ] }),
1041
- unread && r.last_event_preview ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg, fontSize: 12, fontWeight: "700", marginTop: 2 }, font], numberOfLines: 1, children: r.last_event_preview }) : null
1272
+ unread && r.last_event_preview ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.fg, fontSize: 12, fontWeight: "700", marginTop: 2 }, font], numberOfLines: 1, children: r.last_event_preview }) : null
1042
1273
  ]
1043
1274
  },
1044
1275
  r.id
1045
1276
  );
1046
1277
  });
1047
1278
  };
1048
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, onRequestClose: handleClose, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1049
- import_react_native2.KeyboardAvoidingView,
1279
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, onRequestClose: handleClose, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1280
+ import_react_native3.KeyboardAvoidingView,
1050
1281
  {
1051
- style: s.flex,
1052
- behavior: import_react_native2.Platform.OS === "ios" ? "padding" : "height",
1053
- keyboardVerticalOffset: import_react_native2.Platform.OS === "ios" ? 0 : 20,
1282
+ style: s2.flex,
1283
+ behavior: import_react_native3.Platform.OS === "ios" ? "padding" : "height",
1284
+ keyboardVerticalOffset: import_react_native3.Platform.OS === "ios" ? 0 : 20,
1054
1285
  children: [
1055
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1056
- import_react_native2.Animated.View,
1286
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1287
+ import_react_native3.Animated.View,
1057
1288
  {
1058
- style: [s.backdrop, { backgroundColor: colors.backdrop, opacity: backdropOpacity }],
1059
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1060
- import_react_native2.TouchableOpacity,
1289
+ style: [s2.backdrop, { backgroundColor: colors.backdrop, opacity: backdropOpacity }],
1290
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1291
+ import_react_native3.TouchableOpacity,
1061
1292
  {
1062
- style: s.flex,
1293
+ style: s2.flex,
1063
1294
  activeOpacity: 1,
1064
1295
  onPress: handleClose,
1065
1296
  accessibilityRole: "button",
@@ -1068,21 +1299,21 @@ var MushiBottomSheet = ({
1068
1299
  )
1069
1300
  }
1070
1301
  ),
1071
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1072
- import_react_native2.Animated.View,
1302
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1303
+ import_react_native3.Animated.View,
1073
1304
  {
1074
1305
  accessibilityViewIsModal: true,
1075
1306
  onAccessibilityEscape: handleClose,
1076
1307
  style: [
1077
- s.sheet,
1308
+ s2.sheet,
1078
1309
  { backgroundColor: colors.bg, transform: [{ translateY }] }
1079
1310
  ],
1080
1311
  children: [
1081
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1082
- import_react_native2.View,
1312
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1313
+ import_react_native3.View,
1083
1314
  {
1084
1315
  ...panResponder.panHandlers,
1085
- style: s.handleArea,
1316
+ style: s2.handleArea,
1086
1317
  accessible: true,
1087
1318
  accessibilityRole: "adjustable",
1088
1319
  accessibilityLabel: t.widget.close,
@@ -1090,57 +1321,57 @@ var MushiBottomSheet = ({
1090
1321
  onAccessibilityAction: (e) => {
1091
1322
  if (e.nativeEvent.actionName === "decrement" || e.nativeEvent.actionName === "escape") handleClose();
1092
1323
  },
1093
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.handle, { backgroundColor: colors.muted }] })
1324
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [s2.handle, { backgroundColor: colors.muted }] })
1094
1325
  }
1095
1326
  ),
1096
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.header, { borderBottomColor: colors.border }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { accessibilityRole: "header", style: [s.headerTitle, { color: colors.fg }, font], children: headerTitle }) }),
1097
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.tabRow, { borderBottomColor: colors.border }], accessibilityRole: "tablist", children: sheetTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1098
- import_react_native2.TouchableOpacity,
1327
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [s2.header, { borderBottomColor: colors.border }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { accessibilityRole: "header", style: [s2.headerTitle, { color: colors.fg }, font], children: headerTitle }) }),
1328
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [s2.tabRow, { borderBottomColor: colors.border }], accessibilityRole: "tablist", children: sheetTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1329
+ import_react_native3.TouchableOpacity,
1099
1330
  {
1100
1331
  onPress: () => {
1101
1332
  setSheetTab(tab);
1102
1333
  if (tab === "inbox") void loadInbox({ silent: inboxLoaded });
1103
1334
  else closeThread();
1104
1335
  },
1105
- style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
1336
+ style: [s2.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
1106
1337
  accessibilityRole: "tab",
1107
1338
  accessibilityState: { selected: sheetTab === tab },
1108
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.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 })
1339
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.tabLabel, { color: sheetTab === tab ? colors.fg : colors.muted }, font], children: tab === "report" ? t.widget.trigger : tab === "inbox" ? t.flows.reports.title : assistantLabel })
1109
1340
  },
1110
1341
  tab
1111
1342
  )) }),
1112
- sheetTab === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
1113
- assistantTurns.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1114
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, marginBottom: 12, lineHeight: 20 }, font], children: greeting }),
1115
- assistantSuggestions.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1116
- import_react_native2.TouchableOpacity,
1343
+ sheetTab === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.ScrollView, { style: s2.body, keyboardShouldPersistTaps: "handled", children: [
1344
+ assistantTurns.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1345
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.muted, marginBottom: 12, lineHeight: 20 }, font], children: greeting }),
1346
+ assistantSuggestions.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1347
+ import_react_native3.TouchableOpacity,
1117
1348
  {
1118
- style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.surface }],
1349
+ style: [s2.assistantChip, { borderColor: colors.border, backgroundColor: colors.surface }],
1119
1350
  onPress: () => void sendAssistant(chip),
1120
1351
  accessibilityRole: "button",
1121
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg, fontSize: 13 }, font], children: chip })
1352
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.fg, fontSize: 13 }, font], children: chip })
1122
1353
  },
1123
1354
  chip
1124
1355
  ))
1125
- ] }) : assistantTurns.map((turn, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1126
- import_react_native2.View,
1356
+ ] }) : assistantTurns.map((turn, idx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1357
+ import_react_native3.View,
1127
1358
  {
1128
1359
  style: [
1129
- s.assistantBubble,
1360
+ s2.assistantBubble,
1130
1361
  {
1131
1362
  alignSelf: turn.role === "user" ? "flex-end" : "flex-start",
1132
1363
  backgroundColor: turn.role === "user" ? colors.accent : colors.surface
1133
1364
  }
1134
1365
  ],
1135
1366
  children: [
1136
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: turn.role === "user" ? colors.accentFg : colors.fg }, font], children: turn.text }),
1137
- turn.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1138
- import_react_native2.TouchableOpacity,
1367
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: turn.role === "user" ? colors.accentFg : colors.fg }, font], children: turn.text }),
1368
+ turn.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1369
+ import_react_native3.TouchableOpacity,
1139
1370
  {
1140
1371
  onPress: () => void sendAssistant(opt),
1141
1372
  style: { marginTop: 8 },
1142
1373
  accessibilityRole: "button",
1143
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.accent, fontSize: 12 }, font], children: opt })
1374
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.accent, fontSize: 12 }, font], children: opt })
1144
1375
  },
1145
1376
  opt
1146
1377
  ))
@@ -1148,13 +1379,13 @@ var MushiBottomSheet = ({
1148
1379
  },
1149
1380
  `${turn.role}-${idx}`
1150
1381
  )),
1151
- assistantSending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, marginTop: 8 }, font], accessibilityLiveRegion: "polite", children: t.assistant.thinking }) : null,
1152
- assistantError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.error, marginTop: 8 }, font], accessibilityRole: "alert", children: assistantError }) : null,
1153
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.assistantComposer, children: [
1154
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1155
- import_react_native2.TextInput,
1382
+ assistantSending ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.muted, marginTop: 8 }, font], accessibilityLiveRegion: "polite", children: t.assistant.thinking }) : null,
1383
+ assistantError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.error, marginTop: 8 }, font], accessibilityRole: "alert", children: assistantError }) : null,
1384
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: s2.assistantComposer, children: [
1385
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1386
+ import_react_native3.TextInput,
1156
1387
  {
1157
- style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius }, font],
1388
+ style: [s2.input, { flex: 1, minHeight: 44, backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius }, font],
1158
1389
  placeholder: t.assistant.inputPlaceholder,
1159
1390
  placeholderTextColor: colors.muted,
1160
1391
  value: assistantInput,
@@ -1162,71 +1393,101 @@ var MushiBottomSheet = ({
1162
1393
  editable: !assistantSending
1163
1394
  }
1164
1395
  ),
1165
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1166
- import_react_native2.TouchableOpacity,
1396
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1397
+ import_react_native3.TouchableOpacity,
1167
1398
  {
1168
- style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16, borderRadius: radius }],
1399
+ style: [s2.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16, borderRadius: radius }],
1169
1400
  onPress: () => void sendAssistant(assistantInput),
1170
1401
  disabled: !assistantInput.trim() || assistantSending,
1171
1402
  accessibilityRole: "button",
1172
1403
  accessibilityLabel: t.flows.thread.send,
1173
1404
  accessibilityState: { disabled: !assistantInput.trim() || assistantSending, busy: assistantSending },
1174
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentFg }], children: "\u2191" })
1405
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.submitText, { color: colors.accentFg }], children: "\u2191" })
1175
1406
  }
1176
1407
  )
1177
1408
  ] })
1178
- ] }) : sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", contentContainerStyle: { paddingBottom: 24 }, children: selectedReportId ? renderThread() : renderInboxList() }) : phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, accessibilityLiveRegion: "polite", children: [
1179
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentEmoji], children: "\u2705" }),
1180
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.fg }, font], children: t.widget.submitted })
1181
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1182
- import_react_native2.ScrollView,
1409
+ ] }) : sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.ScrollView, { style: s2.body, keyboardShouldPersistTaps: "handled", contentContainerStyle: { paddingBottom: 24 }, children: selectedReportId ? renderThread() : renderInboxList() }) : phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.ScrollView, { style: s2.body, keyboardShouldPersistTaps: "handled", contentContainerStyle: { paddingBottom: 24 }, children: [
1410
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: s2.sentWrap, accessibilityLiveRegion: "polite", children: [
1411
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.sentEmoji], importantForAccessibility: "no", children: "\u2705" }),
1412
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.sentText, { color: colors.fg }, font], children: rc.ui.receipt })
1413
+ ] }),
1414
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: s2.verifyRow, children: [
1415
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1416
+ import_react_native3.TouchableOpacity,
1417
+ {
1418
+ style: [s2.verifyBtn, { backgroundColor: colors.accent, borderRadius: radius }],
1419
+ onPress: () => {
1420
+ resetForm();
1421
+ setSheetTab("inbox");
1422
+ void loadInbox();
1423
+ },
1424
+ accessibilityRole: "button",
1425
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.submitText, { color: colors.accentFg }, font], children: rc.ui.trackIt })
1426
+ }
1427
+ ),
1428
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1429
+ import_react_native3.TouchableOpacity,
1430
+ {
1431
+ style: [s2.verifyBtn, { backgroundColor: colors.border, borderRadius: radius }],
1432
+ onPress: handleClose,
1433
+ accessibilityRole: "button",
1434
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.submitText, { color: colors.fg }, font], children: rc.ui.done })
1435
+ }
1436
+ )
1437
+ ] }),
1438
+ emailState === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.fg, marginTop: 12 }, font], accessibilityLiveRegion: "polite", children: rc.ui.emailCheckInbox }) : emailState !== "hidden" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: { marginTop: 12 }, children: [
1439
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.fg, marginBottom: 6 }, font], children: rc.ui.emailOptIn }),
1440
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: s2.composer, children: [
1441
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1442
+ import_react_native3.TextInput,
1443
+ {
1444
+ style: [
1445
+ s2.input,
1446
+ s2.composerInput,
1447
+ { backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius },
1448
+ font
1449
+ ],
1450
+ value: emailInput,
1451
+ onChangeText: (v) => {
1452
+ setEmailInput(v.slice(0, 254));
1453
+ if (emailState === "invalid" || emailState === "failed") setEmailState("offer");
1454
+ },
1455
+ placeholder: rc.ui.emailPlaceholder,
1456
+ placeholderTextColor: colors.muted,
1457
+ keyboardType: "email-address",
1458
+ autoCapitalize: "none",
1459
+ autoComplete: "email",
1460
+ textContentType: "emailAddress",
1461
+ editable: emailState !== "sending",
1462
+ accessibilityLabel: rc.ui.emailOptIn
1463
+ }
1464
+ ),
1465
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1466
+ import_react_native3.TouchableOpacity,
1467
+ {
1468
+ style: [s2.sendBtn, { backgroundColor: emailInput.trim() ? colors.accent : colors.disabled, borderRadius: radius }],
1469
+ onPress: () => void submitEmail(),
1470
+ disabled: !emailInput.trim() || emailState === "sending",
1471
+ accessibilityRole: "button",
1472
+ accessibilityState: { disabled: !emailInput.trim() || emailState === "sending", busy: emailState === "sending" },
1473
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.submitText, { color: emailInput.trim() ? colors.accentFg : colors.disabledFg }, font], children: emailState === "sending" ? rc.ui.sending : rc.ui.emailSubmit })
1474
+ }
1475
+ )
1476
+ ] }),
1477
+ emailState === "invalid" || emailState === "failed" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: emailState === "invalid" ? rc.ui.emailInvalid : rc.ui.emailFailed }) : null
1478
+ ] }) : null
1479
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1480
+ import_react_native3.ScrollView,
1183
1481
  {
1184
- style: s.body,
1482
+ style: s2.body,
1185
1483
  keyboardShouldPersistTaps: "handled",
1186
1484
  contentContainerStyle: { paddingBottom: 24 },
1187
1485
  children: [
1188
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.stepLabel, { color: colors.muted }, font], children: t.step1.heading }),
1189
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.catRow, accessibilityRole: "radiogroup", children: CATEGORY_KEYS.map((key) => {
1190
- const active = category === key;
1191
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1192
- import_react_native2.TouchableOpacity,
1193
- {
1194
- onPress: () => setCategory(key),
1195
- activeOpacity: 0.7,
1196
- accessibilityRole: "radio",
1197
- accessibilityState: { selected: active, checked: active },
1198
- accessibilityLabel: t.step1.categories[key],
1199
- style: [
1200
- s.catBtn,
1201
- {
1202
- backgroundColor: active ? colors.accent : colors.surface,
1203
- borderColor: active ? colors.accent : colors.border
1204
- }
1205
- ],
1206
- children: [
1207
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.catEmoji, importantForAccessibility: "no", children: CATEGORY_EMOJI[key] }),
1208
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1209
- import_react_native2.Text,
1210
- {
1211
- numberOfLines: 1,
1212
- style: [
1213
- s.catLabel,
1214
- { color: active ? colors.accentFg : colors.fg },
1215
- font
1216
- ],
1217
- children: t.step1.categories[key]
1218
- }
1219
- )
1220
- ]
1221
- },
1222
- key
1223
- );
1224
- }) }),
1225
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1226
- import_react_native2.TextInput,
1486
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1487
+ import_react_native3.TextInput,
1227
1488
  {
1228
1489
  style: [
1229
- s.input,
1490
+ s2.input,
1230
1491
  {
1231
1492
  backgroundColor: colors.surface,
1232
1493
  color: colors.fg,
@@ -1250,22 +1511,59 @@ var MushiBottomSheet = ({
1250
1511
  maxLength: 4e3
1251
1512
  }
1252
1513
  ),
1253
- submitError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: submitError }) : null,
1254
- activeScreenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotRow, children: [
1255
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1256
- import_react_native2.Image,
1514
+ submitError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: submitError }) : null,
1515
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [s2.catRow, { marginTop: 12 }], accessibilityRole: "radiogroup", children: import_reporter_ui2.REPORTER_CATEGORIES.map((key) => {
1516
+ const active = category === key;
1517
+ const label = (0, import_reporter_ui2.reporterCategoryLabel)(key, locale);
1518
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1519
+ import_react_native3.TouchableOpacity,
1520
+ {
1521
+ onPress: () => setCategory(active ? null : key),
1522
+ activeOpacity: 0.7,
1523
+ accessibilityRole: "radio",
1524
+ accessibilityState: { selected: active, checked: active },
1525
+ accessibilityLabel: label,
1526
+ style: [
1527
+ s2.catBtn,
1528
+ {
1529
+ backgroundColor: active ? colors.accent : colors.surface,
1530
+ borderColor: active ? colors.accent : colors.border
1531
+ }
1532
+ ],
1533
+ children: [
1534
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: s2.catEmoji, importantForAccessibility: "no", children: CATEGORY_EMOJI[key] }),
1535
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1536
+ import_react_native3.Text,
1537
+ {
1538
+ numberOfLines: 1,
1539
+ style: [
1540
+ s2.catLabel,
1541
+ { color: active ? colors.accentFg : colors.fg },
1542
+ font
1543
+ ],
1544
+ children: label
1545
+ }
1546
+ )
1547
+ ]
1548
+ },
1549
+ key
1550
+ );
1551
+ }) }),
1552
+ activeScreenshot ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: s2.screenshotRow, children: [
1553
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1554
+ import_react_native3.Image,
1257
1555
  {
1258
1556
  source: { uri: activeScreenshot },
1259
- style: [s.screenshotThumb, { backgroundColor: colors.fg }],
1557
+ style: [s2.screenshotThumb, { backgroundColor: colors.fg }],
1260
1558
  accessibilityLabel: t.step3.screenshotPreviewAlt
1261
1559
  }
1262
1560
  ),
1263
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotMeta, children: [
1264
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.fg }, font], children: t.step3.screenshotAttached.replace(" \u2713", "") }),
1265
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotSub, { color: colors.muted }, font], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : t.step3.screenshotSensitiveHint.split("\u2014")[0]?.trim() ?? t.step3.screenshotSensitiveHint })
1561
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: s2.screenshotMeta, children: [
1562
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.screenshotLabel, { color: colors.fg }, font], children: t.step3.screenshotAttached.replace(" \u2713", "") }),
1563
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.screenshotSub, { color: colors.muted }, font], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : t.step3.screenshotSensitiveHint.split("\u2014")[0]?.trim() ?? t.step3.screenshotSensitiveHint })
1266
1564
  ] }),
1267
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1268
- import_react_native2.TouchableOpacity,
1565
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1566
+ import_react_native3.TouchableOpacity,
1269
1567
  {
1270
1568
  onPress: () => {
1271
1569
  setScreenshotAttached(false);
@@ -1274,13 +1572,13 @@ var MushiBottomSheet = ({
1274
1572
  hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
1275
1573
  accessibilityRole: "button",
1276
1574
  accessibilityLabel: t.widget.close,
1277
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.muted }], children: "\u2715" })
1575
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.screenshotRemove, { color: colors.muted }], children: "\u2715" })
1278
1576
  }
1279
1577
  )
1280
1578
  ] }) : null,
1281
- submitHint ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, fontSize: 12, marginBottom: 6 }, font], children: submitHint }) : null,
1282
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1283
- import_react_native2.TouchableOpacity,
1579
+ submitHint ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [{ color: colors.muted, fontSize: 12, marginBottom: 6 }, font], children: submitHint }) : null,
1580
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1581
+ import_react_native3.TouchableOpacity,
1284
1582
  {
1285
1583
  onPress: handleSubmit,
1286
1584
  disabled: !canSubmit,
@@ -1290,10 +1588,10 @@ var MushiBottomSheet = ({
1290
1588
  accessibilityHint: submitHint,
1291
1589
  accessibilityState: { disabled: !canSubmit, busy: phase === "sending" },
1292
1590
  style: [
1293
- s.submitBtn,
1591
+ s2.submitBtn,
1294
1592
  { backgroundColor: canSubmit ? colors.accent : colors.disabled, borderRadius: radius }
1295
1593
  ],
1296
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: canSubmit ? colors.accentFg : colors.disabledFg }, font], children: phase === "sending" ? t.widget.submitting : t.widget.submit })
1594
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: [s2.submitText, { color: canSubmit ? colors.accentFg : colors.disabledFg }, font], children: phase === "sending" ? t.widget.submitting : t.widget.submit })
1297
1595
  }
1298
1596
  )
1299
1597
  ]
@@ -1306,7 +1604,7 @@ var MushiBottomSheet = ({
1306
1604
  }
1307
1605
  ) });
1308
1606
  };
1309
- var s = import_react_native2.StyleSheet.create({
1607
+ var s2 = import_react_native3.StyleSheet.create({
1310
1608
  flex: { flex: 1 },
1311
1609
  backdrop: {
1312
1610
  // Inlined StyleSheet.absoluteFillObject — RN 0.86 dropped it from the
@@ -1323,10 +1621,10 @@ var s = import_react_native2.StyleSheet.create({
1323
1621
  left: 0,
1324
1622
  right: 0,
1325
1623
  minHeight: SHEET_HEIGHT,
1326
- borderTopLeftRadius: import_core4.MUSHI_RADIUS.sheet,
1327
- borderTopRightRadius: import_core4.MUSHI_RADIUS.sheet,
1328
- paddingBottom: import_react_native2.Platform.OS === "ios" ? 34 : import_core4.MUSHI_SPACING.roomy,
1329
- shadowColor: import_core4.MUSHI_SHADOW_INK,
1624
+ borderTopLeftRadius: import_core5.MUSHI_RADIUS.sheet,
1625
+ borderTopRightRadius: import_core5.MUSHI_RADIUS.sheet,
1626
+ paddingBottom: import_react_native3.Platform.OS === "ios" ? 34 : import_core5.MUSHI_SPACING.roomy,
1627
+ shadowColor: import_core5.MUSHI_SHADOW_INK,
1330
1628
  shadowOffset: { width: 0, height: -3 },
1331
1629
  shadowOpacity: 0.12,
1332
1630
  shadowRadius: 8,
@@ -1334,17 +1632,17 @@ var s = import_react_native2.StyleSheet.create({
1334
1632
  },
1335
1633
  handleArea: {
1336
1634
  alignItems: "center",
1337
- paddingVertical: import_core4.MUSHI_SPACING.comfy
1635
+ paddingVertical: import_core5.MUSHI_SPACING.comfy
1338
1636
  },
1339
1637
  handle: {
1340
1638
  width: 40,
1341
1639
  height: 5,
1342
- borderRadius: import_core4.MUSHI_RADIUS.control,
1640
+ borderRadius: import_core5.MUSHI_RADIUS.control,
1343
1641
  opacity: 0.5
1344
1642
  },
1345
1643
  header: {
1346
- paddingHorizontal: import_core4.MUSHI_SPACING.lounge,
1347
- paddingBottom: import_core4.MUSHI_SPACING.comfy
1644
+ paddingHorizontal: import_core5.MUSHI_SPACING.lounge,
1645
+ paddingBottom: import_core5.MUSHI_SPACING.comfy
1348
1646
  },
1349
1647
  headerTitle: {
1350
1648
  fontSize: 18,
@@ -1352,22 +1650,22 @@ var s = import_react_native2.StyleSheet.create({
1352
1650
  letterSpacing: -0.2
1353
1651
  },
1354
1652
  body: {
1355
- paddingHorizontal: import_core4.MUSHI_SPACING.lounge,
1356
- paddingTop: import_core4.MUSHI_SPACING.tight
1653
+ paddingHorizontal: import_core5.MUSHI_SPACING.lounge,
1654
+ paddingTop: import_core5.MUSHI_SPACING.tight
1357
1655
  },
1358
1656
  stepLabel: {
1359
- fontSize: import_core4.MUSHI_TYPE.sizeLabel,
1657
+ fontSize: import_core5.MUSHI_TYPE.sizeLabel,
1360
1658
  fontWeight: "600",
1361
1659
  letterSpacing: 0.2,
1362
- marginTop: import_core4.MUSHI_SPACING.snug,
1363
- marginBottom: import_core4.MUSHI_SPACING.comfy,
1660
+ marginTop: import_core5.MUSHI_SPACING.snug,
1661
+ marginBottom: import_core5.MUSHI_SPACING.comfy,
1364
1662
  textTransform: "uppercase"
1365
1663
  },
1366
1664
  catRow: {
1367
1665
  flexDirection: "row",
1368
1666
  flexWrap: "wrap",
1369
1667
  gap: 8,
1370
- marginBottom: import_core4.MUSHI_SPACING.roomy
1668
+ marginBottom: import_core5.MUSHI_SPACING.roomy
1371
1669
  },
1372
1670
  catBtn: {
1373
1671
  flexDirection: "row",
@@ -1391,7 +1689,7 @@ var s = import_react_native2.StyleSheet.create({
1391
1689
  padding: 14,
1392
1690
  fontSize: 15,
1393
1691
  minHeight: 100,
1394
- marginBottom: import_core4.MUSHI_SPACING.roomy
1692
+ marginBottom: import_core5.MUSHI_SPACING.roomy
1395
1693
  },
1396
1694
  submitBtn: {
1397
1695
  paddingVertical: 14,
@@ -1399,7 +1697,7 @@ var s = import_react_native2.StyleSheet.create({
1399
1697
  minHeight: 44
1400
1698
  },
1401
1699
  submitText: {
1402
- color: import_core4.MUSHI_INVERSE,
1700
+ color: import_core5.MUSHI_INVERSE,
1403
1701
  fontSize: 16,
1404
1702
  fontWeight: "700"
1405
1703
  },
@@ -1450,7 +1748,7 @@ var s = import_react_native2.StyleSheet.create({
1450
1748
  },
1451
1749
  tabRow: {
1452
1750
  flexDirection: "row",
1453
- borderBottomWidth: import_react_native2.StyleSheet.hairlineWidth,
1751
+ borderBottomWidth: import_react_native3.StyleSheet.hairlineWidth,
1454
1752
  marginBottom: 8
1455
1753
  },
1456
1754
  tabBtn: {
@@ -1468,10 +1766,10 @@ var s = import_react_native2.StyleSheet.create({
1468
1766
  inboxRow: {
1469
1767
  paddingVertical: 12,
1470
1768
  minHeight: 56,
1471
- borderBottomWidth: import_react_native2.StyleSheet.hairlineWidth
1769
+ borderBottomWidth: import_react_native3.StyleSheet.hairlineWidth
1472
1770
  },
1473
1771
  headerCard: {
1474
- borderWidth: import_react_native2.StyleSheet.hairlineWidth,
1772
+ borderWidth: import_react_native3.StyleSheet.hairlineWidth,
1475
1773
  padding: 12,
1476
1774
  marginBottom: 12
1477
1775
  },
@@ -1561,14 +1859,14 @@ var s = import_react_native2.StyleSheet.create({
1561
1859
  });
1562
1860
 
1563
1861
  // src/components/MushiFloatingButton.tsx
1564
- var import_react2 = require("react");
1565
- var import_react_native3 = require("react-native");
1566
- var import_core5 = require("@mushi-mushi/core");
1567
- var import_jsx_runtime2 = require("react/jsx-runtime");
1568
- var FAB_SIZE = import_core5.MUSHI_GEOMETRY.fabSize;
1862
+ var import_react3 = require("react");
1863
+ var import_react_native4 = require("react-native");
1864
+ var import_core6 = require("@mushi-mushi/core");
1865
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1866
+ var FAB_SIZE = import_core6.MUSHI_GEOMETRY.fabSize;
1569
1867
  var EDGE_PADDING = 12;
1570
1868
  function getScreenBounds() {
1571
- const { width, height } = import_react_native3.Dimensions.get("window");
1869
+ const { width, height } = import_react_native4.Dimensions.get("window");
1572
1870
  return { width, height };
1573
1871
  }
1574
1872
  function clamp(value, min, max) {
@@ -1599,25 +1897,25 @@ var MushiFloatingButton = ({
1599
1897
  config,
1600
1898
  accent
1601
1899
  }) => {
1602
- const scheme = (0, import_react_native3.useColorScheme)();
1900
+ const scheme = (0, import_react_native4.useColorScheme)();
1603
1901
  const dark = scheme === "dark";
1604
1902
  const draggable = config?.draggable ?? false;
1605
1903
  const snapToEdge = config?.snapToEdge ?? true;
1606
1904
  const persist = config?.persist ?? false;
1607
- const baseBottom = inset?.bottom ?? (import_react_native3.Platform.OS === "ios" ? 50 : 28);
1905
+ const baseBottom = inset?.bottom ?? (import_react_native4.Platform.OS === "ios" ? 50 : 28);
1608
1906
  const baseX = position === "bottom-left" ? inset?.left ?? EDGE_PADDING : (() => {
1609
1907
  const { width } = getScreenBounds();
1610
1908
  return width - FAB_SIZE - (inset?.right ?? EDGE_PADDING);
1611
1909
  })();
1612
- const scale = (0, import_react2.useRef)(new import_react_native3.Animated.Value(1)).current;
1613
- const posX = (0, import_react2.useRef)(new import_react_native3.Animated.Value(baseX)).current;
1614
- const posY = (0, import_react2.useRef)(new import_react_native3.Animated.Value(0)).current;
1615
- const currentX = (0, import_react2.useRef)(baseX);
1616
- const currentY = (0, import_react2.useRef)(0);
1617
- const isDragging = (0, import_react2.useRef)(false);
1618
- const pal = (0, import_core5.mushiPalette)(dark ? "dark" : "light");
1910
+ const scale = (0, import_react3.useRef)(new import_react_native4.Animated.Value(1)).current;
1911
+ const posX = (0, import_react3.useRef)(new import_react_native4.Animated.Value(baseX)).current;
1912
+ const posY = (0, import_react3.useRef)(new import_react_native4.Animated.Value(0)).current;
1913
+ const currentX = (0, import_react3.useRef)(baseX);
1914
+ const currentY = (0, import_react3.useRef)(0);
1915
+ const isDragging = (0, import_react3.useRef)(false);
1916
+ const pal = (0, import_core6.mushiPalette)(dark ? "dark" : "light");
1619
1917
  const accentColor = accent ?? pal.accent;
1620
- (0, import_react2.useEffect)(() => {
1918
+ (0, import_react3.useEffect)(() => {
1621
1919
  if (!persist) return;
1622
1920
  void loadStoredPos().then((pos) => {
1623
1921
  if (!pos) return;
@@ -1627,7 +1925,7 @@ var MushiFloatingButton = ({
1627
1925
  posY.setValue(pos.y);
1628
1926
  });
1629
1927
  }, [persist, posX, posY]);
1630
- const snapAndPersist = (0, import_react2.useCallback)(
1928
+ const snapAndPersist = (0, import_react3.useCallback)(
1631
1929
  (x, y) => {
1632
1930
  const { width, height } = getScreenBounds();
1633
1931
  let snappedX = x;
@@ -1639,13 +1937,13 @@ var MushiFloatingButton = ({
1639
1937
  const clampedY = clamp(y, -(height - FAB_SIZE - baseBottom - 60), 0);
1640
1938
  currentX.current = clampedX;
1641
1939
  currentY.current = clampedY;
1642
- import_react_native3.Animated.spring(posX, {
1940
+ import_react_native4.Animated.spring(posX, {
1643
1941
  toValue: clampedX,
1644
1942
  useNativeDriver: false,
1645
1943
  damping: 18,
1646
1944
  stiffness: 220
1647
1945
  }).start();
1648
- import_react_native3.Animated.spring(posY, {
1946
+ import_react_native4.Animated.spring(posY, {
1649
1947
  toValue: clampedY,
1650
1948
  useNativeDriver: false,
1651
1949
  damping: 18,
@@ -1655,8 +1953,8 @@ var MushiFloatingButton = ({
1655
1953
  },
1656
1954
  [snapToEdge, persist, posX, posY, baseBottom]
1657
1955
  );
1658
- const panResponder = (0, import_react2.useRef)(
1659
- draggable ? import_react_native3.PanResponder.create({
1956
+ const panResponder = (0, import_react3.useRef)(
1957
+ draggable ? import_react_native4.PanResponder.create({
1660
1958
  onStartShouldSetPanResponder: () => true,
1661
1959
  onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 6 || Math.abs(g.dy) > 6,
1662
1960
  onPanResponderGrant: () => {
@@ -1689,19 +1987,19 @@ var MushiFloatingButton = ({
1689
1987
  }
1690
1988
  isDragging.current = false;
1691
1989
  }
1692
- }) : import_react_native3.PanResponder.create({})
1990
+ }) : import_react_native4.PanResponder.create({})
1693
1991
  ).current;
1694
- const handlePressIn = (0, import_react2.useCallback)(() => {
1992
+ const handlePressIn = (0, import_react3.useCallback)(() => {
1695
1993
  if (isDragging.current) return;
1696
- import_react_native3.Animated.spring(scale, {
1994
+ import_react_native4.Animated.spring(scale, {
1697
1995
  toValue: 0.85,
1698
1996
  useNativeDriver: true,
1699
1997
  damping: 15,
1700
1998
  stiffness: 300
1701
1999
  }).start();
1702
2000
  }, [scale]);
1703
- const handlePressOut = (0, import_react2.useCallback)(() => {
1704
- import_react_native3.Animated.spring(scale, {
2001
+ const handlePressOut = (0, import_react3.useCallback)(() => {
2002
+ import_react_native4.Animated.spring(scale, {
1705
2003
  toValue: 1,
1706
2004
  useNativeDriver: true,
1707
2005
  damping: 10,
@@ -1709,15 +2007,15 @@ var MushiFloatingButton = ({
1709
2007
  }).start();
1710
2008
  if (!isDragging.current) onPress();
1711
2009
  }, [scale, onPress]);
1712
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1713
- import_react_native3.Animated.View,
2010
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2011
+ import_react_native4.Animated.View,
1714
2012
  {
1715
2013
  ...draggable ? panResponder.panHandlers : {},
1716
2014
  style: [
1717
- s2.fab,
2015
+ s3.fab,
1718
2016
  {
1719
2017
  left: posX,
1720
- bottom: import_react_native3.Animated.add(new import_react_native3.Animated.Value(baseBottom), import_react_native3.Animated.multiply(posY, -1)),
2018
+ bottom: import_react_native4.Animated.add(new import_react_native4.Animated.Value(baseBottom), import_react_native4.Animated.multiply(posY, -1)),
1721
2019
  backgroundColor: accentColor,
1722
2020
  shadowColor: dark ? accentColor : pal.ink,
1723
2021
  transform: [{ scale }]
@@ -1726,11 +2024,11 @@ var MushiFloatingButton = ({
1726
2024
  onStartShouldSetResponder: () => !draggable,
1727
2025
  onResponderGrant: handlePressIn,
1728
2026
  onResponderRelease: handlePressOut,
1729
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.Text, { style: s2.emoji, children: import_core5.MUSHI_COPY.triggerText })
2027
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Animated.Text, { style: s3.emoji, children: import_core6.MUSHI_COPY.triggerText })
1730
2028
  }
1731
2029
  );
1732
2030
  };
1733
- var s2 = import_react_native3.StyleSheet.create({
2031
+ var s3 = import_react_native4.StyleSheet.create({
1734
2032
  fab: {
1735
2033
  position: "absolute",
1736
2034
  width: FAB_SIZE,
@@ -1750,93 +2048,93 @@ var s2 = import_react_native3.StyleSheet.create({
1750
2048
  });
1751
2049
 
1752
2050
  // src/components/MushiBanner.tsx
1753
- var import_react3 = require("react");
1754
- var import_react_native4 = require("react-native");
1755
- var import_core6 = require("@mushi-mushi/core");
1756
- var import_jsx_runtime3 = require("react/jsx-runtime");
1757
- var MUSHI_BANNER_DEFAULT_HEIGHT = import_core6.MUSHI_GEOMETRY.bannerHeight;
2051
+ var import_react4 = require("react");
2052
+ var import_react_native5 = require("react-native");
2053
+ var import_core7 = require("@mushi-mushi/core");
2054
+ var import_jsx_runtime4 = require("react/jsx-runtime");
2055
+ var MUSHI_BANNER_DEFAULT_HEIGHT = import_core7.MUSHI_GEOMETRY.bannerHeight;
1758
2056
  var MushiBanner = ({
1759
2057
  onPress,
1760
2058
  onDismiss,
1761
- message = import_core6.MUSHI_COPY.bannerMessage,
1762
- label = import_core6.MUSHI_COPY.bannerLabel,
2059
+ message = import_core7.MUSHI_COPY.bannerMessage,
2060
+ label = import_core7.MUSHI_COPY.bannerLabel,
1763
2061
  onLayoutHeight
1764
2062
  }) => {
1765
- const handleLayout = (0, import_react3.useCallback)(
2063
+ const handleLayout = (0, import_react4.useCallback)(
1766
2064
  (e) => {
1767
2065
  onLayoutHeight?.(e.nativeEvent.layout.height);
1768
2066
  },
1769
2067
  [onLayoutHeight]
1770
2068
  );
1771
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1772
- import_react_native4.View,
2069
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2070
+ import_react_native5.View,
1773
2071
  {
1774
- style: s3.wrap,
2072
+ style: s4.wrap,
1775
2073
  onLayout: handleLayout,
1776
2074
  accessibilityRole: "header",
1777
2075
  accessibilityLabel: "Mushi feedback banner",
1778
2076
  children: [
1779
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.TouchableOpacity, { style: s3.main, onPress, activeOpacity: 0.85, children: [
1780
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.eyebrow, children: label.toUpperCase() }),
1781
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.message, numberOfLines: 2, children: message }),
1782
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.cta, children: import_core6.MUSHI_COPY.bugCta })
2077
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.TouchableOpacity, { style: s4.main, onPress, activeOpacity: 0.85, children: [
2078
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native5.Text, { style: s4.eyebrow, children: label.toUpperCase() }),
2079
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native5.Text, { style: s4.message, numberOfLines: 2, children: message }),
2080
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native5.Text, { style: s4.cta, children: import_core7.MUSHI_COPY.bugCta })
1783
2081
  ] }),
1784
- onDismiss ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1785
- import_react_native4.TouchableOpacity,
2082
+ onDismiss ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2083
+ import_react_native5.TouchableOpacity,
1786
2084
  {
1787
- style: s3.dismiss,
2085
+ style: s4.dismiss,
1788
2086
  onPress: onDismiss,
1789
2087
  accessibilityRole: "button",
1790
2088
  accessibilityLabel: "Dismiss banner",
1791
2089
  hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
1792
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.dismissText, children: "\xD7" })
2090
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native5.Text, { style: s4.dismissText, children: "\xD7" })
1793
2091
  }
1794
2092
  ) : null
1795
2093
  ]
1796
2094
  }
1797
2095
  );
1798
2096
  };
1799
- var s3 = import_react_native4.StyleSheet.create({
2097
+ var s4 = import_react_native5.StyleSheet.create({
1800
2098
  wrap: {
1801
2099
  position: "absolute",
1802
2100
  left: 0,
1803
2101
  right: 0,
1804
2102
  bottom: 0,
1805
- minHeight: import_core6.MUSHI_GEOMETRY.bannerHeight,
2103
+ minHeight: import_core7.MUSHI_GEOMETRY.bannerHeight,
1806
2104
  flexDirection: "row",
1807
2105
  alignItems: "stretch",
1808
- backgroundColor: import_core6.MUSHI_BANNER_NEON.bg,
2106
+ backgroundColor: import_core7.MUSHI_BANNER_NEON.bg,
1809
2107
  borderTopWidth: 1,
1810
- borderTopColor: import_core6.MUSHI_BANNER_NEON.border,
1811
- zIndex: import_core6.MUSHI_Z.banner,
2108
+ borderTopColor: import_core7.MUSHI_BANNER_NEON.border,
2109
+ zIndex: import_core7.MUSHI_Z.banner,
1812
2110
  elevation: 8,
1813
- paddingBottom: import_react_native4.Platform.OS === "ios" ? 0 : 0
2111
+ paddingBottom: import_react_native5.Platform.OS === "ios" ? 0 : 0
1814
2112
  },
1815
2113
  main: {
1816
2114
  flex: 1,
1817
2115
  flexDirection: "row",
1818
2116
  alignItems: "center",
1819
- gap: import_core6.MUSHI_SPACING.snug,
1820
- paddingHorizontal: import_core6.MUSHI_SPACING.comfy,
1821
- paddingVertical: import_core6.MUSHI_SPACING.snug,
2117
+ gap: import_core7.MUSHI_SPACING.snug,
2118
+ paddingHorizontal: import_core7.MUSHI_SPACING.comfy,
2119
+ paddingVertical: import_core7.MUSHI_SPACING.snug,
1822
2120
  minHeight: 44
1823
2121
  },
1824
2122
  eyebrow: {
1825
- fontSize: import_core6.MUSHI_TYPE.sizeLabel,
2123
+ fontSize: import_core7.MUSHI_TYPE.sizeLabel,
1826
2124
  fontWeight: "700",
1827
2125
  letterSpacing: 1.2,
1828
- color: import_core6.MUSHI_BANNER_NEON.fg
2126
+ color: import_core7.MUSHI_BANNER_NEON.fg
1829
2127
  },
1830
2128
  message: {
1831
2129
  flex: 1,
1832
2130
  fontSize: 12,
1833
2131
  fontWeight: "500",
1834
- color: import_core6.MUSHI_BANNER_NEON.fg
2132
+ color: import_core7.MUSHI_BANNER_NEON.fg
1835
2133
  },
1836
2134
  cta: {
1837
2135
  fontSize: 12,
1838
2136
  fontWeight: "700",
1839
- color: import_core6.MUSHI_BANNER_NEON.fg,
2137
+ color: import_core7.MUSHI_BANNER_NEON.fg,
1840
2138
  textDecorationLine: "underline"
1841
2139
  },
1842
2140
  dismiss: {
@@ -1846,13 +2144,13 @@ var s3 = import_react_native4.StyleSheet.create({
1846
2144
  dismissText: {
1847
2145
  fontSize: 20,
1848
2146
  lineHeight: 22,
1849
- color: import_core6.MUSHI_BANNER_NEON.fg,
2147
+ color: import_core7.MUSHI_BANNER_NEON.fg,
1850
2148
  opacity: 0.7
1851
2149
  }
1852
2150
  });
1853
2151
 
1854
2152
  // src/analytics/event-tracker.ts
1855
- var import_core7 = require("@mushi-mushi/core");
2153
+ var import_core8 = require("@mushi-mushi/core");
1856
2154
  var CONSENT_BUFFER_MAX = 50;
1857
2155
  var SPILL_MAX = 200;
1858
2156
  var SPILL_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -1916,7 +2214,7 @@ function createRNEventTracker(opts) {
1916
2214
  const client = resolveClient();
1917
2215
  if (!client) return;
1918
2216
  flushing = true;
1919
- const batch = buffer.splice(0, import_core7.EVENT_PROPERTY_LIMITS.maxServerBatch);
2217
+ const batch = buffer.splice(0, import_core8.EVENT_PROPERTY_LIMITS.maxServerBatch);
1920
2218
  if (pendingIdentify) {
1921
2219
  batch.unshift({ name: "identify", ts: (/* @__PURE__ */ new Date()).toISOString(), properties: {} });
1922
2220
  pendingIdentify = false;
@@ -1940,7 +2238,7 @@ function createRNEventTracker(opts) {
1940
2238
  } finally {
1941
2239
  flushing = false;
1942
2240
  }
1943
- if (buffer.length >= import_core7.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
2241
+ if (buffer.length >= import_core8.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
1944
2242
  }
1945
2243
  function enqueue(ev) {
1946
2244
  if (consent === "denied") return;
@@ -1949,7 +2247,7 @@ function createRNEventTracker(opts) {
1949
2247
  return;
1950
2248
  }
1951
2249
  buffer.push(ev);
1952
- if (buffer.length >= import_core7.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
2250
+ if (buffer.length >= import_core8.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
1953
2251
  }
1954
2252
  const ready = enabled ? (async () => {
1955
2253
  storage = await loadStorage();
@@ -1989,17 +2287,17 @@ function createRNEventTracker(opts) {
1989
2287
  ready,
1990
2288
  track(name, properties, trackOpts) {
1991
2289
  if (!enabled || destroyed) return false;
1992
- if (!(0, import_core7.isValidEventName)(name)) return false;
1993
- const { properties: props } = (0, import_core7.sanitizeEventProperties)(properties, { allowlist, scrub: opts.scrub });
2290
+ if (!(0, import_core8.isValidEventName)(name)) return false;
2291
+ const { properties: props } = (0, import_core8.sanitizeEventProperties)(properties, { allowlist, scrub: opts.scrub });
1994
2292
  let merged = props;
1995
2293
  if (trackOpts?.reserved) {
1996
- const { properties: res } = (0, import_core7.sanitizeEventProperties)(trackOpts.reserved, { allowReserved: true, scrub: opts.scrub });
2294
+ const { properties: res } = (0, import_core8.sanitizeEventProperties)(trackOpts.reserved, { allowReserved: true, scrub: opts.scrub });
1997
2295
  merged = { ...props, ...res };
1998
2296
  }
1999
2297
  merged.$surface = SURFACE;
2000
2298
  const route = opts.getRoute?.();
2001
2299
  if (route && merged.$route === void 0) merged.$route = route;
2002
- if (!(0, import_core7.propertiesWithinByteLimit)(merged)) return false;
2300
+ if (!(0, import_core8.propertiesWithinByteLimit)(merged)) return false;
2003
2301
  enqueue({
2004
2302
  name,
2005
2303
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2068,13 +2366,13 @@ function createRNEventTracker(opts) {
2068
2366
  }
2069
2367
 
2070
2368
  // src/analytics/session-tracker.ts
2071
- var import_core8 = require("@mushi-mushi/core");
2369
+ var import_core9 = require("@mushi-mushi/core");
2072
2370
  var HEARTBEAT_INTERVAL_MS = 6e4;
2073
2371
  var RESUME_WINDOW_MS = 30 * 6e4;
2074
2372
  function createRNSessionTracker(opts) {
2075
2373
  const clock = opts.now ?? Date.now;
2076
2374
  const resolveClient = () => typeof opts.client === "function" ? opts.client() : opts.client;
2077
- let sessionId = opts.sessionId ?? `sess_${(0, import_core8.newUuid)()}`;
2375
+ let sessionId = opts.sessionId ?? `sess_${(0, import_core9.newUuid)()}`;
2078
2376
  let pageViewCount = 0;
2079
2377
  let started = false;
2080
2378
  let allowed = false;
@@ -2142,7 +2440,7 @@ function createRNSessionTracker(opts) {
2142
2440
  backgroundedAt = null;
2143
2441
  if (!allowed) return;
2144
2442
  if (!started || away >= RESUME_WINDOW_MS) {
2145
- sessionId = `sess_${(0, import_core8.newUuid)()}`;
2443
+ sessionId = `sess_${(0, import_core9.newUuid)()}`;
2146
2444
  startSession();
2147
2445
  } else {
2148
2446
  send("session_heartbeat", { route: route() });
@@ -2171,14 +2469,14 @@ function createRNSessionTracker(opts) {
2171
2469
 
2172
2470
  // src/version.ts
2173
2471
  var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
2174
- var MUSHI_SDK_VERSION = "0.23.0" ? "0.23.0" : "0.17.0";
2472
+ var MUSHI_SDK_VERSION = "0.24.1" ? "0.24.1" : "0.17.0";
2175
2473
 
2176
2474
  // src/provider.tsx
2177
- var import_jsx_runtime4 = require("react/jsx-runtime");
2178
- var MushiContext = (0, import_react4.createContext)(null);
2475
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2476
+ var MushiContext = (0, import_react5.createContext)(null);
2179
2477
  function nativeUserAgent() {
2180
- const os = import_react_native5.Platform?.OS ?? "native";
2181
- const version = import_react_native5.Platform?.Version;
2478
+ const os = import_react_native6.Platform?.OS ?? "native";
2479
+ const version = import_react_native6.Platform?.Version;
2182
2480
  return `${MUSHI_SDK_PACKAGE}/${MUSHI_SDK_VERSION} (${version != null ? `${os} ${String(version)}` : os})`;
2183
2481
  }
2184
2482
  var DEFAULT_SCREENSHOT_HINT = "Check the preview \u2014 remove it if any private info (balances, personal details) is visible.";
@@ -2219,28 +2517,28 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2219
2517
  );
2220
2518
  }
2221
2519
  }
2222
- const consoleRef = (0, import_react4.useRef)(null);
2223
- const networkRef = (0, import_react4.useRef)(null);
2224
- const queueRef = (0, import_react4.useRef)(null);
2225
- const apiClientRef = (0, import_react4.useRef)(null);
2226
- const sessionIdRef = (0, import_react4.useRef)(`sess_${(0, import_core9.newUuid)()}`);
2227
- const breadcrumbsRef = (0, import_react4.useRef)(
2228
- (0, import_core9.createBreadcrumbBuffer)({ max: config.capture?.maxBreadcrumbs ?? 50 })
2520
+ const consoleRef = (0, import_react5.useRef)(null);
2521
+ const networkRef = (0, import_react5.useRef)(null);
2522
+ const queueRef = (0, import_react5.useRef)(null);
2523
+ const apiClientRef = (0, import_react5.useRef)(null);
2524
+ const sessionIdRef = (0, import_react5.useRef)(`sess_${(0, import_core10.newUuid)()}`);
2525
+ const breadcrumbsRef = (0, import_react5.useRef)(
2526
+ (0, import_core10.createBreadcrumbBuffer)({ max: config.capture?.maxBreadcrumbs ?? 50 })
2229
2527
  );
2230
- const [sheetVisible, setSheetVisible] = (0, import_react4.useState)(false);
2231
- const [sheetPreferredTab, setSheetPreferredTab] = (0, import_react4.useState)("report");
2232
- const [bannerDismissed, setBannerDismissed] = (0, import_react4.useState)(false);
2233
- const [sheetScreenshot, setSheetScreenshot] = (0, import_react4.useState)(null);
2234
- const reporterTokenRef = (0, import_react4.useRef)(`rn-${config.projectId}-anon`);
2528
+ const [sheetVisible, setSheetVisible] = (0, import_react5.useState)(false);
2529
+ const [sheetPreferredTab, setSheetPreferredTab] = (0, import_react5.useState)("report");
2530
+ const [bannerDismissed, setBannerDismissed] = (0, import_react5.useState)(false);
2531
+ const [sheetScreenshot, setSheetScreenshot] = (0, import_react5.useState)(null);
2532
+ const reporterTokenRef = (0, import_react5.useRef)(`rn-${config.projectId}-anon`);
2235
2533
  let resolveReporterTokenReady = () => {
2236
2534
  };
2237
- const reporterTokenReadyRef = (0, import_react4.useRef)(
2535
+ const reporterTokenReadyRef = (0, import_react5.useRef)(
2238
2536
  new Promise((resolve) => {
2239
2537
  resolveReporterTokenReady = resolve;
2240
2538
  })
2241
2539
  );
2242
- const userTokenRef = (0, import_react4.useRef)(null);
2243
- const pageContextRef = (0, import_react4.useRef)(null);
2540
+ const userTokenRef = (0, import_react5.useRef)(null);
2541
+ const pageContextRef = (0, import_react5.useRef)(null);
2244
2542
  const rawEndpoint = config.apiEndpoint ?? config.endpoint;
2245
2543
  if (rawEndpoint !== void 0 && rawEndpoint.trim() === "") {
2246
2544
  throw new Error(
@@ -2250,15 +2548,15 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2250
2548
  if (config.endpoint && !config.apiEndpoint && __DEV__) {
2251
2549
  console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
2252
2550
  }
2253
- const apiEndpoint = rawEndpoint ?? import_core9.DEFAULT_API_ENDPOINT;
2254
- (0, import_react4.useEffect)(() => {
2551
+ const apiEndpoint = rawEndpoint ?? import_core10.DEFAULT_API_ENDPOINT;
2552
+ (0, import_react5.useEffect)(() => {
2255
2553
  if (config.capture?.console !== false) {
2256
2554
  consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
2257
2555
  }
2258
2556
  if (config.capture?.network !== false) {
2259
2557
  networkRef.current = setupNetworkCapture(config.capture?.maxNetworkEntries, apiEndpoint);
2260
2558
  }
2261
- apiClientRef.current = (0, import_core9.createApiClient)({
2559
+ apiClientRef.current = (0, import_core10.createApiClient)({
2262
2560
  projectId: config.projectId,
2263
2561
  apiKey: config.apiKey,
2264
2562
  apiEndpoint,
@@ -2295,14 +2593,14 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2295
2593
  if (existing && existing.startsWith("mushi_")) {
2296
2594
  reporterTokenRef.current = existing;
2297
2595
  } else {
2298
- const fresh = `mushi_${(0, import_core9.newUuid)()}`;
2596
+ const fresh = `mushi_${(0, import_core10.newUuid)()}`;
2299
2597
  reporterTokenRef.current = fresh;
2300
2598
  await saveReporterToken(fresh, secureStorage);
2301
2599
  }
2302
2600
  resolveReporterTokenReady();
2303
2601
  } catch {
2304
2602
  if (reporterTokenRef.current === `rn-${config.projectId}-anon`) {
2305
- reporterTokenRef.current = `mushi_${(0, import_core9.newUuid)()}`;
2603
+ reporterTokenRef.current = `mushi_${(0, import_core10.newUuid)()}`;
2306
2604
  }
2307
2605
  resolveReporterTokenReady();
2308
2606
  }
@@ -2313,11 +2611,10 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2313
2611
  networkRef.current?.restore();
2314
2612
  };
2315
2613
  }, []);
2316
- (0, import_react4.useEffect)(() => {
2614
+ (0, import_react5.useEffect)(() => {
2317
2615
  let unsubscribe;
2318
2616
  try {
2319
- const NetInfo = require("@react-native-community/netinfo").default;
2320
- unsubscribe = NetInfo.addEventListener((state) => {
2617
+ unsubscribe = resolveNetInfo(config.netInfo)?.addEventListener((state) => {
2321
2618
  if (state.isConnected && state.isInternetReachable) {
2322
2619
  queueRef.current?.flush().catch(() => {
2323
2620
  });
@@ -2326,8 +2623,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2326
2623
  } catch {
2327
2624
  }
2328
2625
  return () => unsubscribe?.();
2329
- }, []);
2330
- const open = (0, import_react4.useCallback)(() => {
2626
+ }, [config.netInfo]);
2627
+ const open = (0, import_react5.useCallback)(() => {
2331
2628
  setSheetPreferredTab("report");
2332
2629
  breadcrumbsRef.current.add({
2333
2630
  category: "lifecycle",
@@ -2340,11 +2637,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2340
2637
  setSheetVisible(true);
2341
2638
  return;
2342
2639
  }
2343
- let vshot = null;
2344
- try {
2345
- vshot = require("react-native-view-shot");
2346
- } catch {
2347
- }
2640
+ const vshot = resolveViewShot(config.viewShot);
2348
2641
  if (!vshot) {
2349
2642
  setSheetScreenshot(null);
2350
2643
  setSheetVisible(true);
@@ -2357,16 +2650,16 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2357
2650
  }).finally(() => {
2358
2651
  setSheetVisible(true);
2359
2652
  });
2360
- }, [config.capture?.screenshot]);
2361
- const openAssistant = (0, import_react4.useCallback)(() => {
2653
+ }, [config.capture?.screenshot, config.viewShot]);
2654
+ const openAssistant = (0, import_react5.useCallback)(() => {
2362
2655
  if (!config.assistant?.enabled) return;
2363
2656
  setSheetPreferredTab("assistant");
2364
2657
  setSheetScreenshot(null);
2365
2658
  setSheetVisible(true);
2366
2659
  }, [config.assistant?.enabled]);
2367
- const close = (0, import_react4.useCallback)(() => setSheetVisible(false), []);
2368
- const attachTo = (0, import_react4.useCallback)(() => ({ onPress: open }), [open]);
2369
- (0, import_react4.useEffect)(() => {
2660
+ const close = (0, import_react5.useCallback)(() => setSheetVisible(false), []);
2661
+ const attachTo = (0, import_react5.useCallback)(() => ({ onPress: open }), [open]);
2662
+ (0, import_react5.useEffect)(() => {
2370
2663
  const trigger2 = config.widget?.trigger ?? "button";
2371
2664
  if (trigger2 !== "shake" && trigger2 !== "both") return;
2372
2665
  let disposed = false;
@@ -2374,8 +2667,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2374
2667
  let lastShake = 0;
2375
2668
  const threshold = config.widget?.shakeThreshold ?? 2.7;
2376
2669
  try {
2377
- const mod = require("expo-sensors");
2378
- if (!disposed) {
2670
+ const mod = resolveExpoSensors(config.expoSensors);
2671
+ if (mod && !disposed) {
2379
2672
  mod.Accelerometer.setUpdateInterval(120);
2380
2673
  const sub = mod.Accelerometer.addListener((evt) => {
2381
2674
  const g = Math.sqrt(evt.x * evt.x + evt.y * evt.y + evt.z * evt.z);
@@ -2393,8 +2686,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2393
2686
  disposed = true;
2394
2687
  cleanup?.();
2395
2688
  };
2396
- }, [config.widget?.trigger, config.widget?.shakeThreshold, open]);
2397
- const submitReport = (0, import_react4.useCallback)(
2689
+ }, [config.widget?.trigger, config.widget?.shakeThreshold, config.expoSensors, open]);
2690
+ const submitReport = (0, import_react5.useCallback)(
2398
2691
  async (data) => {
2399
2692
  await reporterTokenReadyRef.current;
2400
2693
  const deviceInfo = getDeviceInfo();
@@ -2422,7 +2715,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2422
2715
  const merged = Object.keys(metadataRef.current).length > 0 ? { ...contextMeta, ...metadataRef.current } : Object.keys(contextMeta).length > 0 ? contextMeta : void 0;
2423
2716
  const breadcrumbs = breadcrumbsRef.current.getAll().map((c) => ({
2424
2717
  ...c,
2425
- message: (0, import_core9.scrubPii)(c.message)
2718
+ message: (0, import_core10.scrubPii)(c.message)
2426
2719
  }));
2427
2720
  const timeline = breadcrumbs.map((c) => ({
2428
2721
  ts: c.timestamp,
@@ -2431,10 +2724,11 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2431
2724
  }));
2432
2725
  const fingerprintHash = getDeviceFingerprintHash(deviceInfo);
2433
2726
  const report = {
2434
- id: (0, import_core9.newUuid)(),
2727
+ id: (0, import_core10.newUuid)(),
2435
2728
  projectId: config.projectId,
2436
2729
  category: data.category,
2437
- description: (0, import_core9.scrubPii)(data.description),
2730
+ ...data.userCategory ? { userCategory: data.userCategory } : {},
2731
+ description: (0, import_core10.scrubPii)(data.description),
2438
2732
  environment: {
2439
2733
  userAgent: deviceInfo.systemName ?? "ReactNative",
2440
2734
  platform: deviceInfo.platform ?? "mobile",
@@ -2447,7 +2741,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2447
2741
  },
2448
2742
  consoleLogs: (consoleRef.current?.getEntries() ?? []).map((entry) => ({
2449
2743
  ...entry,
2450
- message: (0, import_core9.scrubPii)(entry.message)
2744
+ message: (0, import_core10.scrubPii)(entry.message)
2451
2745
  })),
2452
2746
  networkLogs: networkRef.current?.getEntries().map((entry) => ({
2453
2747
  ...entry,
@@ -2477,7 +2771,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2477
2771
  const code = result.error?.code ?? "";
2478
2772
  const isCredentialError = code === "HTTP_401" || code === "HTTP_403" || code.includes("UNAUTHORIZED") || code.includes("FORBIDDEN") || code.includes("INVALID_API_KEY");
2479
2773
  if (isCredentialError) {
2480
- const where = apiEndpoint.replace(/\/$/, "") === import_core9.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.";
2774
+ const where = apiEndpoint.replace(/\/$/, "") === import_core10.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.";
2481
2775
  console.error(
2482
2776
  '[Mushi] Credentials rejected. Check your projectId and apiKey (must have "report:write" scope). ' + where
2483
2777
  );
@@ -2493,17 +2787,18 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2493
2787
  };
2494
2788
  } else {
2495
2789
  config.callbacks?.onReportSubmitted?.(report.id);
2790
+ void writeReporterFlag(config.projectId, "has_reports", "1");
2496
2791
  return { ok: true };
2497
2792
  }
2498
2793
  },
2499
2794
  [config.projectId, config.callbacks, apiEndpoint]
2500
2795
  );
2501
- const userRef = (0, import_react4.useRef)(null);
2502
- const metadataRef = (0, import_react4.useRef)({});
2503
- const screenRef = (0, import_react4.useRef)(null);
2504
- const activityQueueRef = (0, import_react4.useRef)([]);
2505
- const rewardsFlushRef = (0, import_react4.useRef)(null);
2506
- (0, import_react4.useEffect)(() => {
2796
+ const userRef = (0, import_react5.useRef)(null);
2797
+ const metadataRef = (0, import_react5.useRef)({});
2798
+ const screenRef = (0, import_react5.useRef)(null);
2799
+ const activityQueueRef = (0, import_react5.useRef)([]);
2800
+ const rewardsFlushRef = (0, import_react5.useRef)(null);
2801
+ (0, import_react5.useEffect)(() => {
2507
2802
  const rewards = config.rewards;
2508
2803
  if (!rewards?.enabled || !rewards.trackActivity) return;
2509
2804
  const flushMs = Math.max(3e4, rewards.flushIntervalMs ?? 3e5);
@@ -2523,9 +2818,9 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2523
2818
  if (rewardsFlushRef.current) clearInterval(rewardsFlushRef.current);
2524
2819
  };
2525
2820
  }, [config.rewards?.enabled, config.rewards?.trackActivity, config.rewards?.flushIntervalMs]);
2526
- const eventTrackerRef = (0, import_react4.useRef)(null);
2527
- const sessionTrackerRef = (0, import_react4.useRef)(null);
2528
- (0, import_react4.useEffect)(() => {
2821
+ const eventTrackerRef = (0, import_react5.useRef)(null);
2822
+ const sessionTrackerRef = (0, import_react5.useRef)(null);
2823
+ (0, import_react5.useEffect)(() => {
2529
2824
  const getRoute = () => screenRef.current?.route ?? screenRef.current?.name ?? null;
2530
2825
  const tracker = createRNEventTracker({
2531
2826
  projectId: config.projectId,
@@ -2538,7 +2833,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2538
2833
  getRoute,
2539
2834
  sdkVersion: MUSHI_SDK_VERSION,
2540
2835
  config: config.analytics,
2541
- scrub: import_core9.scrubPii
2836
+ scrub: import_core10.scrubPii
2542
2837
  });
2543
2838
  eventTrackerRef.current = tracker;
2544
2839
  const sessions = config.trackSessions === false ? null : createRNSessionTracker({
@@ -2556,8 +2851,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2556
2851
  sessionTrackerRef.current = sessions;
2557
2852
  let sub;
2558
2853
  try {
2559
- if (typeof import_react_native5.AppState?.addEventListener === "function") {
2560
- sub = import_react_native5.AppState.addEventListener("change", (state) => {
2854
+ if (typeof import_react_native6.AppState?.addEventListener === "function") {
2855
+ sub = import_react_native6.AppState.addEventListener("change", (state) => {
2561
2856
  sessions?.onAppStateChange(state);
2562
2857
  if (state !== "active") tracker.flush().catch(() => {
2563
2858
  });
@@ -2580,7 +2875,71 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2580
2875
  config.analytics?.flushIntervalMs,
2581
2876
  config.trackSessions
2582
2877
  ]);
2583
- const instance = (0, import_react4.useMemo)(
2878
+ const reporterUpdatesRef = (0, import_react5.useRef)(
2879
+ createReporterUpdateHub(async () => {
2880
+ const client = apiClientRef.current;
2881
+ if (!client) return null;
2882
+ await reporterTokenReadyRef.current;
2883
+ const res = await (0, import_reporter_channels2.reporterChannels)(client).getUpdates(reporterTokenRef.current);
2884
+ return res.ok ? res.data ?? null : null;
2885
+ })
2886
+ );
2887
+ (0, import_react5.useEffect)(() => {
2888
+ let sub;
2889
+ try {
2890
+ if (typeof import_react_native6.AppState?.addEventListener === "function") {
2891
+ sub = import_react_native6.AppState.addEventListener("change", (state) => {
2892
+ if (state === "active" && reporterUpdatesRef.current.listenerCount() > 0) void reporterUpdatesRef.current.emit();
2893
+ });
2894
+ }
2895
+ } catch {
2896
+ }
2897
+ return () => sub?.remove();
2898
+ }, []);
2899
+ const [toast, setToast] = (0, import_react5.useState)(null);
2900
+ const toastShownRef = (0, import_react5.useRef)(false);
2901
+ const toastEnabled = config.notifications?.toast !== false;
2902
+ const maybeShowToast = (0, import_react5.useCallback)(async () => {
2903
+ if (!toastEnabled || toastShownRef.current) return;
2904
+ if (await readReporterFlag(config.projectId, "has_reports") !== "1") return;
2905
+ const updates = await reporterUpdatesRef.current.emit();
2906
+ if (!updates) return;
2907
+ const now = Date.now();
2908
+ const show = (0, import_reporter_ui3.reporterShouldShowToast)({
2909
+ enabled: toastEnabled,
2910
+ unreadTotal: updates.unread_total,
2911
+ shownThisSession: toastShownRef.current,
2912
+ lastShownAt: Number(await readReporterFlag(config.projectId, "toast_shown_at")) || null,
2913
+ now
2914
+ });
2915
+ if (!show) return;
2916
+ toastShownRef.current = true;
2917
+ void writeReporterFlag(config.projectId, "toast_shown_at", String(now));
2918
+ const locale = (0, import_reporter_ui3.resolveReporterLocale)(
2919
+ typeof navigator !== "undefined" ? navigator.language : void 0
2920
+ );
2921
+ setToast({ message: (0, import_reporter_ui3.reporterToastMessage)(updates, locale), reportId: updates.latest[0]?.report_id ?? null });
2922
+ }, [toastEnabled, config.projectId]);
2923
+ (0, import_react5.useEffect)(() => {
2924
+ void reporterTokenReadyRef.current.then(() => maybeShowToast());
2925
+ let sub;
2926
+ try {
2927
+ if (typeof import_react_native6.AppState?.addEventListener === "function") {
2928
+ sub = import_react_native6.AppState.addEventListener("change", (state) => {
2929
+ if (state === "active") void maybeShowToast();
2930
+ });
2931
+ }
2932
+ } catch {
2933
+ }
2934
+ return () => sub?.remove();
2935
+ }, [maybeShowToast]);
2936
+ const openMyReports = (0, import_react5.useCallback)(() => {
2937
+ setToast(null);
2938
+ setSheetScreenshot(null);
2939
+ setSheetPreferredTab("inbox");
2940
+ setSheetVisible(true);
2941
+ }, []);
2942
+ const instance = (0, import_react5.useMemo)(
2584
2943
  () => ({
2585
2944
  open,
2586
2945
  close,
@@ -2611,7 +2970,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2611
2970
  identifyWithToken(token) {
2612
2971
  userTokenRef.current = token && typeof token === "string" ? token : null;
2613
2972
  if (userTokenRef.current) {
2614
- const claims = (0, import_core9.parseIdentityToken)(userTokenRef.current);
2973
+ const claims = (0, import_core10.parseIdentityToken)(userTokenRef.current);
2615
2974
  if (claims?.sub) {
2616
2975
  userRef.current = {
2617
2976
  id: claims.sub,
@@ -2683,6 +3042,37 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2683
3042
  await reporterTokenReadyRef.current;
2684
3043
  return markReporterReportRead(client, reporterTokenRef.current, reportId);
2685
3044
  },
3045
+ getReporterUpdates: () => reporterUpdatesRef.current.emit(),
3046
+ async loadMyReportDetail(reportId) {
3047
+ const client = apiClientRef.current;
3048
+ if (!client) return null;
3049
+ try {
3050
+ await reporterTokenReadyRef.current;
3051
+ const res = await (0, import_reporter_channels2.reporterChannels)(client).getReport(reportId, reporterTokenRef.current);
3052
+ if (res.ok) return res.data ?? null;
3053
+ return res.error?.status === 404 && res.error.code !== "NOT_FOUND" ? "unsupported" : null;
3054
+ } catch {
3055
+ return null;
3056
+ }
3057
+ },
3058
+ emailPrefill() {
3059
+ return config.notifications?.emailFromIdentity ? userRef.current?.email ?? null : null;
3060
+ },
3061
+ openMyReports,
3062
+ onReporterUpdate: (cb) => reporterUpdatesRef.current.subscribe(cb),
3063
+ async getNotificationPrefs() {
3064
+ const client = apiClientRef.current;
3065
+ if (!client) return null;
3066
+ await reporterTokenReadyRef.current;
3067
+ const res = await (0, import_reporter_channels2.reporterChannels)(client).getPrefs(reporterTokenRef.current);
3068
+ return res.ok ? res.data ?? null : null;
3069
+ },
3070
+ async setNotificationPrefs(update) {
3071
+ const client = apiClientRef.current;
3072
+ if (!client) return { ok: false, error: { code: "NOT_INITIALIZED", message: "Mushi is not ready yet" } };
3073
+ await reporterTokenReadyRef.current;
3074
+ return (0, import_reporter_channels2.reporterChannels)(client).setPrefs(reporterTokenRef.current, update);
3075
+ },
2686
3076
  async replyToReport(reportId, body) {
2687
3077
  const client = apiClientRef.current;
2688
3078
  if (!client) return null;
@@ -2753,14 +3143,14 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2753
3143
  return res.ok ? res.data : null;
2754
3144
  }
2755
3145
  }),
2756
- [open, close, attachTo, submitReport, openAssistant, config.rewards?.enabled, config.assistant?.enabled]
3146
+ [open, close, attachTo, submitReport, openAssistant, openMyReports, config.rewards?.enabled, config.assistant?.enabled, config.notifications?.emailFromIdentity]
2757
3147
  );
2758
3148
  const trigger = config.widget?.trigger ?? "button";
2759
3149
  const showBanner = trigger === "banner" && !bannerDismissed;
2760
3150
  const showFab = !showBanner && (trigger === "button" || trigger === "both" || trigger === "auto" || trigger === "edge-tab");
2761
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(MushiContext.Provider, { value: instance, children: [
3151
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(MushiContext.Provider, { value: instance, children: [
2762
3152
  children,
2763
- showBanner ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3153
+ showBanner ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2764
3154
  MushiBanner,
2765
3155
  {
2766
3156
  onPress: open,
@@ -2769,7 +3159,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2769
3159
  label: config.widget?.banner?.label
2770
3160
  }
2771
3161
  ) : null,
2772
- showFab && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3162
+ showFab && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2773
3163
  MushiFloatingButton,
2774
3164
  {
2775
3165
  onPress: open,
@@ -2777,7 +3167,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2777
3167
  inset: config.widget?.inset
2778
3168
  }
2779
3169
  ),
2780
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3170
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2781
3171
  MushiBottomSheet,
2782
3172
  {
2783
3173
  visible: sheetVisible,
@@ -2791,13 +3181,24 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2791
3181
  assistantGreeting: config.assistant?.greeting,
2792
3182
  assistantSuggestions: config.assistant?.suggestions,
2793
3183
  inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0,
3184
+ theme: config.widget?.theme,
3185
+ minDescriptionLength: config.widget?.minDescriptionLength,
3186
+ emailOptIn: config.notifications?.email !== false
3187
+ }
3188
+ ),
3189
+ toast ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3190
+ MushiUpdateToast,
3191
+ {
3192
+ message: toast.message,
3193
+ onView: openMyReports,
3194
+ onDismiss: () => setToast(null),
2794
3195
  theme: config.widget?.theme
2795
3196
  }
2796
- )
3197
+ ) : null
2797
3198
  ] });
2798
3199
  }
2799
3200
  function useMushiContext() {
2800
- return (0, import_react4.useContext)(MushiContext);
3201
+ return (0, import_react5.useContext)(MushiContext);
2801
3202
  }
2802
3203
 
2803
3204
  // src/hooks/useMushi.ts
@@ -2806,10 +3207,10 @@ function useMushi() {
2806
3207
  }
2807
3208
 
2808
3209
  // src/hooks/useMushiReport.ts
2809
- var import_react5 = require("react");
3210
+ var import_react6 = require("react");
2810
3211
  function useMushiReport() {
2811
3212
  const mushi = useMushiContext();
2812
- const submitReport = (0, import_react5.useCallback)(
3213
+ const submitReport = (0, import_react6.useCallback)(
2813
3214
  async (data) => {
2814
3215
  if (!mushi) throw new Error("MushiProvider not found");
2815
3216
  await mushi.submitReport(data);
@@ -2820,22 +3221,22 @@ function useMushiReport() {
2820
3221
  }
2821
3222
 
2822
3223
  // src/hooks/useMushiWidget.ts
2823
- var import_react6 = require("react");
3224
+ var import_react7 = require("react");
2824
3225
  function useMushiWidget() {
2825
3226
  const mushi = useMushiContext();
2826
3227
  return {
2827
- open: (0, import_react6.useCallback)(() => mushi?.open(), [mushi]),
2828
- close: (0, import_react6.useCallback)(() => mushi?.close(), [mushi])
3228
+ open: (0, import_react7.useCallback)(() => mushi?.open(), [mushi]),
3229
+ close: (0, import_react7.useCallback)(() => mushi?.close(), [mushi])
2829
3230
  };
2830
3231
  }
2831
3232
 
2832
3233
  // src/components/MushiTrigger.tsx
2833
- var import_react7 = require("react");
3234
+ var import_react8 = require("react");
2834
3235
  function MushiTrigger({ children }) {
2835
3236
  const mushi = useMushiContext();
2836
- if (!(0, import_react7.isValidElement)(children)) return children;
3237
+ if (!(0, import_react8.isValidElement)(children)) return children;
2837
3238
  const originalOnPress = children.props.onPress;
2838
- return (0, import_react7.cloneElement)(children, {
3239
+ return (0, import_react8.cloneElement)(children, {
2839
3240
  onPress: () => {
2840
3241
  originalOnPress?.();
2841
3242
  mushi?.open();
@@ -2844,10 +3245,10 @@ function MushiTrigger({ children }) {
2844
3245
  }
2845
3246
 
2846
3247
  // src/capture/navigation-capture.ts
2847
- var import_react8 = require("react");
3248
+ var import_react9 = require("react");
2848
3249
  function useNavigationCapture(navigationRef) {
2849
- const historyRef = (0, import_react8.useRef)([]);
2850
- (0, import_react8.useEffect)(() => {
3250
+ const historyRef = (0, import_react9.useRef)([]);
3251
+ (0, import_react9.useEffect)(() => {
2851
3252
  if (!navigationRef?.addListener || !navigationRef?.getCurrentRoute) return;
2852
3253
  const unsubscribe = navigationRef.addListener("state", () => {
2853
3254
  const route = navigationRef.getCurrentRoute?.();