@mushi-mushi/react-native 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -50,7 +50,7 @@ module.exports = __toCommonJS(index_exports);
50
50
  // src/provider.tsx
51
51
  var import_react4 = require("react");
52
52
  var import_react_native5 = require("react-native");
53
- var import_core8 = require("@mushi-mushi/core");
53
+ var import_core9 = require("@mushi-mushi/core");
54
54
 
55
55
  // src/capture/console-capture.ts
56
56
  var import_core = require("@mushi-mushi/core");
@@ -478,32 +478,99 @@ function sleep(ms) {
478
478
  // src/components/MushiBottomSheet.tsx
479
479
  var import_react = require("react");
480
480
  var import_react_native2 = require("react-native");
481
- var import_core3 = require("@mushi-mushi/core");
481
+ var import_core4 = require("@mushi-mushi/core");
482
+ var import_reporter_ui = require("@mushi-mushi/core/reporter-ui");
482
483
  var import_i18n = require("@mushi-mushi/web/i18n");
483
484
 
484
- // src/reporter-status.ts
485
- function reporterStatusShort(status) {
486
- switch (status) {
487
- case "new":
488
- case "queued":
489
- case "pending":
490
- case "submitted":
491
- return "Received";
492
- case "classified":
493
- case "triaged":
494
- case "grouped":
495
- case "dispatched":
496
- return "In review";
497
- case "fixing":
498
- return "Fixing";
499
- case "fixed":
500
- case "resolved":
501
- case "completed":
502
- return "Fixed";
503
- case "dismissed":
504
- return "Closed";
505
- default:
506
- return status.replace(/_/g, " ").slice(0, 16);
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
+ // src/reporter-thread.ts
535
+ var THREAD_LOAD_TIMEOUT_MS = 12e3;
536
+ function settleWithin(p, ms) {
537
+ return new Promise((resolve) => {
538
+ const timer = setTimeout(() => resolve(null), ms);
539
+ p.then(
540
+ (value) => {
541
+ clearTimeout(timer);
542
+ resolve(value);
543
+ },
544
+ () => {
545
+ clearTimeout(timer);
546
+ resolve(null);
547
+ }
548
+ );
549
+ });
550
+ }
551
+ function loadReporterThread(load, reportId, timeoutMs = THREAD_LOAD_TIMEOUT_MS) {
552
+ let started;
553
+ try {
554
+ started = load(reportId);
555
+ } catch {
556
+ return Promise.resolve(null);
557
+ }
558
+ return settleWithin(started, timeoutMs);
559
+ }
560
+ async function markReporterReportRead(client, reporterToken, reportId) {
561
+ try {
562
+ const res = await client.listNotifications(reporterToken, { limit: 100 });
563
+ if (!res.ok) return 0;
564
+ const rows = res.data?.notifications ?? [];
565
+ const ids = rows.filter((n) => !n.read_at && n.payload?.reportId === reportId).map((n) => String(n.id));
566
+ let marked = 0;
567
+ for (const id of ids) {
568
+ const r = await client.markNotificationRead(id, reporterToken);
569
+ if (r.ok) marked++;
570
+ }
571
+ return marked;
572
+ } catch {
573
+ return 0;
507
574
  }
508
575
  }
509
576
 
@@ -512,6 +579,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
512
579
  var { height: SCREEN_HEIGHT } = import_react_native2.Dimensions.get("window");
513
580
  var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
514
581
  var DISMISS_THRESHOLD = 80;
582
+ var MIN_DESCRIPTION = 20;
515
583
  var CATEGORY_KEYS = ["bug", "slow", "visual", "confusing", "other"];
516
584
  var CATEGORY_EMOJI = {
517
585
  bug: "\u{1F41B}",
@@ -520,7 +588,6 @@ var CATEGORY_EMOJI = {
520
588
  confusing: "\u{1F615}",
521
589
  other: "\u{1F4AC}"
522
590
  };
523
- var VERIFIABLE_STATUSES = /* @__PURE__ */ new Set(["fixed", "resolved", "verified"]);
524
591
  var MushiBottomSheet = ({
525
592
  visible,
526
593
  onClose,
@@ -529,13 +596,18 @@ var MushiBottomSheet = ({
529
596
  onClearScreenshot,
530
597
  screenshotSensitiveHint,
531
598
  assistantEnabled = false,
532
- assistantLabel = import_core3.MUSHI_COPY.assistantTab,
599
+ assistantLabel = import_core4.MUSHI_COPY.assistantTab,
533
600
  assistantGreeting,
534
601
  assistantSuggestions = [],
535
- inboxPollIntervalMs = 0
602
+ inboxPollIntervalMs = 0,
603
+ theme
536
604
  }) => {
537
605
  const mushi = useMushiContext();
538
606
  const t = (0, import_i18n.getLocale)();
607
+ const locale = (0, import_reporter_ui.resolveReporterLocale)(
608
+ typeof navigator !== "undefined" ? navigator.language : void 0
609
+ );
610
+ const rc = (0, import_reporter_ui.reporterCopy)(locale);
539
611
  const greeting = assistantGreeting ?? t.assistant.defaultGreeting;
540
612
  const scheme = (0, import_react_native2.useColorScheme)();
541
613
  const dark = scheme === "dark";
@@ -547,26 +619,37 @@ var MushiBottomSheet = ({
547
619
  const [submitError, setSubmitError] = (0, import_react.useState)(null);
548
620
  const [sheetTab, setSheetTab] = (0, import_react.useState)("report");
549
621
  const [inboxReports, setInboxReports] = (0, import_react.useState)([]);
622
+ const [inboxLoaded, setInboxLoaded] = (0, import_react.useState)(false);
550
623
  const [inboxLoading, setInboxLoading] = (0, import_react.useState)(false);
551
624
  const [selectedReportId, setSelectedReportId] = (0, import_react.useState)(null);
625
+ const selectedRef = (0, import_react.useRef)(null);
552
626
  const [threadComments, setThreadComments] = (0, import_react.useState)([]);
627
+ const [threadStatus, setThreadStatus] = (0, import_react.useState)("idle");
553
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);
554
632
  const [assistantInput, setAssistantInput] = (0, import_react.useState)("");
555
633
  const [assistantThreadId, setAssistantThreadId] = (0, import_react.useState)(null);
556
634
  const [assistantSending, setAssistantSending] = (0, import_react.useState)(false);
557
635
  const [assistantError, setAssistantError] = (0, import_react.useState)(null);
558
636
  const [assistantTurns, setAssistantTurns] = (0, import_react.useState)([]);
559
637
  const [screenshotAttached, setScreenshotAttached] = (0, import_react.useState)(true);
560
- const loadInbox = (0, import_react.useCallback)(async () => {
561
- if (!mushi?.listMyReports) return;
562
- setInboxLoading(true);
563
- try {
564
- const rows = await mushi.listMyReports();
565
- setInboxReports(rows);
566
- } finally {
567
- setInboxLoading(false);
568
- }
569
- }, [mushi]);
638
+ const loadInbox = (0, import_react.useCallback)(
639
+ async (opts = {}) => {
640
+ if (!mushi?.listMyReports) return;
641
+ if (!opts.silent) setInboxLoading(true);
642
+ try {
643
+ const rows = await mushi.listMyReports();
644
+ setInboxReports(rows.filter((r) => !(0, import_reporter_ui.reporterStatus)(r, locale).hidden));
645
+ setInboxLoaded(true);
646
+ } catch {
647
+ } finally {
648
+ if (!opts.silent) setInboxLoading(false);
649
+ }
650
+ },
651
+ [mushi, locale]
652
+ );
570
653
  (0, import_react.useEffect)(() => {
571
654
  if (visible) {
572
655
  setScreenshotAttached(true);
@@ -577,28 +660,87 @@ var MushiBottomSheet = ({
577
660
  (0, import_react.useEffect)(() => {
578
661
  if (!visible || sheetTab !== "inbox" || inboxPollIntervalMs <= 0) return;
579
662
  const timer = setInterval(() => {
580
- void loadInbox();
663
+ void loadInbox({ silent: true });
581
664
  }, inboxPollIntervalMs);
582
665
  return () => clearInterval(timer);
583
666
  }, [visible, sheetTab, inboxPollIntervalMs, loadInbox]);
584
- const openThread = (0, import_react.useCallback)(async (reportId) => {
585
- setSelectedReportId(reportId);
586
- if (!mushi?.listMyComments) return;
587
- const comments = await mushi.listMyComments(reportId);
588
- setThreadComments(comments);
589
- }, [mushi]);
667
+ const openThread = (0, import_react.useCallback)(
668
+ async (reportId, opts = {}) => {
669
+ selectedRef.current = reportId;
670
+ setSelectedReportId(reportId);
671
+ if (!opts.silent) {
672
+ setThreadComments([]);
673
+ setThreadStatus("loading");
674
+ setReplyState("idle");
675
+ setFeedbackError(false);
676
+ }
677
+ const load = mushi?.loadMyThread ?? mushi?.listMyComments;
678
+ const comments = load ? await loadReporterThread(load, reportId) : null;
679
+ if (selectedRef.current !== reportId) return;
680
+ if (comments === null) {
681
+ setThreadStatus((prev) => opts.silent && prev === "ready" ? "ready" : "error");
682
+ return;
683
+ }
684
+ setThreadComments(comments);
685
+ setThreadStatus("ready");
686
+ if (!opts.silent && mushi?.markReportRead) {
687
+ mushi.markReportRead(reportId).then((marked) => {
688
+ if (marked > 0) {
689
+ setInboxReports((rows) => rows.map((r) => r.id === reportId ? { ...r, unread_count: 0 } : r));
690
+ }
691
+ }).catch(() => void 0);
692
+ }
693
+ },
694
+ [mushi]
695
+ );
696
+ const closeThread = (0, import_react.useCallback)(() => {
697
+ selectedRef.current = null;
698
+ setSelectedReportId(null);
699
+ setThreadStatus("idle");
700
+ }, []);
590
701
  const sendReply = (0, import_react.useCallback)(async () => {
591
- if (!mushi?.replyToReport || !selectedReportId || !replyText.trim()) return;
592
- await mushi.replyToReport(selectedReportId, replyText.trim());
593
- setReplyText("");
594
- await openThread(selectedReportId);
595
- }, [mushi, selectedReportId, replyText, openThread]);
596
- const submitFeedback = (0, import_react.useCallback)(async (signal) => {
597
- if (!mushi?.submitFeedbackSignal || !selectedReportId) return;
598
- await mushi.submitFeedbackSignal(selectedReportId, signal);
599
- await loadInbox();
600
- await openThread(selectedReportId);
601
- }, [mushi, selectedReportId, loadInbox, openThread]);
702
+ const body = replyText.trim();
703
+ const reportId = selectedReportId;
704
+ if (!mushi?.replyToReport || !reportId || !body || replyState === "sending") return;
705
+ setReplyState("sending");
706
+ try {
707
+ const comment = await settleWithin(mushi.replyToReport(reportId, body), THREAD_LOAD_TIMEOUT_MS);
708
+ if (!comment) {
709
+ setReplyState("failed");
710
+ return;
711
+ }
712
+ setReplyText("");
713
+ setReplyState("idle");
714
+ if (selectedRef.current === reportId) {
715
+ setThreadComments((prev) => [...prev, comment]);
716
+ void openThread(reportId, { silent: true });
717
+ }
718
+ } catch {
719
+ setReplyState("failed");
720
+ }
721
+ }, [mushi, selectedReportId, replyText, replyState, openThread]);
722
+ const submitFeedback = (0, import_react.useCallback)(
723
+ async (signal) => {
724
+ const reportId = selectedReportId;
725
+ if (!mushi?.submitFeedbackSignal || !reportId || feedbackSending) return;
726
+ setFeedbackSending(true);
727
+ setFeedbackError(false);
728
+ try {
729
+ const outcome = await mushi.submitFeedbackSignal(reportId, signal);
730
+ if (!outcome) {
731
+ setFeedbackError(true);
732
+ return;
733
+ }
734
+ await loadInbox({ silent: true });
735
+ await openThread(reportId, { silent: true });
736
+ } catch {
737
+ setFeedbackError(true);
738
+ } finally {
739
+ setFeedbackSending(false);
740
+ }
741
+ },
742
+ [mushi, selectedReportId, feedbackSending, loadInbox, openThread]
743
+ );
602
744
  const sendAssistant = (0, import_react.useCallback)(async (message) => {
603
745
  if (!mushi?.askAssistant || !message.trim() || assistantSending) return;
604
746
  const trimmed = message.trim();
@@ -694,7 +836,7 @@ var MushiBottomSheet = ({
694
836
  const handleSubmit = async () => {
695
837
  if (!category || !description.trim() || !mushi) return;
696
838
  const trimmed = description.trim();
697
- if (trimmed.length < 20) {
839
+ if (trimmed.length < MIN_DESCRIPTION) {
698
840
  setSubmitError("Please write at least 20 characters so we can understand the issue.");
699
841
  return;
700
842
  }
@@ -729,11 +871,181 @@ var MushiBottomSheet = ({
729
871
  }
730
872
  };
731
873
  const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
732
- const pal = (0, import_core3.mushiPalette)(dark ? "dark" : "light");
733
- const colors = dark ? { bg: pal.paper, text: pal.ink, sub: pal.inkMuted, card: pal.paperRaised, accent: import_core3.MUSHI_BANNER_NEON.bg, accentInk: import_core3.MUSHI_BANNER_NEON.fg, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.6)", disabled: import_core3.MUSHI_CONTROL_DISABLED.dark, disabledText: pal.inkFaint } : { bg: pal.paperRaised, text: pal.ink, sub: pal.inkMuted, card: pal.paper, accent: import_core3.MUSHI_BANNER_NEON.bg, accentInk: import_core3.MUSHI_BANNER_NEON.fg, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.35)", disabled: import_core3.MUSHI_CONTROL_DISABLED.light, disabledText: pal.inkFaint };
874
+ const colors = resolveRNTheme(dark, theme);
875
+ const font = colors.fontFamily ? { fontFamily: colors.fontFamily } : null;
876
+ const radius = colors.radius;
734
877
  const sheetTabs = ["report", "inbox", ...assistantEnabled ? ["assistant"] : []];
735
- const canSubmit = !!category && description.trim().length >= 20 && (phase === "form" || phase === "error");
736
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
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;
880
+ const selectedReport = selectedReportId ? inboxReports.find((r) => r.id === selectedReportId) ?? null : null;
881
+ const selectedView = selectedReport ? (0, import_reporter_ui.reporterStatus)(selectedReport, locale) : null;
882
+ 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,
886
+ {
887
+ onPress: closeThread,
888
+ accessibilityRole: "button",
889
+ accessibilityLabel: t.widget.back,
890
+ 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: [
892
+ "\u2190 ",
893
+ t.widget.back
894
+ ] })
895
+ }
896
+ ),
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 ?? "" })
901
+ ] }) : 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,
906
+ {
907
+ onPress: () => selectedReportId && void openThread(selectedReportId),
908
+ 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 })
911
+ }
912
+ )
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,
918
+ {
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() })
933
+ ] }) : 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,
937
+ {
938
+ style: [s.verifyBtn, { backgroundColor: colors.accent, borderRadius: radius }],
939
+ onPress: () => void submitFeedback("confirms"),
940
+ disabled: feedbackSending,
941
+ accessibilityRole: "button",
942
+ 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 })
944
+ }
945
+ ),
946
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
947
+ import_react_native2.TouchableOpacity,
948
+ {
949
+ style: [s.verifyBtn, { backgroundColor: colors.border, borderRadius: radius }],
950
+ onPress: () => void submitFeedback("not_fixed"),
951
+ disabled: feedbackSending,
952
+ accessibilityRole: "button",
953
+ 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 })
955
+ }
956
+ )
957
+ ] }) : 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,
962
+ {
963
+ style: [
964
+ s.input,
965
+ s.composerInput,
966
+ { backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius },
967
+ font
968
+ ],
969
+ placeholder: t.flows.thread.replyPlaceholder,
970
+ placeholderTextColor: colors.muted,
971
+ value: replyText,
972
+ onChangeText: (text) => {
973
+ setReplyText(text.slice(0, 2e3));
974
+ if (replyState === "failed") setReplyState("idle");
975
+ },
976
+ multiline: true,
977
+ maxLength: 2e3,
978
+ editable: replyState !== "sending",
979
+ accessibilityLabel: t.flows.thread.replyPlaceholder
980
+ }
981
+ ),
982
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
983
+ import_react_native2.TouchableOpacity,
984
+ {
985
+ style: [
986
+ s.sendBtn,
987
+ {
988
+ backgroundColor: replyText.trim() && replyState !== "sending" ? colors.accent : colors.disabled,
989
+ borderRadius: radius
990
+ }
991
+ ],
992
+ onPress: () => void sendReply(),
993
+ disabled: !replyText.trim() || replyState === "sending",
994
+ accessibilityRole: "button",
995
+ accessibilityLabel: replyState === "failed" ? rc.ui.retry : t.flows.thread.send,
996
+ accessibilityState: { disabled: !replyText.trim() || replyState === "sending", busy: replyState === "sending" },
997
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
998
+ import_react_native2.Text,
999
+ {
1000
+ style: [
1001
+ s.submitText,
1002
+ { color: replyText.trim() && replyState !== "sending" ? colors.accentFg : colors.disabledFg },
1003
+ font
1004
+ ],
1005
+ children: replyState === "failed" ? rc.ui.retry : t.flows.thread.send
1006
+ }
1007
+ )
1008
+ }
1009
+ )
1010
+ ] }),
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: [
1012
+ rc.ui.sendFailed,
1013
+ " \xB7 ",
1014
+ rc.ui.retry
1015
+ ] }) : null
1016
+ ] });
1017
+ const renderInboxList = () => {
1018
+ 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 });
1020
+ }
1021
+ 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 });
1023
+ }
1024
+ return inboxReports.map((r) => {
1025
+ const view = (0, import_reporter_ui.reporterStatus)(r, locale);
1026
+ const unread = (r.unread_count ?? 0) > 0;
1027
+ 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,
1030
+ {
1031
+ style: [s.inboxRow, { borderColor: colors.border }],
1032
+ onPress: () => void openThread(r.id),
1033
+ accessibilityRole: "button",
1034
+ accessibilityLabel: `${view.label}. ${title}${unread ? `. ${r.unread_count} new` : ""}`,
1035
+ 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: [
1038
+ view.label,
1039
+ view.othersNote ? ` \xB7 ${view.othersNote}` : ""
1040
+ ] }),
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
1042
+ ]
1043
+ },
1044
+ r.id
1045
+ );
1046
+ });
1047
+ };
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)(
737
1049
  import_react_native2.KeyboardAvoidingView,
738
1050
  {
739
1051
  style: s.flex,
@@ -744,44 +1056,69 @@ var MushiBottomSheet = ({
744
1056
  import_react_native2.Animated.View,
745
1057
  {
746
1058
  style: [s.backdrop, { backgroundColor: colors.backdrop, opacity: backdropOpacity }],
747
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: s.flex, activeOpacity: 1, onPress: handleClose })
1059
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1060
+ import_react_native2.TouchableOpacity,
1061
+ {
1062
+ style: s.flex,
1063
+ activeOpacity: 1,
1064
+ onPress: handleClose,
1065
+ accessibilityRole: "button",
1066
+ accessibilityLabel: t.widget.close
1067
+ }
1068
+ )
748
1069
  }
749
1070
  ),
750
1071
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
751
1072
  import_react_native2.Animated.View,
752
1073
  {
1074
+ accessibilityViewIsModal: true,
1075
+ onAccessibilityEscape: handleClose,
753
1076
  style: [
754
1077
  s.sheet,
755
1078
  { backgroundColor: colors.bg, transform: [{ translateY }] }
756
1079
  ],
757
1080
  children: [
758
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
759
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.brandHeader, { backgroundColor: import_core3.MUSHI_BANNER_NEON.bg, borderBottomColor: import_core3.MUSHI_BANNER_NEON.border }], children: [
760
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.brandEyebrow, { color: import_core3.MUSHI_BANNER_NEON.fg }], children: "MUSHI \xB7 BETA" }),
761
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.brandTitle, { color: import_core3.MUSHI_BANNER_NEON.fg }], children: sheetTab === "assistant" ? assistantLabel : sheetTab === "inbox" ? t.flows.reports.title : t.widget.title })
762
- ] }),
763
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.tabRow, { borderBottomColor: colors.border }], children: sheetTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1081
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1082
+ import_react_native2.View,
1083
+ {
1084
+ ...panResponder.panHandlers,
1085
+ style: s.handleArea,
1086
+ accessible: true,
1087
+ accessibilityRole: "adjustable",
1088
+ accessibilityLabel: t.widget.close,
1089
+ accessibilityActions: [{ name: "decrement", label: t.widget.close }, { name: "escape" }],
1090
+ onAccessibilityAction: (e) => {
1091
+ if (e.nativeEvent.actionName === "decrement" || e.nativeEvent.actionName === "escape") handleClose();
1092
+ },
1093
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.handle, { backgroundColor: colors.muted }] })
1094
+ }
1095
+ ),
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)(
764
1098
  import_react_native2.TouchableOpacity,
765
1099
  {
766
1100
  onPress: () => {
767
1101
  setSheetTab(tab);
768
- if (tab === "inbox") void loadInbox();
769
- else setSelectedReportId(null);
1102
+ if (tab === "inbox") void loadInbox({ silent: inboxLoaded });
1103
+ else closeThread();
770
1104
  },
771
1105
  style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
772
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.text : colors.sub }], children: tab === "report" ? t.widget.trigger : tab === "inbox" ? t.flows.reports.title : assistantLabel })
1106
+ accessibilityRole: "tab",
1107
+ 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 })
773
1109
  },
774
1110
  tab
775
1111
  )) }),
776
1112
  sheetTab === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
777
1113
  assistantTurns.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
778
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, marginBottom: 12, lineHeight: 20 }, children: greeting }),
1114
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, marginBottom: 12, lineHeight: 20 }, font], children: greeting }),
779
1115
  assistantSuggestions.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
780
1116
  import_react_native2.TouchableOpacity,
781
1117
  {
782
- style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.card }],
1118
+ style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.surface }],
783
1119
  onPress: () => void sendAssistant(chip),
784
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text, fontSize: 13 }, children: chip })
1120
+ accessibilityRole: "button",
1121
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg, fontSize: 13 }, font], children: chip })
785
1122
  },
786
1123
  chip
787
1124
  ))
@@ -792,25 +1129,34 @@ var MushiBottomSheet = ({
792
1129
  s.assistantBubble,
793
1130
  {
794
1131
  alignSelf: turn.role === "user" ? "flex-end" : "flex-start",
795
- backgroundColor: turn.role === "user" ? colors.accent : colors.card
1132
+ backgroundColor: turn.role === "user" ? colors.accent : colors.surface
796
1133
  }
797
1134
  ],
798
1135
  children: [
799
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: turn.role === "user" ? colors.accentInk : colors.text }, children: turn.text }),
800
- turn.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { onPress: () => void sendAssistant(opt), style: { marginTop: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.accent, fontSize: 12 }, children: opt }) }, opt))
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,
1139
+ {
1140
+ onPress: () => void sendAssistant(opt),
1141
+ style: { marginTop: 8 },
1142
+ accessibilityRole: "button",
1143
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.accent, fontSize: 12 }, font], children: opt })
1144
+ },
1145
+ opt
1146
+ ))
801
1147
  ]
802
1148
  },
803
1149
  `${turn.role}-${idx}`
804
1150
  )),
805
- assistantSending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, marginTop: 8 }, accessibilityLiveRegion: "polite", children: t.assistant.thinking }) : null,
806
- assistantError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: pal.danger, marginTop: 8 }, accessibilityRole: "alert", children: assistantError }) : null,
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,
807
1153
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.assistantComposer, children: [
808
1154
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
809
1155
  import_react_native2.TextInput,
810
1156
  {
811
- style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.card, color: colors.text, borderColor: colors.border }],
1157
+ style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius }, font],
812
1158
  placeholder: t.assistant.inputPlaceholder,
813
- placeholderTextColor: colors.sub,
1159
+ placeholderTextColor: colors.muted,
814
1160
  value: assistantInput,
815
1161
  onChangeText: setAssistantInput,
816
1162
  editable: !assistantSending
@@ -819,45 +1165,19 @@ var MushiBottomSheet = ({
819
1165
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
820
1166
  import_react_native2.TouchableOpacity,
821
1167
  {
822
- style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16 }],
1168
+ style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16, borderRadius: radius }],
823
1169
  onPress: () => void sendAssistant(assistantInput),
824
1170
  disabled: !assistantInput.trim() || assistantSending,
825
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: "\u2191" })
1171
+ accessibilityRole: "button",
1172
+ accessibilityLabel: t.flows.thread.send,
1173
+ 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" })
826
1175
  }
827
1176
  )
828
1177
  ] })
829
- ] }) : sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: inboxLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub }, children: t.flows.reports.loading }) : selectedReportId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
830
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.Text, { style: { color: colors.accent, marginBottom: 8 }, children: [
831
- "\u2190 ",
832
- t.widget.back
833
- ] }) }),
834
- threadComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
835
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
836
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text }, children: c.body })
837
- ] }, c.id)),
838
- VERIFIABLE_STATUSES.has(
839
- inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
840
- ) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.verifyRow, children: [
841
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: t.flows.thread.confirmFixed }) }),
842
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.text }], children: t.flows.thread.notFixed }) })
843
- ] }),
844
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
845
- import_react_native2.TextInput,
846
- {
847
- style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
848
- placeholder: t.flows.thread.replyPlaceholder,
849
- placeholderTextColor: colors.sub,
850
- value: replyText,
851
- onChangeText: setReplyText
852
- }
853
- ),
854
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: t.flows.thread.send }) })
855
- ] }) : inboxReports.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
856
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
857
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: reporterStatusShort(r.status) })
858
- ] }, r.id)) }) : phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, children: [
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: [
859
1179
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentEmoji], children: "\u2705" }),
860
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.text }], children: t.widget.submitted })
1180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.fg }, font], children: t.widget.submitted })
861
1181
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
862
1182
  import_react_native2.ScrollView,
863
1183
  {
@@ -865,29 +1185,34 @@ var MushiBottomSheet = ({
865
1185
  keyboardShouldPersistTaps: "handled",
866
1186
  contentContainerStyle: { paddingBottom: 24 },
867
1187
  children: [
868
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.stepLabel, { color: colors.sub }], children: t.step1.heading }),
869
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.catRow, children: CATEGORY_KEYS.map((key) => {
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) => {
870
1190
  const active = category === key;
871
1191
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
872
1192
  import_react_native2.TouchableOpacity,
873
1193
  {
874
1194
  onPress: () => setCategory(key),
875
1195
  activeOpacity: 0.7,
1196
+ accessibilityRole: "radio",
1197
+ accessibilityState: { selected: active, checked: active },
1198
+ accessibilityLabel: t.step1.categories[key],
876
1199
  style: [
877
1200
  s.catBtn,
878
1201
  {
879
- backgroundColor: active ? colors.accent : colors.card,
1202
+ backgroundColor: active ? colors.accent : colors.surface,
880
1203
  borderColor: active ? colors.accent : colors.border
881
1204
  }
882
1205
  ],
883
1206
  children: [
884
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.catEmoji, children: CATEGORY_EMOJI[key] }),
1207
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.catEmoji, importantForAccessibility: "no", children: CATEGORY_EMOJI[key] }),
885
1208
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
886
1209
  import_react_native2.Text,
887
1210
  {
1211
+ numberOfLines: 1,
888
1212
  style: [
889
1213
  s.catLabel,
890
- { color: active ? colors.accentInk : colors.text }
1214
+ { color: active ? colors.accentFg : colors.fg },
1215
+ font
891
1216
  ],
892
1217
  children: t.step1.categories[key]
893
1218
  }
@@ -903,13 +1228,16 @@ var MushiBottomSheet = ({
903
1228
  style: [
904
1229
  s.input,
905
1230
  {
906
- backgroundColor: colors.card,
907
- color: colors.text,
908
- borderColor: colors.border
909
- }
1231
+ backgroundColor: colors.surface,
1232
+ color: colors.fg,
1233
+ borderColor: colors.border,
1234
+ borderRadius: radius
1235
+ },
1236
+ font
910
1237
  ],
911
1238
  placeholder: t.step3.descriptionPlaceholder,
912
- placeholderTextColor: colors.sub,
1239
+ placeholderTextColor: colors.muted,
1240
+ accessibilityLabel: t.step3.descriptionPlaceholder,
913
1241
  multiline: true,
914
1242
  textAlignVertical: "top",
915
1243
  value: description,
@@ -922,19 +1250,19 @@ var MushiBottomSheet = ({
922
1250
  maxLength: 4e3
923
1251
  }
924
1252
  ),
925
- submitError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.accent, marginTop: 6, fontSize: 12 }, children: submitError }) : null,
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,
926
1254
  activeScreenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotRow, children: [
927
1255
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
928
1256
  import_react_native2.Image,
929
1257
  {
930
1258
  source: { uri: activeScreenshot },
931
- style: [s.screenshotThumb, { backgroundColor: colors.text }],
1259
+ style: [s.screenshotThumb, { backgroundColor: colors.fg }],
932
1260
  accessibilityLabel: t.step3.screenshotPreviewAlt
933
1261
  }
934
1262
  ),
935
1263
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotMeta, children: [
936
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.text }], children: t.step3.screenshotAttached.replace(" \u2713", "") }),
937
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotSub, { color: colors.sub }], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : t.step3.screenshotSensitiveHint.split("\u2014")[0]?.trim() ?? t.step3.screenshotSensitiveHint })
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 })
938
1266
  ] }),
939
1267
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
940
1268
  import_react_native2.TouchableOpacity,
@@ -944,22 +1272,28 @@ var MushiBottomSheet = ({
944
1272
  onClearScreenshot?.();
945
1273
  },
946
1274
  hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
1275
+ accessibilityRole: "button",
947
1276
  accessibilityLabel: t.widget.close,
948
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
1277
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.muted }], children: "\u2715" })
949
1278
  }
950
1279
  )
951
1280
  ] }) : 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,
952
1282
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
953
1283
  import_react_native2.TouchableOpacity,
954
1284
  {
955
1285
  onPress: handleSubmit,
956
1286
  disabled: !canSubmit,
957
1287
  activeOpacity: 0.8,
1288
+ accessibilityRole: "button",
1289
+ accessibilityLabel: phase === "sending" ? t.widget.submitting : t.widget.submit,
1290
+ accessibilityHint: submitHint,
1291
+ accessibilityState: { disabled: !canSubmit, busy: phase === "sending" },
958
1292
  style: [
959
1293
  s.submitBtn,
960
- { backgroundColor: canSubmit ? colors.accent : colors.disabled }
1294
+ { backgroundColor: canSubmit ? colors.accent : colors.disabled, borderRadius: radius }
961
1295
  ],
962
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: canSubmit ? colors.accentInk : colors.disabledText }], children: phase === "sending" ? t.widget.submitting : t.widget.submit })
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 })
963
1297
  }
964
1298
  )
965
1299
  ]
@@ -989,10 +1323,10 @@ var s = import_react_native2.StyleSheet.create({
989
1323
  left: 0,
990
1324
  right: 0,
991
1325
  minHeight: SHEET_HEIGHT,
992
- borderTopLeftRadius: import_core3.MUSHI_RADIUS.sheet,
993
- borderTopRightRadius: import_core3.MUSHI_RADIUS.sheet,
994
- paddingBottom: import_react_native2.Platform.OS === "ios" ? 34 : import_core3.MUSHI_SPACING.roomy,
995
- shadowColor: import_core3.MUSHI_SHADOW_INK,
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,
996
1330
  shadowOffset: { width: 0, height: -3 },
997
1331
  shadowOpacity: 0.12,
998
1332
  shadowRadius: 8,
@@ -1000,88 +1334,72 @@ var s = import_react_native2.StyleSheet.create({
1000
1334
  },
1001
1335
  handleArea: {
1002
1336
  alignItems: "center",
1003
- paddingVertical: import_core3.MUSHI_SPACING.comfy
1337
+ paddingVertical: import_core4.MUSHI_SPACING.comfy
1004
1338
  },
1005
1339
  handle: {
1006
1340
  width: 40,
1007
1341
  height: 5,
1008
- borderRadius: import_core3.MUSHI_RADIUS.control,
1342
+ borderRadius: import_core4.MUSHI_RADIUS.control,
1009
1343
  opacity: 0.5
1010
1344
  },
1011
- brandHeader: {
1012
- paddingHorizontal: import_core3.MUSHI_SPACING.lounge,
1013
- paddingTop: import_core3.MUSHI_SPACING.comfy,
1014
- paddingBottom: import_core3.MUSHI_SPACING.comfy,
1015
- borderBottomWidth: 1.5
1345
+ header: {
1346
+ paddingHorizontal: import_core4.MUSHI_SPACING.lounge,
1347
+ paddingBottom: import_core4.MUSHI_SPACING.comfy
1016
1348
  },
1017
- brandEyebrow: {
1018
- fontSize: import_core3.MUSHI_TYPE.sizeLabel,
1019
- fontWeight: "700",
1020
- letterSpacing: 1.2,
1021
- marginBottom: 2,
1022
- opacity: 0.7
1023
- },
1024
- brandTitle: {
1349
+ headerTitle: {
1025
1350
  fontSize: 18,
1026
- fontWeight: "800",
1351
+ fontWeight: "700",
1027
1352
  letterSpacing: -0.2
1028
1353
  },
1029
1354
  body: {
1030
- paddingHorizontal: import_core3.MUSHI_SPACING.lounge,
1031
- paddingTop: import_core3.MUSHI_SPACING.tight
1032
- },
1033
- title: {
1034
- fontSize: 20,
1035
- fontWeight: "700",
1036
- marginBottom: import_core3.MUSHI_SPACING.roomy
1355
+ paddingHorizontal: import_core4.MUSHI_SPACING.lounge,
1356
+ paddingTop: import_core4.MUSHI_SPACING.tight
1037
1357
  },
1038
1358
  stepLabel: {
1039
- fontSize: import_core3.MUSHI_TYPE.sizeLabel,
1359
+ fontSize: import_core4.MUSHI_TYPE.sizeLabel,
1040
1360
  fontWeight: "600",
1041
1361
  letterSpacing: 0.2,
1042
- marginTop: import_core3.MUSHI_SPACING.snug,
1043
- marginBottom: import_core3.MUSHI_SPACING.comfy,
1362
+ marginTop: import_core4.MUSHI_SPACING.snug,
1363
+ marginBottom: import_core4.MUSHI_SPACING.comfy,
1044
1364
  textTransform: "uppercase"
1045
1365
  },
1046
1366
  catRow: {
1047
1367
  flexDirection: "row",
1048
- justifyContent: "space-between",
1049
- marginBottom: import_core3.MUSHI_SPACING.roomy
1368
+ flexWrap: "wrap",
1369
+ gap: 8,
1370
+ marginBottom: import_core4.MUSHI_SPACING.roomy
1050
1371
  },
1051
1372
  catBtn: {
1373
+ flexDirection: "row",
1052
1374
  alignItems: "center",
1053
- paddingVertical: import_core3.MUSHI_SPACING.comfy,
1054
- paddingHorizontal: 6,
1055
- borderRadius: import_core3.MUSHI_RADIUS.card,
1375
+ gap: 6,
1376
+ paddingVertical: 8,
1377
+ paddingHorizontal: 12,
1378
+ borderRadius: 999,
1056
1379
  borderWidth: 1,
1057
- flex: 1,
1058
- marginHorizontal: 3,
1059
1380
  minHeight: 44
1060
1381
  },
1061
1382
  catEmoji: {
1062
- fontSize: 22,
1063
- marginBottom: import_core3.MUSHI_SPACING.tight
1383
+ fontSize: 16
1064
1384
  },
1065
1385
  catLabel: {
1066
- fontSize: import_core3.MUSHI_TYPE.sizeLabel,
1386
+ fontSize: 14,
1067
1387
  fontWeight: "600"
1068
1388
  },
1069
1389
  input: {
1070
1390
  borderWidth: 1,
1071
- borderRadius: import_core3.MUSHI_RADIUS.card,
1072
1391
  padding: 14,
1073
1392
  fontSize: 15,
1074
1393
  minHeight: 100,
1075
- marginBottom: import_core3.MUSHI_SPACING.roomy
1394
+ marginBottom: import_core4.MUSHI_SPACING.roomy
1076
1395
  },
1077
1396
  submitBtn: {
1078
- borderRadius: import_core3.MUSHI_RADIUS.card,
1079
1397
  paddingVertical: 14,
1080
1398
  alignItems: "center",
1081
1399
  minHeight: 44
1082
1400
  },
1083
1401
  submitText: {
1084
- color: import_core3.MUSHI_INVERSE,
1402
+ color: import_core4.MUSHI_INVERSE,
1085
1403
  fontSize: 16,
1086
1404
  fontWeight: "700"
1087
1405
  },
@@ -1140,7 +1458,8 @@ var s = import_react_native2.StyleSheet.create({
1140
1458
  alignItems: "center",
1141
1459
  paddingVertical: 10,
1142
1460
  borderBottomWidth: 2,
1143
- borderBottomColor: "transparent"
1461
+ borderBottomColor: "transparent",
1462
+ minHeight: 44
1144
1463
  },
1145
1464
  tabLabel: {
1146
1465
  fontSize: 14,
@@ -1148,12 +1467,47 @@ var s = import_react_native2.StyleSheet.create({
1148
1467
  },
1149
1468
  inboxRow: {
1150
1469
  paddingVertical: 12,
1470
+ minHeight: 56,
1151
1471
  borderBottomWidth: import_react_native2.StyleSheet.hairlineWidth
1152
1472
  },
1473
+ headerCard: {
1474
+ borderWidth: import_react_native2.StyleSheet.hairlineWidth,
1475
+ padding: 12,
1476
+ marginBottom: 12
1477
+ },
1478
+ pill: {
1479
+ alignSelf: "flex-start",
1480
+ borderWidth: 1,
1481
+ borderRadius: 999,
1482
+ paddingHorizontal: 8,
1483
+ paddingVertical: 2,
1484
+ fontSize: 12,
1485
+ fontWeight: "600",
1486
+ overflow: "hidden"
1487
+ },
1488
+ skeleton: {
1489
+ height: 14,
1490
+ borderRadius: 6,
1491
+ marginBottom: 10
1492
+ },
1493
+ errorRow: {
1494
+ flexDirection: "row",
1495
+ alignItems: "center",
1496
+ gap: 8,
1497
+ marginBottom: 12
1498
+ },
1499
+ retryBtn: {
1500
+ borderWidth: 1,
1501
+ paddingHorizontal: 12,
1502
+ paddingVertical: 8,
1503
+ minHeight: 44,
1504
+ justifyContent: "center"
1505
+ },
1153
1506
  threadBubble: {
1154
1507
  borderRadius: 10,
1155
1508
  padding: 10,
1156
- marginBottom: 8
1509
+ marginBottom: 8,
1510
+ maxWidth: "88%"
1157
1511
  },
1158
1512
  verifyRow: {
1159
1513
  flexDirection: "row",
@@ -1162,9 +1516,26 @@ var s = import_react_native2.StyleSheet.create({
1162
1516
  },
1163
1517
  verifyBtn: {
1164
1518
  flex: 1,
1165
- borderRadius: 10,
1166
1519
  paddingVertical: 10,
1167
- alignItems: "center"
1520
+ alignItems: "center",
1521
+ minHeight: 44
1522
+ },
1523
+ composer: {
1524
+ flexDirection: "row",
1525
+ alignItems: "flex-end",
1526
+ gap: 8
1527
+ },
1528
+ composerInput: {
1529
+ flex: 1,
1530
+ minHeight: 44,
1531
+ maxHeight: 120,
1532
+ marginBottom: 0
1533
+ },
1534
+ sendBtn: {
1535
+ paddingHorizontal: 16,
1536
+ paddingVertical: 12,
1537
+ minHeight: 44,
1538
+ justifyContent: "center"
1168
1539
  },
1169
1540
  assistantBubble: {
1170
1541
  maxWidth: "88%",
@@ -1192,9 +1563,9 @@ var s = import_react_native2.StyleSheet.create({
1192
1563
  // src/components/MushiFloatingButton.tsx
1193
1564
  var import_react2 = require("react");
1194
1565
  var import_react_native3 = require("react-native");
1195
- var import_core4 = require("@mushi-mushi/core");
1566
+ var import_core5 = require("@mushi-mushi/core");
1196
1567
  var import_jsx_runtime2 = require("react/jsx-runtime");
1197
- var FAB_SIZE = import_core4.MUSHI_GEOMETRY.fabSize;
1568
+ var FAB_SIZE = import_core5.MUSHI_GEOMETRY.fabSize;
1198
1569
  var EDGE_PADDING = 12;
1199
1570
  function getScreenBounds() {
1200
1571
  const { width, height } = import_react_native3.Dimensions.get("window");
@@ -1244,7 +1615,7 @@ var MushiFloatingButton = ({
1244
1615
  const currentX = (0, import_react2.useRef)(baseX);
1245
1616
  const currentY = (0, import_react2.useRef)(0);
1246
1617
  const isDragging = (0, import_react2.useRef)(false);
1247
- const pal = (0, import_core4.mushiPalette)(dark ? "dark" : "light");
1618
+ const pal = (0, import_core5.mushiPalette)(dark ? "dark" : "light");
1248
1619
  const accentColor = accent ?? pal.accent;
1249
1620
  (0, import_react2.useEffect)(() => {
1250
1621
  if (!persist) return;
@@ -1355,7 +1726,7 @@ var MushiFloatingButton = ({
1355
1726
  onStartShouldSetResponder: () => !draggable,
1356
1727
  onResponderGrant: handlePressIn,
1357
1728
  onResponderRelease: handlePressOut,
1358
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.Text, { style: s2.emoji, children: import_core4.MUSHI_COPY.triggerText })
1729
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.Text, { style: s2.emoji, children: import_core5.MUSHI_COPY.triggerText })
1359
1730
  }
1360
1731
  );
1361
1732
  };
@@ -1381,14 +1752,14 @@ var s2 = import_react_native3.StyleSheet.create({
1381
1752
  // src/components/MushiBanner.tsx
1382
1753
  var import_react3 = require("react");
1383
1754
  var import_react_native4 = require("react-native");
1384
- var import_core5 = require("@mushi-mushi/core");
1755
+ var import_core6 = require("@mushi-mushi/core");
1385
1756
  var import_jsx_runtime3 = require("react/jsx-runtime");
1386
- var MUSHI_BANNER_DEFAULT_HEIGHT = import_core5.MUSHI_GEOMETRY.bannerHeight;
1757
+ var MUSHI_BANNER_DEFAULT_HEIGHT = import_core6.MUSHI_GEOMETRY.bannerHeight;
1387
1758
  var MushiBanner = ({
1388
1759
  onPress,
1389
1760
  onDismiss,
1390
- message = import_core5.MUSHI_COPY.bannerMessage,
1391
- label = import_core5.MUSHI_COPY.bannerLabel,
1761
+ message = import_core6.MUSHI_COPY.bannerMessage,
1762
+ label = import_core6.MUSHI_COPY.bannerLabel,
1392
1763
  onLayoutHeight
1393
1764
  }) => {
1394
1765
  const handleLayout = (0, import_react3.useCallback)(
@@ -1408,7 +1779,7 @@ var MushiBanner = ({
1408
1779
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.TouchableOpacity, { style: s3.main, onPress, activeOpacity: 0.85, children: [
1409
1780
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.eyebrow, children: label.toUpperCase() }),
1410
1781
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.message, numberOfLines: 2, children: message }),
1411
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.cta, children: import_core5.MUSHI_COPY.bugCta })
1782
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.cta, children: import_core6.MUSHI_COPY.bugCta })
1412
1783
  ] }),
1413
1784
  onDismiss ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1414
1785
  import_react_native4.TouchableOpacity,
@@ -1431,13 +1802,13 @@ var s3 = import_react_native4.StyleSheet.create({
1431
1802
  left: 0,
1432
1803
  right: 0,
1433
1804
  bottom: 0,
1434
- minHeight: import_core5.MUSHI_GEOMETRY.bannerHeight,
1805
+ minHeight: import_core6.MUSHI_GEOMETRY.bannerHeight,
1435
1806
  flexDirection: "row",
1436
1807
  alignItems: "stretch",
1437
- backgroundColor: import_core5.MUSHI_BANNER_NEON.bg,
1808
+ backgroundColor: import_core6.MUSHI_BANNER_NEON.bg,
1438
1809
  borderTopWidth: 1,
1439
- borderTopColor: import_core5.MUSHI_BANNER_NEON.border,
1440
- zIndex: import_core5.MUSHI_Z.banner,
1810
+ borderTopColor: import_core6.MUSHI_BANNER_NEON.border,
1811
+ zIndex: import_core6.MUSHI_Z.banner,
1441
1812
  elevation: 8,
1442
1813
  paddingBottom: import_react_native4.Platform.OS === "ios" ? 0 : 0
1443
1814
  },
@@ -1445,27 +1816,27 @@ var s3 = import_react_native4.StyleSheet.create({
1445
1816
  flex: 1,
1446
1817
  flexDirection: "row",
1447
1818
  alignItems: "center",
1448
- gap: import_core5.MUSHI_SPACING.snug,
1449
- paddingHorizontal: import_core5.MUSHI_SPACING.comfy,
1450
- paddingVertical: import_core5.MUSHI_SPACING.snug,
1819
+ gap: import_core6.MUSHI_SPACING.snug,
1820
+ paddingHorizontal: import_core6.MUSHI_SPACING.comfy,
1821
+ paddingVertical: import_core6.MUSHI_SPACING.snug,
1451
1822
  minHeight: 44
1452
1823
  },
1453
1824
  eyebrow: {
1454
- fontSize: import_core5.MUSHI_TYPE.sizeLabel,
1825
+ fontSize: import_core6.MUSHI_TYPE.sizeLabel,
1455
1826
  fontWeight: "700",
1456
1827
  letterSpacing: 1.2,
1457
- color: import_core5.MUSHI_BANNER_NEON.fg
1828
+ color: import_core6.MUSHI_BANNER_NEON.fg
1458
1829
  },
1459
1830
  message: {
1460
1831
  flex: 1,
1461
1832
  fontSize: 12,
1462
1833
  fontWeight: "500",
1463
- color: import_core5.MUSHI_BANNER_NEON.fg
1834
+ color: import_core6.MUSHI_BANNER_NEON.fg
1464
1835
  },
1465
1836
  cta: {
1466
1837
  fontSize: 12,
1467
1838
  fontWeight: "700",
1468
- color: import_core5.MUSHI_BANNER_NEON.fg,
1839
+ color: import_core6.MUSHI_BANNER_NEON.fg,
1469
1840
  textDecorationLine: "underline"
1470
1841
  },
1471
1842
  dismiss: {
@@ -1475,13 +1846,13 @@ var s3 = import_react_native4.StyleSheet.create({
1475
1846
  dismissText: {
1476
1847
  fontSize: 20,
1477
1848
  lineHeight: 22,
1478
- color: import_core5.MUSHI_BANNER_NEON.fg,
1849
+ color: import_core6.MUSHI_BANNER_NEON.fg,
1479
1850
  opacity: 0.7
1480
1851
  }
1481
1852
  });
1482
1853
 
1483
1854
  // src/analytics/event-tracker.ts
1484
- var import_core6 = require("@mushi-mushi/core");
1855
+ var import_core7 = require("@mushi-mushi/core");
1485
1856
  var CONSENT_BUFFER_MAX = 50;
1486
1857
  var SPILL_MAX = 200;
1487
1858
  var SPILL_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -1545,7 +1916,7 @@ function createRNEventTracker(opts) {
1545
1916
  const client = resolveClient();
1546
1917
  if (!client) return;
1547
1918
  flushing = true;
1548
- const batch = buffer.splice(0, import_core6.EVENT_PROPERTY_LIMITS.maxServerBatch);
1919
+ const batch = buffer.splice(0, import_core7.EVENT_PROPERTY_LIMITS.maxServerBatch);
1549
1920
  if (pendingIdentify) {
1550
1921
  batch.unshift({ name: "identify", ts: (/* @__PURE__ */ new Date()).toISOString(), properties: {} });
1551
1922
  pendingIdentify = false;
@@ -1569,7 +1940,7 @@ function createRNEventTracker(opts) {
1569
1940
  } finally {
1570
1941
  flushing = false;
1571
1942
  }
1572
- if (buffer.length >= import_core6.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
1943
+ if (buffer.length >= import_core7.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
1573
1944
  }
1574
1945
  function enqueue(ev) {
1575
1946
  if (consent === "denied") return;
@@ -1578,7 +1949,7 @@ function createRNEventTracker(opts) {
1578
1949
  return;
1579
1950
  }
1580
1951
  buffer.push(ev);
1581
- if (buffer.length >= import_core6.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
1952
+ if (buffer.length >= import_core7.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
1582
1953
  }
1583
1954
  const ready = enabled ? (async () => {
1584
1955
  storage = await loadStorage();
@@ -1618,17 +1989,17 @@ function createRNEventTracker(opts) {
1618
1989
  ready,
1619
1990
  track(name, properties, trackOpts) {
1620
1991
  if (!enabled || destroyed) return false;
1621
- if (!(0, import_core6.isValidEventName)(name)) return false;
1622
- const { properties: props } = (0, import_core6.sanitizeEventProperties)(properties, { allowlist, scrub: opts.scrub });
1992
+ if (!(0, import_core7.isValidEventName)(name)) return false;
1993
+ const { properties: props } = (0, import_core7.sanitizeEventProperties)(properties, { allowlist, scrub: opts.scrub });
1623
1994
  let merged = props;
1624
1995
  if (trackOpts?.reserved) {
1625
- const { properties: res } = (0, import_core6.sanitizeEventProperties)(trackOpts.reserved, { allowReserved: true, scrub: opts.scrub });
1996
+ const { properties: res } = (0, import_core7.sanitizeEventProperties)(trackOpts.reserved, { allowReserved: true, scrub: opts.scrub });
1626
1997
  merged = { ...props, ...res };
1627
1998
  }
1628
1999
  merged.$surface = SURFACE;
1629
2000
  const route = opts.getRoute?.();
1630
2001
  if (route && merged.$route === void 0) merged.$route = route;
1631
- if (!(0, import_core6.propertiesWithinByteLimit)(merged)) return false;
2002
+ if (!(0, import_core7.propertiesWithinByteLimit)(merged)) return false;
1632
2003
  enqueue({
1633
2004
  name,
1634
2005
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1697,13 +2068,13 @@ function createRNEventTracker(opts) {
1697
2068
  }
1698
2069
 
1699
2070
  // src/analytics/session-tracker.ts
1700
- var import_core7 = require("@mushi-mushi/core");
2071
+ var import_core8 = require("@mushi-mushi/core");
1701
2072
  var HEARTBEAT_INTERVAL_MS = 6e4;
1702
2073
  var RESUME_WINDOW_MS = 30 * 6e4;
1703
2074
  function createRNSessionTracker(opts) {
1704
2075
  const clock = opts.now ?? Date.now;
1705
2076
  const resolveClient = () => typeof opts.client === "function" ? opts.client() : opts.client;
1706
- let sessionId = opts.sessionId ?? `sess_${(0, import_core7.newUuid)()}`;
2077
+ let sessionId = opts.sessionId ?? `sess_${(0, import_core8.newUuid)()}`;
1707
2078
  let pageViewCount = 0;
1708
2079
  let started = false;
1709
2080
  let allowed = false;
@@ -1771,7 +2142,7 @@ function createRNSessionTracker(opts) {
1771
2142
  backgroundedAt = null;
1772
2143
  if (!allowed) return;
1773
2144
  if (!started || away >= RESUME_WINDOW_MS) {
1774
- sessionId = `sess_${(0, import_core7.newUuid)()}`;
2145
+ sessionId = `sess_${(0, import_core8.newUuid)()}`;
1775
2146
  startSession();
1776
2147
  } else {
1777
2148
  send("session_heartbeat", { route: route() });
@@ -1800,7 +2171,7 @@ function createRNSessionTracker(opts) {
1800
2171
 
1801
2172
  // src/version.ts
1802
2173
  var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
1803
- var MUSHI_SDK_VERSION = "0.22.0" ? "0.22.0" : "0.17.0";
2174
+ var MUSHI_SDK_VERSION = "0.23.0" ? "0.23.0" : "0.17.0";
1804
2175
 
1805
2176
  // src/provider.tsx
1806
2177
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -1852,9 +2223,9 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1852
2223
  const networkRef = (0, import_react4.useRef)(null);
1853
2224
  const queueRef = (0, import_react4.useRef)(null);
1854
2225
  const apiClientRef = (0, import_react4.useRef)(null);
1855
- const sessionIdRef = (0, import_react4.useRef)(`sess_${(0, import_core8.newUuid)()}`);
2226
+ const sessionIdRef = (0, import_react4.useRef)(`sess_${(0, import_core9.newUuid)()}`);
1856
2227
  const breadcrumbsRef = (0, import_react4.useRef)(
1857
- (0, import_core8.createBreadcrumbBuffer)({ max: config.capture?.maxBreadcrumbs ?? 50 })
2228
+ (0, import_core9.createBreadcrumbBuffer)({ max: config.capture?.maxBreadcrumbs ?? 50 })
1858
2229
  );
1859
2230
  const [sheetVisible, setSheetVisible] = (0, import_react4.useState)(false);
1860
2231
  const [sheetPreferredTab, setSheetPreferredTab] = (0, import_react4.useState)("report");
@@ -1879,7 +2250,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1879
2250
  if (config.endpoint && !config.apiEndpoint && __DEV__) {
1880
2251
  console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
1881
2252
  }
1882
- const apiEndpoint = rawEndpoint ?? import_core8.DEFAULT_API_ENDPOINT;
2253
+ const apiEndpoint = rawEndpoint ?? import_core9.DEFAULT_API_ENDPOINT;
1883
2254
  (0, import_react4.useEffect)(() => {
1884
2255
  if (config.capture?.console !== false) {
1885
2256
  consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
@@ -1887,7 +2258,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1887
2258
  if (config.capture?.network !== false) {
1888
2259
  networkRef.current = setupNetworkCapture(config.capture?.maxNetworkEntries, apiEndpoint);
1889
2260
  }
1890
- apiClientRef.current = (0, import_core8.createApiClient)({
2261
+ apiClientRef.current = (0, import_core9.createApiClient)({
1891
2262
  projectId: config.projectId,
1892
2263
  apiKey: config.apiKey,
1893
2264
  apiEndpoint,
@@ -1924,14 +2295,14 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
1924
2295
  if (existing && existing.startsWith("mushi_")) {
1925
2296
  reporterTokenRef.current = existing;
1926
2297
  } else {
1927
- const fresh = `mushi_${(0, import_core8.newUuid)()}`;
2298
+ const fresh = `mushi_${(0, import_core9.newUuid)()}`;
1928
2299
  reporterTokenRef.current = fresh;
1929
2300
  await saveReporterToken(fresh, secureStorage);
1930
2301
  }
1931
2302
  resolveReporterTokenReady();
1932
2303
  } catch {
1933
2304
  if (reporterTokenRef.current === `rn-${config.projectId}-anon`) {
1934
- reporterTokenRef.current = `mushi_${(0, import_core8.newUuid)()}`;
2305
+ reporterTokenRef.current = `mushi_${(0, import_core9.newUuid)()}`;
1935
2306
  }
1936
2307
  resolveReporterTokenReady();
1937
2308
  }
@@ -2051,7 +2422,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2051
2422
  const merged = Object.keys(metadataRef.current).length > 0 ? { ...contextMeta, ...metadataRef.current } : Object.keys(contextMeta).length > 0 ? contextMeta : void 0;
2052
2423
  const breadcrumbs = breadcrumbsRef.current.getAll().map((c) => ({
2053
2424
  ...c,
2054
- message: (0, import_core8.scrubPii)(c.message)
2425
+ message: (0, import_core9.scrubPii)(c.message)
2055
2426
  }));
2056
2427
  const timeline = breadcrumbs.map((c) => ({
2057
2428
  ts: c.timestamp,
@@ -2060,10 +2431,10 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2060
2431
  }));
2061
2432
  const fingerprintHash = getDeviceFingerprintHash(deviceInfo);
2062
2433
  const report = {
2063
- id: (0, import_core8.newUuid)(),
2434
+ id: (0, import_core9.newUuid)(),
2064
2435
  projectId: config.projectId,
2065
2436
  category: data.category,
2066
- description: (0, import_core8.scrubPii)(data.description),
2437
+ description: (0, import_core9.scrubPii)(data.description),
2067
2438
  environment: {
2068
2439
  userAgent: deviceInfo.systemName ?? "ReactNative",
2069
2440
  platform: deviceInfo.platform ?? "mobile",
@@ -2076,7 +2447,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2076
2447
  },
2077
2448
  consoleLogs: (consoleRef.current?.getEntries() ?? []).map((entry) => ({
2078
2449
  ...entry,
2079
- message: (0, import_core8.scrubPii)(entry.message)
2450
+ message: (0, import_core9.scrubPii)(entry.message)
2080
2451
  })),
2081
2452
  networkLogs: networkRef.current?.getEntries().map((entry) => ({
2082
2453
  ...entry,
@@ -2106,7 +2477,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2106
2477
  const code = result.error?.code ?? "";
2107
2478
  const isCredentialError = code === "HTTP_401" || code === "HTTP_403" || code.includes("UNAUTHORIZED") || code.includes("FORBIDDEN") || code.includes("INVALID_API_KEY");
2108
2479
  if (isCredentialError) {
2109
- const where = apiEndpoint.replace(/\/$/, "") === import_core8.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.";
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.";
2110
2481
  console.error(
2111
2482
  '[Mushi] Credentials rejected. Check your projectId and apiKey (must have "report:write" scope). ' + where
2112
2483
  );
@@ -2167,7 +2538,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2167
2538
  getRoute,
2168
2539
  sdkVersion: MUSHI_SDK_VERSION,
2169
2540
  config: config.analytics,
2170
- scrub: import_core8.scrubPii
2541
+ scrub: import_core9.scrubPii
2171
2542
  });
2172
2543
  eventTrackerRef.current = tracker;
2173
2544
  const sessions = config.trackSessions === false ? null : createRNSessionTracker({
@@ -2240,7 +2611,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2240
2611
  identifyWithToken(token) {
2241
2612
  userTokenRef.current = token && typeof token === "string" ? token : null;
2242
2613
  if (userTokenRef.current) {
2243
- const claims = (0, import_core8.parseIdentityToken)(userTokenRef.current);
2614
+ const claims = (0, import_core9.parseIdentityToken)(userTokenRef.current);
2244
2615
  if (claims?.sub) {
2245
2616
  userRef.current = {
2246
2617
  id: claims.sub,
@@ -2295,6 +2666,23 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2295
2666
  const res = await client.listReporterComments(reportId, reporterTokenRef.current);
2296
2667
  return res.ok ? res.data.comments ?? [] : [];
2297
2668
  },
2669
+ async loadMyThread(reportId) {
2670
+ const client = apiClientRef.current;
2671
+ if (!client) return null;
2672
+ try {
2673
+ await reporterTokenReadyRef.current;
2674
+ const res = await client.listReporterComments(reportId, reporterTokenRef.current);
2675
+ return res.ok ? res.data?.comments ?? [] : null;
2676
+ } catch {
2677
+ return null;
2678
+ }
2679
+ },
2680
+ async markReportRead(reportId) {
2681
+ const client = apiClientRef.current;
2682
+ if (!client) return 0;
2683
+ await reporterTokenReadyRef.current;
2684
+ return markReporterReportRead(client, reporterTokenRef.current, reportId);
2685
+ },
2298
2686
  async replyToReport(reportId, body) {
2299
2687
  const client = apiClientRef.current;
2300
2688
  if (!client) return null;
@@ -2402,7 +2790,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2402
2790
  assistantLabel: config.assistant?.label,
2403
2791
  assistantGreeting: config.assistant?.greeting,
2404
2792
  assistantSuggestions: config.assistant?.suggestions,
2405
- inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0
2793
+ inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0,
2794
+ theme: config.widget?.theme
2406
2795
  }
2407
2796
  )
2408
2797
  ] });