@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.js CHANGED
@@ -476,41 +476,105 @@ import {
476
476
  useColorScheme
477
477
  } from "react-native";
478
478
  import {
479
- mushiPalette,
480
- MUSHI_BANNER_NEON,
481
479
  MUSHI_COPY,
482
- MUSHI_CONTROL_DISABLED,
483
480
  MUSHI_INVERSE,
484
- MUSHI_RADIUS,
481
+ MUSHI_RADIUS as MUSHI_RADIUS2,
485
482
  MUSHI_SHADOW_INK,
486
483
  MUSHI_SPACING,
487
484
  MUSHI_TYPE
488
485
  } from "@mushi-mushi/core";
486
+ import { reporterCopy, reporterStatus, resolveReporterLocale } from "@mushi-mushi/core/reporter-ui";
489
487
  import { getLocale } from "@mushi-mushi/web/i18n";
490
488
 
491
- // src/reporter-status.ts
492
- function reporterStatusShort(status) {
493
- switch (status) {
494
- case "new":
495
- case "queued":
496
- case "pending":
497
- case "submitted":
498
- return "Received";
499
- case "classified":
500
- case "triaged":
501
- case "grouped":
502
- case "dispatched":
503
- return "In review";
504
- case "fixing":
505
- return "Fixing";
506
- case "fixed":
507
- case "resolved":
508
- case "completed":
509
- return "Fixed";
510
- case "dismissed":
511
- return "Closed";
512
- default:
513
- return status.replace(/_/g, " ").slice(0, 16);
489
+ // src/theme.ts
490
+ import { mushiPalette, MUSHI_CONTROL_DISABLED, MUSHI_RADIUS } from "@mushi-mushi/core";
491
+ function hexToRgb(hex) {
492
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})([0-9a-f]{2})?$/i.exec(hex.trim());
493
+ if (!m) return null;
494
+ const h = m[1].length === 3 ? m[1].split("").map((c) => c + c).join("") : m[1];
495
+ return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
496
+ }
497
+ function luminance([r, g, b]) {
498
+ const lin = (c) => {
499
+ const s4 = c / 255;
500
+ return s4 <= 0.03928 ? s4 / 12.92 : ((s4 + 0.055) / 1.055) ** 2.4;
501
+ };
502
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
503
+ }
504
+ function contrastingText(bg, fallback) {
505
+ const rgb = hexToRgb(bg);
506
+ if (!rgb) return fallback;
507
+ const l = luminance(rgb);
508
+ return 1.05 / (l + 0.05) >= (l + 0.05) / 0.05 ? "#FFFFFF" : "#000000";
509
+ }
510
+ function resolveRNTheme(dark, override = {}) {
511
+ const pal = mushiPalette(dark ? "dark" : "light");
512
+ const base = {
513
+ bg: dark ? pal.paper : pal.paperRaised,
514
+ fg: pal.ink,
515
+ muted: pal.inkMuted,
516
+ surface: dark ? pal.paperRaised : pal.paper,
517
+ border: pal.ruleStrong,
518
+ accent: pal.ink,
519
+ accentFg: dark ? pal.paper : pal.paperRaised,
520
+ success: pal.ok,
521
+ error: pal.danger,
522
+ fontFamily: void 0,
523
+ radius: MUSHI_RADIUS.card
524
+ };
525
+ const defined = Object.fromEntries(
526
+ Object.entries(override).filter(([, v]) => v !== void 0 && v !== null && v !== "")
527
+ );
528
+ const merged = { ...base, ...defined };
529
+ if (defined.accent && !defined.accentFg) merged.accentFg = contrastingText(defined.accent, base.accentFg);
530
+ return {
531
+ ...merged,
532
+ backdrop: dark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.35)",
533
+ disabled: dark ? MUSHI_CONTROL_DISABLED.dark : MUSHI_CONTROL_DISABLED.light,
534
+ disabledFg: pal.inkFaint
535
+ };
536
+ }
537
+
538
+ // src/reporter-thread.ts
539
+ var THREAD_LOAD_TIMEOUT_MS = 12e3;
540
+ function settleWithin(p, ms) {
541
+ return new Promise((resolve) => {
542
+ const timer = setTimeout(() => resolve(null), ms);
543
+ p.then(
544
+ (value) => {
545
+ clearTimeout(timer);
546
+ resolve(value);
547
+ },
548
+ () => {
549
+ clearTimeout(timer);
550
+ resolve(null);
551
+ }
552
+ );
553
+ });
554
+ }
555
+ function loadReporterThread(load, reportId, timeoutMs = THREAD_LOAD_TIMEOUT_MS) {
556
+ let started;
557
+ try {
558
+ started = load(reportId);
559
+ } catch {
560
+ return Promise.resolve(null);
561
+ }
562
+ return settleWithin(started, timeoutMs);
563
+ }
564
+ async function markReporterReportRead(client, reporterToken, reportId) {
565
+ try {
566
+ const res = await client.listNotifications(reporterToken, { limit: 100 });
567
+ if (!res.ok) return 0;
568
+ const rows = res.data?.notifications ?? [];
569
+ const ids = rows.filter((n) => !n.read_at && n.payload?.reportId === reportId).map((n) => String(n.id));
570
+ let marked = 0;
571
+ for (const id of ids) {
572
+ const r = await client.markNotificationRead(id, reporterToken);
573
+ if (r.ok) marked++;
574
+ }
575
+ return marked;
576
+ } catch {
577
+ return 0;
514
578
  }
515
579
  }
516
580
 
@@ -519,6 +583,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
519
583
  var { height: SCREEN_HEIGHT } = Dimensions2.get("window");
520
584
  var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
521
585
  var DISMISS_THRESHOLD = 80;
586
+ var MIN_DESCRIPTION = 20;
522
587
  var CATEGORY_KEYS = ["bug", "slow", "visual", "confusing", "other"];
523
588
  var CATEGORY_EMOJI = {
524
589
  bug: "\u{1F41B}",
@@ -527,7 +592,6 @@ var CATEGORY_EMOJI = {
527
592
  confusing: "\u{1F615}",
528
593
  other: "\u{1F4AC}"
529
594
  };
530
- var VERIFIABLE_STATUSES = /* @__PURE__ */ new Set(["fixed", "resolved", "verified"]);
531
595
  var MushiBottomSheet = ({
532
596
  visible,
533
597
  onClose,
@@ -539,10 +603,15 @@ var MushiBottomSheet = ({
539
603
  assistantLabel = MUSHI_COPY.assistantTab,
540
604
  assistantGreeting,
541
605
  assistantSuggestions = [],
542
- inboxPollIntervalMs = 0
606
+ inboxPollIntervalMs = 0,
607
+ theme
543
608
  }) => {
544
609
  const mushi = useMushiContext();
545
610
  const t = getLocale();
611
+ const locale = resolveReporterLocale(
612
+ typeof navigator !== "undefined" ? navigator.language : void 0
613
+ );
614
+ const rc = reporterCopy(locale);
546
615
  const greeting = assistantGreeting ?? t.assistant.defaultGreeting;
547
616
  const scheme = useColorScheme();
548
617
  const dark = scheme === "dark";
@@ -554,26 +623,37 @@ var MushiBottomSheet = ({
554
623
  const [submitError, setSubmitError] = useState(null);
555
624
  const [sheetTab, setSheetTab] = useState("report");
556
625
  const [inboxReports, setInboxReports] = useState([]);
626
+ const [inboxLoaded, setInboxLoaded] = useState(false);
557
627
  const [inboxLoading, setInboxLoading] = useState(false);
558
628
  const [selectedReportId, setSelectedReportId] = useState(null);
629
+ const selectedRef = useRef(null);
559
630
  const [threadComments, setThreadComments] = useState([]);
631
+ const [threadStatus, setThreadStatus] = useState("idle");
560
632
  const [replyText, setReplyText] = useState("");
633
+ const [replyState, setReplyState] = useState("idle");
634
+ const [feedbackSending, setFeedbackSending] = useState(false);
635
+ const [feedbackError, setFeedbackError] = useState(false);
561
636
  const [assistantInput, setAssistantInput] = useState("");
562
637
  const [assistantThreadId, setAssistantThreadId] = useState(null);
563
638
  const [assistantSending, setAssistantSending] = useState(false);
564
639
  const [assistantError, setAssistantError] = useState(null);
565
640
  const [assistantTurns, setAssistantTurns] = useState([]);
566
641
  const [screenshotAttached, setScreenshotAttached] = useState(true);
567
- const loadInbox = useCallback(async () => {
568
- if (!mushi?.listMyReports) return;
569
- setInboxLoading(true);
570
- try {
571
- const rows = await mushi.listMyReports();
572
- setInboxReports(rows);
573
- } finally {
574
- setInboxLoading(false);
575
- }
576
- }, [mushi]);
642
+ const loadInbox = useCallback(
643
+ async (opts = {}) => {
644
+ if (!mushi?.listMyReports) return;
645
+ if (!opts.silent) setInboxLoading(true);
646
+ try {
647
+ const rows = await mushi.listMyReports();
648
+ setInboxReports(rows.filter((r) => !reporterStatus(r, locale).hidden));
649
+ setInboxLoaded(true);
650
+ } catch {
651
+ } finally {
652
+ if (!opts.silent) setInboxLoading(false);
653
+ }
654
+ },
655
+ [mushi, locale]
656
+ );
577
657
  useEffect(() => {
578
658
  if (visible) {
579
659
  setScreenshotAttached(true);
@@ -584,28 +664,87 @@ var MushiBottomSheet = ({
584
664
  useEffect(() => {
585
665
  if (!visible || sheetTab !== "inbox" || inboxPollIntervalMs <= 0) return;
586
666
  const timer = setInterval(() => {
587
- void loadInbox();
667
+ void loadInbox({ silent: true });
588
668
  }, inboxPollIntervalMs);
589
669
  return () => clearInterval(timer);
590
670
  }, [visible, sheetTab, inboxPollIntervalMs, loadInbox]);
591
- const openThread = useCallback(async (reportId) => {
592
- setSelectedReportId(reportId);
593
- if (!mushi?.listMyComments) return;
594
- const comments = await mushi.listMyComments(reportId);
595
- setThreadComments(comments);
596
- }, [mushi]);
671
+ const openThread = useCallback(
672
+ async (reportId, opts = {}) => {
673
+ selectedRef.current = reportId;
674
+ setSelectedReportId(reportId);
675
+ if (!opts.silent) {
676
+ setThreadComments([]);
677
+ setThreadStatus("loading");
678
+ setReplyState("idle");
679
+ setFeedbackError(false);
680
+ }
681
+ const load = mushi?.loadMyThread ?? mushi?.listMyComments;
682
+ const comments = load ? await loadReporterThread(load, reportId) : null;
683
+ if (selectedRef.current !== reportId) return;
684
+ if (comments === null) {
685
+ setThreadStatus((prev) => opts.silent && prev === "ready" ? "ready" : "error");
686
+ return;
687
+ }
688
+ setThreadComments(comments);
689
+ setThreadStatus("ready");
690
+ if (!opts.silent && mushi?.markReportRead) {
691
+ mushi.markReportRead(reportId).then((marked) => {
692
+ if (marked > 0) {
693
+ setInboxReports((rows) => rows.map((r) => r.id === reportId ? { ...r, unread_count: 0 } : r));
694
+ }
695
+ }).catch(() => void 0);
696
+ }
697
+ },
698
+ [mushi]
699
+ );
700
+ const closeThread = useCallback(() => {
701
+ selectedRef.current = null;
702
+ setSelectedReportId(null);
703
+ setThreadStatus("idle");
704
+ }, []);
597
705
  const sendReply = useCallback(async () => {
598
- if (!mushi?.replyToReport || !selectedReportId || !replyText.trim()) return;
599
- await mushi.replyToReport(selectedReportId, replyText.trim());
600
- setReplyText("");
601
- await openThread(selectedReportId);
602
- }, [mushi, selectedReportId, replyText, openThread]);
603
- const submitFeedback = useCallback(async (signal) => {
604
- if (!mushi?.submitFeedbackSignal || !selectedReportId) return;
605
- await mushi.submitFeedbackSignal(selectedReportId, signal);
606
- await loadInbox();
607
- await openThread(selectedReportId);
608
- }, [mushi, selectedReportId, loadInbox, openThread]);
706
+ const body = replyText.trim();
707
+ const reportId = selectedReportId;
708
+ if (!mushi?.replyToReport || !reportId || !body || replyState === "sending") return;
709
+ setReplyState("sending");
710
+ try {
711
+ const comment = await settleWithin(mushi.replyToReport(reportId, body), THREAD_LOAD_TIMEOUT_MS);
712
+ if (!comment) {
713
+ setReplyState("failed");
714
+ return;
715
+ }
716
+ setReplyText("");
717
+ setReplyState("idle");
718
+ if (selectedRef.current === reportId) {
719
+ setThreadComments((prev) => [...prev, comment]);
720
+ void openThread(reportId, { silent: true });
721
+ }
722
+ } catch {
723
+ setReplyState("failed");
724
+ }
725
+ }, [mushi, selectedReportId, replyText, replyState, openThread]);
726
+ const submitFeedback = useCallback(
727
+ async (signal) => {
728
+ const reportId = selectedReportId;
729
+ if (!mushi?.submitFeedbackSignal || !reportId || feedbackSending) return;
730
+ setFeedbackSending(true);
731
+ setFeedbackError(false);
732
+ try {
733
+ const outcome = await mushi.submitFeedbackSignal(reportId, signal);
734
+ if (!outcome) {
735
+ setFeedbackError(true);
736
+ return;
737
+ }
738
+ await loadInbox({ silent: true });
739
+ await openThread(reportId, { silent: true });
740
+ } catch {
741
+ setFeedbackError(true);
742
+ } finally {
743
+ setFeedbackSending(false);
744
+ }
745
+ },
746
+ [mushi, selectedReportId, feedbackSending, loadInbox, openThread]
747
+ );
609
748
  const sendAssistant = useCallback(async (message) => {
610
749
  if (!mushi?.askAssistant || !message.trim() || assistantSending) return;
611
750
  const trimmed = message.trim();
@@ -701,7 +840,7 @@ var MushiBottomSheet = ({
701
840
  const handleSubmit = async () => {
702
841
  if (!category || !description.trim() || !mushi) return;
703
842
  const trimmed = description.trim();
704
- if (trimmed.length < 20) {
843
+ if (trimmed.length < MIN_DESCRIPTION) {
705
844
  setSubmitError("Please write at least 20 characters so we can understand the issue.");
706
845
  return;
707
846
  }
@@ -736,11 +875,181 @@ var MushiBottomSheet = ({
736
875
  }
737
876
  };
738
877
  const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
739
- const pal = mushiPalette(dark ? "dark" : "light");
740
- const colors = dark ? { bg: pal.paper, text: pal.ink, sub: pal.inkMuted, card: pal.paperRaised, accent: MUSHI_BANNER_NEON.bg, accentInk: MUSHI_BANNER_NEON.fg, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.6)", disabled: MUSHI_CONTROL_DISABLED.dark, disabledText: pal.inkFaint } : { bg: pal.paperRaised, text: pal.ink, sub: pal.inkMuted, card: pal.paper, accent: MUSHI_BANNER_NEON.bg, accentInk: MUSHI_BANNER_NEON.fg, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.35)", disabled: MUSHI_CONTROL_DISABLED.light, disabledText: pal.inkFaint };
878
+ const colors = resolveRNTheme(dark, theme);
879
+ const font = colors.fontFamily ? { fontFamily: colors.fontFamily } : null;
880
+ const radius = colors.radius;
741
881
  const sheetTabs = ["report", "inbox", ...assistantEnabled ? ["assistant"] : []];
742
- const canSubmit = !!category && description.trim().length >= 20 && (phase === "form" || phase === "error");
743
- return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, children: /* @__PURE__ */ jsxs(
882
+ const canSubmit = !!category && description.trim().length >= MIN_DESCRIPTION && (phase === "form" || phase === "error");
883
+ const submitHint = canSubmit || phase === "sending" ? void 0 : !category ? t.step1.heading : rc.ui.addWords;
884
+ const selectedReport = selectedReportId ? inboxReports.find((r) => r.id === selectedReportId) ?? null : null;
885
+ const selectedView = selectedReport ? reporterStatus(selectedReport, locale) : null;
886
+ const headerTitle = sheetTab === "assistant" ? assistantLabel : sheetTab === "inbox" ? t.flows.reports.title : t.widget.title;
887
+ const renderThread = () => /* @__PURE__ */ jsxs(Fragment, { children: [
888
+ /* @__PURE__ */ jsx(
889
+ TouchableOpacity,
890
+ {
891
+ onPress: closeThread,
892
+ accessibilityRole: "button",
893
+ accessibilityLabel: t.widget.back,
894
+ hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
895
+ children: /* @__PURE__ */ jsxs(Text, { style: [{ color: colors.accent, marginBottom: 8 }, font], children: [
896
+ "\u2190 ",
897
+ t.widget.back
898
+ ] })
899
+ }
900
+ ),
901
+ selectedReport && selectedView ? /* @__PURE__ */ jsxs(View, { style: [s.headerCard, { backgroundColor: colors.surface, borderRadius: radius, borderColor: colors.border }], children: [
902
+ /* @__PURE__ */ jsx(Text, { style: [s.pill, { color: colors.fg, borderColor: colors.border }, font], children: selectedView.label }),
903
+ /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 12, marginTop: 4 }, font], children: selectedView.detail }),
904
+ /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, marginTop: 8 }, font], numberOfLines: 4, children: selectedReport.title ?? selectedReport.summary ?? selectedReport.description ?? "" })
905
+ ] }) : null,
906
+ threadStatus === "loading" ? /* @__PURE__ */ jsx(View, { accessibilityLabel: t.flows.reports.loading, accessibilityLiveRegion: "polite", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(View, { style: [s.skeleton, { backgroundColor: colors.surface, width: `${90 - i * 18}%` }] }, i)) }) : threadStatus === "error" ? /* @__PURE__ */ jsxs(View, { style: s.errorRow, accessibilityLiveRegion: "polite", children: [
907
+ /* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, flex: 1 }, font], accessibilityRole: "alert", children: rc.ui.loadError }),
908
+ /* @__PURE__ */ jsx(
909
+ TouchableOpacity,
910
+ {
911
+ onPress: () => selectedReportId && void openThread(selectedReportId),
912
+ accessibilityRole: "button",
913
+ style: [s.retryBtn, { borderColor: colors.border, borderRadius: radius }],
914
+ children: /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontWeight: "600" }, font], children: rc.ui.retry })
915
+ }
916
+ )
917
+ ] }) : threadComments.length === 0 ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, marginBottom: 8 }, font], children: rc.ui.noReplies }) : threadComments.map((c) => /* @__PURE__ */ jsxs(
918
+ View,
919
+ {
920
+ style: [
921
+ s.threadBubble,
922
+ {
923
+ backgroundColor: colors.surface,
924
+ alignSelf: c.author_kind === "reporter" ? "flex-end" : "flex-start"
925
+ }
926
+ ],
927
+ children: [
928
+ /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: c.author_kind === "reporter" ? rc.ui.you : rc.ui.developer }),
929
+ /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg }, font], children: c.body })
930
+ ]
931
+ },
932
+ c.id
933
+ )),
934
+ replyState === "sending" ? /* @__PURE__ */ jsxs(View, { style: [s.threadBubble, { backgroundColor: colors.surface, alignSelf: "flex-end", opacity: 0.7 }], children: [
935
+ /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: rc.ui.sending }),
936
+ /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg }, font], children: replyText.trim() })
937
+ ] }) : null,
938
+ selectedView?.canVerify ? /* @__PURE__ */ jsxs(View, { style: s.verifyRow, children: [
939
+ /* @__PURE__ */ jsx(
940
+ TouchableOpacity,
941
+ {
942
+ style: [s.verifyBtn, { backgroundColor: colors.accent, borderRadius: radius }],
943
+ onPress: () => void submitFeedback("confirms"),
944
+ disabled: feedbackSending,
945
+ accessibilityRole: "button",
946
+ accessibilityState: { disabled: feedbackSending, busy: feedbackSending },
947
+ children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentFg }, font], children: rc.ui.yes })
948
+ }
949
+ ),
950
+ /* @__PURE__ */ jsx(
951
+ TouchableOpacity,
952
+ {
953
+ style: [s.verifyBtn, { backgroundColor: colors.border, borderRadius: radius }],
954
+ onPress: () => void submitFeedback("not_fixed"),
955
+ disabled: feedbackSending,
956
+ accessibilityRole: "button",
957
+ accessibilityState: { disabled: feedbackSending, busy: feedbackSending },
958
+ children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.fg }, font], children: rc.ui.notYet })
959
+ }
960
+ )
961
+ ] }) : null,
962
+ feedbackError ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, marginBottom: 8 }, font], accessibilityRole: "alert", children: rc.ui.sendFailed }) : null,
963
+ /* @__PURE__ */ jsxs(View, { style: s.composer, children: [
964
+ /* @__PURE__ */ jsx(
965
+ TextInput,
966
+ {
967
+ style: [
968
+ s.input,
969
+ s.composerInput,
970
+ { backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius },
971
+ font
972
+ ],
973
+ placeholder: t.flows.thread.replyPlaceholder,
974
+ placeholderTextColor: colors.muted,
975
+ value: replyText,
976
+ onChangeText: (text) => {
977
+ setReplyText(text.slice(0, 2e3));
978
+ if (replyState === "failed") setReplyState("idle");
979
+ },
980
+ multiline: true,
981
+ maxLength: 2e3,
982
+ editable: replyState !== "sending",
983
+ accessibilityLabel: t.flows.thread.replyPlaceholder
984
+ }
985
+ ),
986
+ /* @__PURE__ */ jsx(
987
+ TouchableOpacity,
988
+ {
989
+ style: [
990
+ s.sendBtn,
991
+ {
992
+ backgroundColor: replyText.trim() && replyState !== "sending" ? colors.accent : colors.disabled,
993
+ borderRadius: radius
994
+ }
995
+ ],
996
+ onPress: () => void sendReply(),
997
+ disabled: !replyText.trim() || replyState === "sending",
998
+ accessibilityRole: "button",
999
+ accessibilityLabel: replyState === "failed" ? rc.ui.retry : t.flows.thread.send,
1000
+ accessibilityState: { disabled: !replyText.trim() || replyState === "sending", busy: replyState === "sending" },
1001
+ children: /* @__PURE__ */ jsx(
1002
+ Text,
1003
+ {
1004
+ style: [
1005
+ s.submitText,
1006
+ { color: replyText.trim() && replyState !== "sending" ? colors.accentFg : colors.disabledFg },
1007
+ font
1008
+ ],
1009
+ children: replyState === "failed" ? rc.ui.retry : t.flows.thread.send
1010
+ }
1011
+ )
1012
+ }
1013
+ )
1014
+ ] }),
1015
+ replyState === "failed" ? /* @__PURE__ */ jsxs(Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: [
1016
+ rc.ui.sendFailed,
1017
+ " \xB7 ",
1018
+ rc.ui.retry
1019
+ ] }) : null
1020
+ ] });
1021
+ const renderInboxList = () => {
1022
+ if (inboxLoading && !inboxLoaded) {
1023
+ return /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted }, font], children: t.flows.reports.loading });
1024
+ }
1025
+ if (inboxReports.length === 0) {
1026
+ return /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted }, font], children: rc.ui.empty });
1027
+ }
1028
+ return inboxReports.map((r) => {
1029
+ const view = reporterStatus(r, locale);
1030
+ const unread = (r.unread_count ?? 0) > 0;
1031
+ const title = (r.title ?? r.summary ?? r.description ?? "Report").slice(0, 80);
1032
+ return /* @__PURE__ */ jsxs(
1033
+ TouchableOpacity,
1034
+ {
1035
+ style: [s.inboxRow, { borderColor: colors.border }],
1036
+ onPress: () => void openThread(r.id),
1037
+ accessibilityRole: "button",
1038
+ accessibilityLabel: `${view.label}. ${title}${unread ? `. ${r.unread_count} new` : ""}`,
1039
+ children: [
1040
+ /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontWeight: unread ? "700" : "600" }, font], numberOfLines: 1, children: title }),
1041
+ /* @__PURE__ */ jsxs(Text, { style: [{ color: unread ? colors.fg : colors.muted, fontSize: 12, marginTop: 2 }, font], numberOfLines: 1, children: [
1042
+ view.label,
1043
+ view.othersNote ? ` \xB7 ${view.othersNote}` : ""
1044
+ ] }),
1045
+ unread && r.last_event_preview ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontSize: 12, fontWeight: "700", marginTop: 2 }, font], numberOfLines: 1, children: r.last_event_preview }) : null
1046
+ ]
1047
+ },
1048
+ r.id
1049
+ );
1050
+ });
1051
+ };
1052
+ return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, onRequestClose: handleClose, children: /* @__PURE__ */ jsxs(
744
1053
  KeyboardAvoidingView,
745
1054
  {
746
1055
  style: s.flex,
@@ -751,44 +1060,69 @@ var MushiBottomSheet = ({
751
1060
  Animated.View,
752
1061
  {
753
1062
  style: [s.backdrop, { backgroundColor: colors.backdrop, opacity: backdropOpacity }],
754
- children: /* @__PURE__ */ jsx(TouchableOpacity, { style: s.flex, activeOpacity: 1, onPress: handleClose })
1063
+ children: /* @__PURE__ */ jsx(
1064
+ TouchableOpacity,
1065
+ {
1066
+ style: s.flex,
1067
+ activeOpacity: 1,
1068
+ onPress: handleClose,
1069
+ accessibilityRole: "button",
1070
+ accessibilityLabel: t.widget.close
1071
+ }
1072
+ )
755
1073
  }
756
1074
  ),
757
1075
  /* @__PURE__ */ jsxs(
758
1076
  Animated.View,
759
1077
  {
1078
+ accessibilityViewIsModal: true,
1079
+ onAccessibilityEscape: handleClose,
760
1080
  style: [
761
1081
  s.sheet,
762
1082
  { backgroundColor: colors.bg, transform: [{ translateY }] }
763
1083
  ],
764
1084
  children: [
765
- /* @__PURE__ */ jsx(View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ jsx(View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
766
- /* @__PURE__ */ jsxs(View, { style: [s.brandHeader, { backgroundColor: MUSHI_BANNER_NEON.bg, borderBottomColor: MUSHI_BANNER_NEON.border }], children: [
767
- /* @__PURE__ */ jsx(Text, { style: [s.brandEyebrow, { color: MUSHI_BANNER_NEON.fg }], children: "MUSHI \xB7 BETA" }),
768
- /* @__PURE__ */ jsx(Text, { style: [s.brandTitle, { color: MUSHI_BANNER_NEON.fg }], children: sheetTab === "assistant" ? assistantLabel : sheetTab === "inbox" ? t.flows.reports.title : t.widget.title })
769
- ] }),
770
- /* @__PURE__ */ jsx(View, { style: [s.tabRow, { borderBottomColor: colors.border }], children: sheetTabs.map((tab) => /* @__PURE__ */ jsx(
1085
+ /* @__PURE__ */ jsx(
1086
+ View,
1087
+ {
1088
+ ...panResponder.panHandlers,
1089
+ style: s.handleArea,
1090
+ accessible: true,
1091
+ accessibilityRole: "adjustable",
1092
+ accessibilityLabel: t.widget.close,
1093
+ accessibilityActions: [{ name: "decrement", label: t.widget.close }, { name: "escape" }],
1094
+ onAccessibilityAction: (e) => {
1095
+ if (e.nativeEvent.actionName === "decrement" || e.nativeEvent.actionName === "escape") handleClose();
1096
+ },
1097
+ children: /* @__PURE__ */ jsx(View, { style: [s.handle, { backgroundColor: colors.muted }] })
1098
+ }
1099
+ ),
1100
+ /* @__PURE__ */ jsx(View, { style: [s.header, { borderBottomColor: colors.border }], children: /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: [s.headerTitle, { color: colors.fg }, font], children: headerTitle }) }),
1101
+ /* @__PURE__ */ jsx(View, { style: [s.tabRow, { borderBottomColor: colors.border }], accessibilityRole: "tablist", children: sheetTabs.map((tab) => /* @__PURE__ */ jsx(
771
1102
  TouchableOpacity,
772
1103
  {
773
1104
  onPress: () => {
774
1105
  setSheetTab(tab);
775
- if (tab === "inbox") void loadInbox();
776
- else setSelectedReportId(null);
1106
+ if (tab === "inbox") void loadInbox({ silent: inboxLoaded });
1107
+ else closeThread();
777
1108
  },
778
1109
  style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
779
- children: /* @__PURE__ */ jsx(Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.text : colors.sub }], children: tab === "report" ? t.widget.trigger : tab === "inbox" ? t.flows.reports.title : assistantLabel })
1110
+ accessibilityRole: "tab",
1111
+ accessibilityState: { selected: sheetTab === tab },
1112
+ children: /* @__PURE__ */ jsx(Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.fg : colors.muted }, font], children: tab === "report" ? t.widget.trigger : tab === "inbox" ? t.flows.reports.title : assistantLabel })
780
1113
  },
781
1114
  tab
782
1115
  )) }),
783
1116
  sheetTab === "assistant" ? /* @__PURE__ */ jsxs(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
784
1117
  assistantTurns.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
785
- /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, marginBottom: 12, lineHeight: 20 }, children: greeting }),
1118
+ /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, marginBottom: 12, lineHeight: 20 }, font], children: greeting }),
786
1119
  assistantSuggestions.map((chip) => /* @__PURE__ */ jsx(
787
1120
  TouchableOpacity,
788
1121
  {
789
- style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.card }],
1122
+ style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.surface }],
790
1123
  onPress: () => void sendAssistant(chip),
791
- children: /* @__PURE__ */ jsx(Text, { style: { color: colors.text, fontSize: 13 }, children: chip })
1124
+ accessibilityRole: "button",
1125
+ children: /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontSize: 13 }, font], children: chip })
792
1126
  },
793
1127
  chip
794
1128
  ))
@@ -799,25 +1133,34 @@ var MushiBottomSheet = ({
799
1133
  s.assistantBubble,
800
1134
  {
801
1135
  alignSelf: turn.role === "user" ? "flex-end" : "flex-start",
802
- backgroundColor: turn.role === "user" ? colors.accent : colors.card
1136
+ backgroundColor: turn.role === "user" ? colors.accent : colors.surface
803
1137
  }
804
1138
  ],
805
1139
  children: [
806
- /* @__PURE__ */ jsx(Text, { style: { color: turn.role === "user" ? colors.accentInk : colors.text }, children: turn.text }),
807
- turn.options?.map((opt) => /* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => void sendAssistant(opt), style: { marginTop: 8 }, children: /* @__PURE__ */ jsx(Text, { style: { color: colors.accent, fontSize: 12 }, children: opt }) }, opt))
1140
+ /* @__PURE__ */ jsx(Text, { style: [{ color: turn.role === "user" ? colors.accentFg : colors.fg }, font], children: turn.text }),
1141
+ turn.options?.map((opt) => /* @__PURE__ */ jsx(
1142
+ TouchableOpacity,
1143
+ {
1144
+ onPress: () => void sendAssistant(opt),
1145
+ style: { marginTop: 8 },
1146
+ accessibilityRole: "button",
1147
+ children: /* @__PURE__ */ jsx(Text, { style: [{ color: colors.accent, fontSize: 12 }, font], children: opt })
1148
+ },
1149
+ opt
1150
+ ))
808
1151
  ]
809
1152
  },
810
1153
  `${turn.role}-${idx}`
811
1154
  )),
812
- assistantSending ? /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, marginTop: 8 }, accessibilityLiveRegion: "polite", children: t.assistant.thinking }) : null,
813
- assistantError ? /* @__PURE__ */ jsx(Text, { style: { color: pal.danger, marginTop: 8 }, accessibilityRole: "alert", children: assistantError }) : null,
1155
+ assistantSending ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, marginTop: 8 }, font], accessibilityLiveRegion: "polite", children: t.assistant.thinking }) : null,
1156
+ assistantError ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, marginTop: 8 }, font], accessibilityRole: "alert", children: assistantError }) : null,
814
1157
  /* @__PURE__ */ jsxs(View, { style: s.assistantComposer, children: [
815
1158
  /* @__PURE__ */ jsx(
816
1159
  TextInput,
817
1160
  {
818
- style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.card, color: colors.text, borderColor: colors.border }],
1161
+ style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius }, font],
819
1162
  placeholder: t.assistant.inputPlaceholder,
820
- placeholderTextColor: colors.sub,
1163
+ placeholderTextColor: colors.muted,
821
1164
  value: assistantInput,
822
1165
  onChangeText: setAssistantInput,
823
1166
  editable: !assistantSending
@@ -826,45 +1169,19 @@ var MushiBottomSheet = ({
826
1169
  /* @__PURE__ */ jsx(
827
1170
  TouchableOpacity,
828
1171
  {
829
- style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16 }],
1172
+ style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16, borderRadius: radius }],
830
1173
  onPress: () => void sendAssistant(assistantInput),
831
1174
  disabled: !assistantInput.trim() || assistantSending,
832
- children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: "\u2191" })
1175
+ accessibilityRole: "button",
1176
+ accessibilityLabel: t.flows.thread.send,
1177
+ accessibilityState: { disabled: !assistantInput.trim() || assistantSending, busy: assistantSending },
1178
+ children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentFg }], children: "\u2191" })
833
1179
  }
834
1180
  )
835
1181
  ] })
836
- ] }) : sheetTab === "inbox" ? /* @__PURE__ */ jsx(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: inboxLoading ? /* @__PURE__ */ jsx(Text, { style: { color: colors.sub }, children: t.flows.reports.loading }) : selectedReportId ? /* @__PURE__ */ jsxs(Fragment, { children: [
837
- /* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ jsxs(Text, { style: { color: colors.accent, marginBottom: 8 }, children: [
838
- "\u2190 ",
839
- t.widget.back
840
- ] }) }),
841
- threadComments.map((c) => /* @__PURE__ */ jsxs(View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
842
- /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
843
- /* @__PURE__ */ jsx(Text, { style: { color: colors.text }, children: c.body })
844
- ] }, c.id)),
845
- VERIFIABLE_STATUSES.has(
846
- inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
847
- ) && /* @__PURE__ */ jsxs(View, { style: s.verifyRow, children: [
848
- /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: t.flows.thread.confirmFixed }) }),
849
- /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.text }], children: t.flows.thread.notFixed }) })
850
- ] }),
851
- /* @__PURE__ */ jsx(
852
- TextInput,
853
- {
854
- style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
855
- placeholder: t.flows.thread.replyPlaceholder,
856
- placeholderTextColor: colors.sub,
857
- value: replyText,
858
- onChangeText: setReplyText
859
- }
860
- ),
861
- /* @__PURE__ */ jsx(TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: t.flows.thread.send }) })
862
- ] }) : inboxReports.map((r) => /* @__PURE__ */ jsxs(TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
863
- /* @__PURE__ */ jsx(Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
864
- /* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: reporterStatusShort(r.status) })
865
- ] }, r.id)) }) : phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, children: [
1182
+ ] }) : sheetTab === "inbox" ? /* @__PURE__ */ jsx(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", contentContainerStyle: { paddingBottom: 24 }, children: selectedReportId ? renderThread() : renderInboxList() }) : phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, accessibilityLiveRegion: "polite", children: [
866
1183
  /* @__PURE__ */ jsx(Text, { style: [s.sentEmoji], children: "\u2705" }),
867
- /* @__PURE__ */ jsx(Text, { style: [s.sentText, { color: colors.text }], children: t.widget.submitted })
1184
+ /* @__PURE__ */ jsx(Text, { style: [s.sentText, { color: colors.fg }, font], children: t.widget.submitted })
868
1185
  ] }) : /* @__PURE__ */ jsxs(
869
1186
  ScrollView,
870
1187
  {
@@ -872,29 +1189,34 @@ var MushiBottomSheet = ({
872
1189
  keyboardShouldPersistTaps: "handled",
873
1190
  contentContainerStyle: { paddingBottom: 24 },
874
1191
  children: [
875
- /* @__PURE__ */ jsx(Text, { style: [s.stepLabel, { color: colors.sub }], children: t.step1.heading }),
876
- /* @__PURE__ */ jsx(View, { style: s.catRow, children: CATEGORY_KEYS.map((key) => {
1192
+ /* @__PURE__ */ jsx(Text, { style: [s.stepLabel, { color: colors.muted }, font], children: t.step1.heading }),
1193
+ /* @__PURE__ */ jsx(View, { style: s.catRow, accessibilityRole: "radiogroup", children: CATEGORY_KEYS.map((key) => {
877
1194
  const active = category === key;
878
1195
  return /* @__PURE__ */ jsxs(
879
1196
  TouchableOpacity,
880
1197
  {
881
1198
  onPress: () => setCategory(key),
882
1199
  activeOpacity: 0.7,
1200
+ accessibilityRole: "radio",
1201
+ accessibilityState: { selected: active, checked: active },
1202
+ accessibilityLabel: t.step1.categories[key],
883
1203
  style: [
884
1204
  s.catBtn,
885
1205
  {
886
- backgroundColor: active ? colors.accent : colors.card,
1206
+ backgroundColor: active ? colors.accent : colors.surface,
887
1207
  borderColor: active ? colors.accent : colors.border
888
1208
  }
889
1209
  ],
890
1210
  children: [
891
- /* @__PURE__ */ jsx(Text, { style: s.catEmoji, children: CATEGORY_EMOJI[key] }),
1211
+ /* @__PURE__ */ jsx(Text, { style: s.catEmoji, importantForAccessibility: "no", children: CATEGORY_EMOJI[key] }),
892
1212
  /* @__PURE__ */ jsx(
893
1213
  Text,
894
1214
  {
1215
+ numberOfLines: 1,
895
1216
  style: [
896
1217
  s.catLabel,
897
- { color: active ? colors.accentInk : colors.text }
1218
+ { color: active ? colors.accentFg : colors.fg },
1219
+ font
898
1220
  ],
899
1221
  children: t.step1.categories[key]
900
1222
  }
@@ -910,13 +1232,16 @@ var MushiBottomSheet = ({
910
1232
  style: [
911
1233
  s.input,
912
1234
  {
913
- backgroundColor: colors.card,
914
- color: colors.text,
915
- borderColor: colors.border
916
- }
1235
+ backgroundColor: colors.surface,
1236
+ color: colors.fg,
1237
+ borderColor: colors.border,
1238
+ borderRadius: radius
1239
+ },
1240
+ font
917
1241
  ],
918
1242
  placeholder: t.step3.descriptionPlaceholder,
919
- placeholderTextColor: colors.sub,
1243
+ placeholderTextColor: colors.muted,
1244
+ accessibilityLabel: t.step3.descriptionPlaceholder,
920
1245
  multiline: true,
921
1246
  textAlignVertical: "top",
922
1247
  value: description,
@@ -929,19 +1254,19 @@ var MushiBottomSheet = ({
929
1254
  maxLength: 4e3
930
1255
  }
931
1256
  ),
932
- submitError ? /* @__PURE__ */ jsx(Text, { style: { color: colors.accent, marginTop: 6, fontSize: 12 }, children: submitError }) : null,
1257
+ submitError ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: submitError }) : null,
933
1258
  activeScreenshot ? /* @__PURE__ */ jsxs(View, { style: s.screenshotRow, children: [
934
1259
  /* @__PURE__ */ jsx(
935
1260
  Image,
936
1261
  {
937
1262
  source: { uri: activeScreenshot },
938
- style: [s.screenshotThumb, { backgroundColor: colors.text }],
1263
+ style: [s.screenshotThumb, { backgroundColor: colors.fg }],
939
1264
  accessibilityLabel: t.step3.screenshotPreviewAlt
940
1265
  }
941
1266
  ),
942
1267
  /* @__PURE__ */ jsxs(View, { style: s.screenshotMeta, children: [
943
- /* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.text }], children: t.step3.screenshotAttached.replace(" \u2713", "") }),
944
- /* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.sub }], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : t.step3.screenshotSensitiveHint.split("\u2014")[0]?.trim() ?? t.step3.screenshotSensitiveHint })
1268
+ /* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.fg }, font], children: t.step3.screenshotAttached.replace(" \u2713", "") }),
1269
+ /* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.muted }, font], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : t.step3.screenshotSensitiveHint.split("\u2014")[0]?.trim() ?? t.step3.screenshotSensitiveHint })
945
1270
  ] }),
946
1271
  /* @__PURE__ */ jsx(
947
1272
  TouchableOpacity,
@@ -951,22 +1276,28 @@ var MushiBottomSheet = ({
951
1276
  onClearScreenshot?.();
952
1277
  },
953
1278
  hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
1279
+ accessibilityRole: "button",
954
1280
  accessibilityLabel: t.widget.close,
955
- children: /* @__PURE__ */ jsx(Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
1281
+ children: /* @__PURE__ */ jsx(Text, { style: [s.screenshotRemove, { color: colors.muted }], children: "\u2715" })
956
1282
  }
957
1283
  )
958
1284
  ] }) : null,
1285
+ submitHint ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 12, marginBottom: 6 }, font], children: submitHint }) : null,
959
1286
  /* @__PURE__ */ jsx(
960
1287
  TouchableOpacity,
961
1288
  {
962
1289
  onPress: handleSubmit,
963
1290
  disabled: !canSubmit,
964
1291
  activeOpacity: 0.8,
1292
+ accessibilityRole: "button",
1293
+ accessibilityLabel: phase === "sending" ? t.widget.submitting : t.widget.submit,
1294
+ accessibilityHint: submitHint,
1295
+ accessibilityState: { disabled: !canSubmit, busy: phase === "sending" },
965
1296
  style: [
966
1297
  s.submitBtn,
967
- { backgroundColor: canSubmit ? colors.accent : colors.disabled }
1298
+ { backgroundColor: canSubmit ? colors.accent : colors.disabled, borderRadius: radius }
968
1299
  ],
969
- children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: canSubmit ? colors.accentInk : colors.disabledText }], children: phase === "sending" ? t.widget.submitting : t.widget.submit })
1300
+ children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: canSubmit ? colors.accentFg : colors.disabledFg }, font], children: phase === "sending" ? t.widget.submitting : t.widget.submit })
970
1301
  }
971
1302
  )
972
1303
  ]
@@ -996,8 +1327,8 @@ var s = StyleSheet.create({
996
1327
  left: 0,
997
1328
  right: 0,
998
1329
  minHeight: SHEET_HEIGHT,
999
- borderTopLeftRadius: MUSHI_RADIUS.sheet,
1000
- borderTopRightRadius: MUSHI_RADIUS.sheet,
1330
+ borderTopLeftRadius: MUSHI_RADIUS2.sheet,
1331
+ borderTopRightRadius: MUSHI_RADIUS2.sheet,
1001
1332
  paddingBottom: Platform2.OS === "ios" ? 34 : MUSHI_SPACING.roomy,
1002
1333
  shadowColor: MUSHI_SHADOW_INK,
1003
1334
  shadowOffset: { width: 0, height: -3 },
@@ -1012,36 +1343,22 @@ var s = StyleSheet.create({
1012
1343
  handle: {
1013
1344
  width: 40,
1014
1345
  height: 5,
1015
- borderRadius: MUSHI_RADIUS.control,
1346
+ borderRadius: MUSHI_RADIUS2.control,
1016
1347
  opacity: 0.5
1017
1348
  },
1018
- brandHeader: {
1349
+ header: {
1019
1350
  paddingHorizontal: MUSHI_SPACING.lounge,
1020
- paddingTop: MUSHI_SPACING.comfy,
1021
- paddingBottom: MUSHI_SPACING.comfy,
1022
- borderBottomWidth: 1.5
1023
- },
1024
- brandEyebrow: {
1025
- fontSize: MUSHI_TYPE.sizeLabel,
1026
- fontWeight: "700",
1027
- letterSpacing: 1.2,
1028
- marginBottom: 2,
1029
- opacity: 0.7
1351
+ paddingBottom: MUSHI_SPACING.comfy
1030
1352
  },
1031
- brandTitle: {
1353
+ headerTitle: {
1032
1354
  fontSize: 18,
1033
- fontWeight: "800",
1355
+ fontWeight: "700",
1034
1356
  letterSpacing: -0.2
1035
1357
  },
1036
1358
  body: {
1037
1359
  paddingHorizontal: MUSHI_SPACING.lounge,
1038
1360
  paddingTop: MUSHI_SPACING.tight
1039
1361
  },
1040
- title: {
1041
- fontSize: 20,
1042
- fontWeight: "700",
1043
- marginBottom: MUSHI_SPACING.roomy
1044
- },
1045
1362
  stepLabel: {
1046
1363
  fontSize: MUSHI_TYPE.sizeLabel,
1047
1364
  fontWeight: "600",
@@ -1052,37 +1369,35 @@ var s = StyleSheet.create({
1052
1369
  },
1053
1370
  catRow: {
1054
1371
  flexDirection: "row",
1055
- justifyContent: "space-between",
1372
+ flexWrap: "wrap",
1373
+ gap: 8,
1056
1374
  marginBottom: MUSHI_SPACING.roomy
1057
1375
  },
1058
1376
  catBtn: {
1377
+ flexDirection: "row",
1059
1378
  alignItems: "center",
1060
- paddingVertical: MUSHI_SPACING.comfy,
1061
- paddingHorizontal: 6,
1062
- borderRadius: MUSHI_RADIUS.card,
1379
+ gap: 6,
1380
+ paddingVertical: 8,
1381
+ paddingHorizontal: 12,
1382
+ borderRadius: 999,
1063
1383
  borderWidth: 1,
1064
- flex: 1,
1065
- marginHorizontal: 3,
1066
1384
  minHeight: 44
1067
1385
  },
1068
1386
  catEmoji: {
1069
- fontSize: 22,
1070
- marginBottom: MUSHI_SPACING.tight
1387
+ fontSize: 16
1071
1388
  },
1072
1389
  catLabel: {
1073
- fontSize: MUSHI_TYPE.sizeLabel,
1390
+ fontSize: 14,
1074
1391
  fontWeight: "600"
1075
1392
  },
1076
1393
  input: {
1077
1394
  borderWidth: 1,
1078
- borderRadius: MUSHI_RADIUS.card,
1079
1395
  padding: 14,
1080
1396
  fontSize: 15,
1081
1397
  minHeight: 100,
1082
1398
  marginBottom: MUSHI_SPACING.roomy
1083
1399
  },
1084
1400
  submitBtn: {
1085
- borderRadius: MUSHI_RADIUS.card,
1086
1401
  paddingVertical: 14,
1087
1402
  alignItems: "center",
1088
1403
  minHeight: 44
@@ -1147,7 +1462,8 @@ var s = StyleSheet.create({
1147
1462
  alignItems: "center",
1148
1463
  paddingVertical: 10,
1149
1464
  borderBottomWidth: 2,
1150
- borderBottomColor: "transparent"
1465
+ borderBottomColor: "transparent",
1466
+ minHeight: 44
1151
1467
  },
1152
1468
  tabLabel: {
1153
1469
  fontSize: 14,
@@ -1155,12 +1471,47 @@ var s = StyleSheet.create({
1155
1471
  },
1156
1472
  inboxRow: {
1157
1473
  paddingVertical: 12,
1474
+ minHeight: 56,
1158
1475
  borderBottomWidth: StyleSheet.hairlineWidth
1159
1476
  },
1477
+ headerCard: {
1478
+ borderWidth: StyleSheet.hairlineWidth,
1479
+ padding: 12,
1480
+ marginBottom: 12
1481
+ },
1482
+ pill: {
1483
+ alignSelf: "flex-start",
1484
+ borderWidth: 1,
1485
+ borderRadius: 999,
1486
+ paddingHorizontal: 8,
1487
+ paddingVertical: 2,
1488
+ fontSize: 12,
1489
+ fontWeight: "600",
1490
+ overflow: "hidden"
1491
+ },
1492
+ skeleton: {
1493
+ height: 14,
1494
+ borderRadius: 6,
1495
+ marginBottom: 10
1496
+ },
1497
+ errorRow: {
1498
+ flexDirection: "row",
1499
+ alignItems: "center",
1500
+ gap: 8,
1501
+ marginBottom: 12
1502
+ },
1503
+ retryBtn: {
1504
+ borderWidth: 1,
1505
+ paddingHorizontal: 12,
1506
+ paddingVertical: 8,
1507
+ minHeight: 44,
1508
+ justifyContent: "center"
1509
+ },
1160
1510
  threadBubble: {
1161
1511
  borderRadius: 10,
1162
1512
  padding: 10,
1163
- marginBottom: 8
1513
+ marginBottom: 8,
1514
+ maxWidth: "88%"
1164
1515
  },
1165
1516
  verifyRow: {
1166
1517
  flexDirection: "row",
@@ -1169,9 +1520,26 @@ var s = StyleSheet.create({
1169
1520
  },
1170
1521
  verifyBtn: {
1171
1522
  flex: 1,
1172
- borderRadius: 10,
1173
1523
  paddingVertical: 10,
1174
- alignItems: "center"
1524
+ alignItems: "center",
1525
+ minHeight: 44
1526
+ },
1527
+ composer: {
1528
+ flexDirection: "row",
1529
+ alignItems: "flex-end",
1530
+ gap: 8
1531
+ },
1532
+ composerInput: {
1533
+ flex: 1,
1534
+ minHeight: 44,
1535
+ maxHeight: 120,
1536
+ marginBottom: 0
1537
+ },
1538
+ sendBtn: {
1539
+ paddingHorizontal: 16,
1540
+ paddingVertical: 12,
1541
+ minHeight: 44,
1542
+ justifyContent: "center"
1175
1543
  },
1176
1544
  assistantBubble: {
1177
1545
  maxWidth: "88%",
@@ -1401,7 +1769,7 @@ import {
1401
1769
  StyleSheet as StyleSheet3,
1402
1770
  Platform as Platform4
1403
1771
  } from "react-native";
1404
- import { MUSHI_BANNER_NEON as MUSHI_BANNER_NEON2, MUSHI_COPY as MUSHI_COPY3, MUSHI_GEOMETRY as MUSHI_GEOMETRY2, MUSHI_SPACING as MUSHI_SPACING2, MUSHI_TYPE as MUSHI_TYPE2, MUSHI_Z } from "@mushi-mushi/core";
1772
+ import { MUSHI_BANNER_NEON, MUSHI_COPY as MUSHI_COPY3, MUSHI_GEOMETRY as MUSHI_GEOMETRY2, MUSHI_SPACING as MUSHI_SPACING2, MUSHI_TYPE as MUSHI_TYPE2, MUSHI_Z } from "@mushi-mushi/core";
1405
1773
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1406
1774
  var MUSHI_BANNER_DEFAULT_HEIGHT = MUSHI_GEOMETRY2.bannerHeight;
1407
1775
  var MushiBanner = ({
@@ -1454,9 +1822,9 @@ var s3 = StyleSheet3.create({
1454
1822
  minHeight: MUSHI_GEOMETRY2.bannerHeight,
1455
1823
  flexDirection: "row",
1456
1824
  alignItems: "stretch",
1457
- backgroundColor: MUSHI_BANNER_NEON2.bg,
1825
+ backgroundColor: MUSHI_BANNER_NEON.bg,
1458
1826
  borderTopWidth: 1,
1459
- borderTopColor: MUSHI_BANNER_NEON2.border,
1827
+ borderTopColor: MUSHI_BANNER_NEON.border,
1460
1828
  zIndex: MUSHI_Z.banner,
1461
1829
  elevation: 8,
1462
1830
  paddingBottom: Platform4.OS === "ios" ? 0 : 0
@@ -1474,18 +1842,18 @@ var s3 = StyleSheet3.create({
1474
1842
  fontSize: MUSHI_TYPE2.sizeLabel,
1475
1843
  fontWeight: "700",
1476
1844
  letterSpacing: 1.2,
1477
- color: MUSHI_BANNER_NEON2.fg
1845
+ color: MUSHI_BANNER_NEON.fg
1478
1846
  },
1479
1847
  message: {
1480
1848
  flex: 1,
1481
1849
  fontSize: 12,
1482
1850
  fontWeight: "500",
1483
- color: MUSHI_BANNER_NEON2.fg
1851
+ color: MUSHI_BANNER_NEON.fg
1484
1852
  },
1485
1853
  cta: {
1486
1854
  fontSize: 12,
1487
1855
  fontWeight: "700",
1488
- color: MUSHI_BANNER_NEON2.fg,
1856
+ color: MUSHI_BANNER_NEON.fg,
1489
1857
  textDecorationLine: "underline"
1490
1858
  },
1491
1859
  dismiss: {
@@ -1495,7 +1863,7 @@ var s3 = StyleSheet3.create({
1495
1863
  dismissText: {
1496
1864
  fontSize: 20,
1497
1865
  lineHeight: 22,
1498
- color: MUSHI_BANNER_NEON2.fg,
1866
+ color: MUSHI_BANNER_NEON.fg,
1499
1867
  opacity: 0.7
1500
1868
  }
1501
1869
  });
@@ -1825,7 +2193,7 @@ function createRNSessionTracker(opts) {
1825
2193
 
1826
2194
  // src/version.ts
1827
2195
  var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
1828
- var MUSHI_SDK_VERSION = "0.22.0" ? "0.22.0" : "0.17.0";
2196
+ var MUSHI_SDK_VERSION = "0.23.0" ? "0.23.0" : "0.17.0";
1829
2197
 
1830
2198
  // src/provider.tsx
1831
2199
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -2320,6 +2688,23 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2320
2688
  const res = await client.listReporterComments(reportId, reporterTokenRef.current);
2321
2689
  return res.ok ? res.data.comments ?? [] : [];
2322
2690
  },
2691
+ async loadMyThread(reportId) {
2692
+ const client = apiClientRef.current;
2693
+ if (!client) return null;
2694
+ try {
2695
+ await reporterTokenReadyRef.current;
2696
+ const res = await client.listReporterComments(reportId, reporterTokenRef.current);
2697
+ return res.ok ? res.data?.comments ?? [] : null;
2698
+ } catch {
2699
+ return null;
2700
+ }
2701
+ },
2702
+ async markReportRead(reportId) {
2703
+ const client = apiClientRef.current;
2704
+ if (!client) return 0;
2705
+ await reporterTokenReadyRef.current;
2706
+ return markReporterReportRead(client, reporterTokenRef.current, reportId);
2707
+ },
2323
2708
  async replyToReport(reportId, body) {
2324
2709
  const client = apiClientRef.current;
2325
2710
  if (!client) return null;
@@ -2427,7 +2812,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
2427
2812
  assistantLabel: config.assistant?.label,
2428
2813
  assistantGreeting: config.assistant?.greeting,
2429
2814
  assistantSuggestions: config.assistant?.suggestions,
2430
- inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0
2815
+ inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0,
2816
+ theme: config.widget?.theme
2431
2817
  }
2432
2818
  )
2433
2819
  ] });