spaps-issue-reporting-react 0.4.1 → 0.4.2

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
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  FloatingIssueReportButton: () => FloatingIssueReportButton,
34
+ IssueReportMessageThread: () => IssueReportMessageThread,
34
35
  IssueReportingPageConfig: () => IssueReportingPageConfig,
35
36
  IssueReportingProvider: () => IssueReportingProvider,
36
37
  ReportModeContext: () => ReportModeContext,
@@ -44,9 +45,13 @@ __export(index_exports, {
44
45
  getIssueStatusClassName: () => getIssueStatusClassName,
45
46
  isClosedIssueStatus: () => isClosedIssueStatus,
46
47
  isOpenIssueStatus: () => isOpenIssueStatus,
48
+ isReporterMessageConflict: () => isReporterMessageConflict,
47
49
  issueReportingKeys: () => issueReportingKeys,
50
+ selectReporterVisibleMessages: () => selectReporterVisibleMessages,
48
51
  useIssueReporting: () => useIssueReporting,
49
52
  useIssueReportingHistory: () => useIssueReportingHistory,
53
+ useIssueReportingMessageMutation: () => useIssueReportingMessageMutation,
54
+ useIssueReportingMessages: () => useIssueReportingMessages,
50
55
  useIssueReportingMutations: () => useIssueReportingMutations,
51
56
  useIssueReportingStatus: () => useIssueReportingStatus,
52
57
  useReportMode: () => useReportMode
@@ -158,13 +163,31 @@ var defaultIssueReportingCopy = {
158
163
  retryAction: "Retry",
159
164
  originHumanLabel: "Human",
160
165
  originMachineLabel: "Machine",
161
- machineOriginFallback: "system"
166
+ machineOriginFallback: "system",
167
+ threadTitle: "Conversation",
168
+ threadDescription: "Messages from the support team about this report, and your replies.",
169
+ threadLoading: "Loading conversation...",
170
+ threadLoadFailed: "Failed to load the conversation",
171
+ threadEmpty: "No messages on this report yet.",
172
+ threadNeedsResponseBadge: "Needs your response",
173
+ threadAuthorOperator: "Support",
174
+ threadAuthorReporter: "You",
175
+ threadKindClarificationRequest: "Question",
176
+ threadKindReporterResponse: "Your reply",
177
+ threadKindFinalResponse: "Resolution",
178
+ threadResponsePlaceholder: "Write your response...",
179
+ threadResponseLabel: "Respond to the support team",
180
+ threadResponseSubmitAction: "Send response",
181
+ threadResponseSubmittingAction: "Sending...",
182
+ threadResponseConflict: "That response was already sent, or the message changed. Refresh and try again.",
183
+ threadResponseFailed: "Failed to send your response"
162
184
  };
163
185
  var issueReportingKeys = {
164
186
  all: ["spaps-issue-reporting"],
165
187
  status: (scope) => [...issueReportingKeys.all, "status", scope],
166
188
  history: (scope) => [...issueReportingKeys.all, "history", scope],
167
- detail: (issueReportId) => [...issueReportingKeys.all, "detail", issueReportId]
189
+ detail: (issueReportId) => [...issueReportingKeys.all, "detail", issueReportId],
190
+ messages: (issueReportId) => [...issueReportingKeys.all, "messages", issueReportId]
168
191
  };
169
192
  function resolvePageUrl(getPageUrl) {
170
193
  if (getPageUrl) {
@@ -334,6 +357,27 @@ function getEntryPointClassName(state) {
334
357
  }
335
358
  return "text-slate-500";
336
359
  }
360
+ function selectReporterVisibleMessages(messages) {
361
+ return messages.filter(
362
+ (message) => message.reporter_visible && message.state === "active"
363
+ ).slice().sort((a, b) => a.created_at.localeCompare(b.created_at));
364
+ }
365
+ function isReporterMessageConflict(error) {
366
+ if (!error || typeof error !== "object") {
367
+ return false;
368
+ }
369
+ const record = error;
370
+ const code = record.code ?? record.error?.code;
371
+ if (typeof code === "string" && code === "ISSUE_REPORT_MESSAGE_CONFLICT") {
372
+ return true;
373
+ }
374
+ const status = record.status ?? record.statusCode ?? record.status_code ?? void 0;
375
+ if (status === 409) {
376
+ return true;
377
+ }
378
+ const message = error instanceof Error ? error.message : typeof record.message === "string" ? record.message : "";
379
+ return /ISSUE_REPORT_MESSAGE_CONFLICT/i.test(message) || /\b409\b/.test(message);
380
+ }
337
381
  function getIssueNoteLengthMessage(note, copy) {
338
382
  if (note.length < NOTE_MIN_LENGTH) {
339
383
  return `${NOTE_MIN_LENGTH - note.length} ${copy.noteMinimumSuffix}`;
@@ -457,6 +501,39 @@ function useIssueReportingMutations() {
457
501
  replyMutation
458
502
  };
459
503
  }
504
+ function useIssueReportingMessages(issueReportId) {
505
+ const { client, isEligible } = useIssueReporting();
506
+ const listMessages = client.issueReporting.listMessages;
507
+ return (0, import_react_query.useQuery)({
508
+ queryKey: issueReportingKeys.messages(issueReportId ?? "none"),
509
+ queryFn: () => listMessages(issueReportId),
510
+ enabled: isEligible && Boolean(issueReportId) && Boolean(listMessages),
511
+ retry: false
512
+ });
513
+ }
514
+ function useIssueReportingMessageMutation(issueReportId) {
515
+ const queryClient = (0, import_react_query.useQueryClient)();
516
+ const { client } = useIssueReporting();
517
+ return (0, import_react_query.useMutation)({
518
+ mutationFn: (payload) => {
519
+ const submit = client.issueReporting.submitMessage;
520
+ if (!submit || !issueReportId) {
521
+ return Promise.reject(
522
+ new Error("This client does not support submitting messages.")
523
+ );
524
+ }
525
+ return submit(issueReportId, payload);
526
+ },
527
+ onSuccess: async () => {
528
+ if (!issueReportId) {
529
+ return;
530
+ }
531
+ await queryClient.invalidateQueries({
532
+ queryKey: issueReportingKeys.messages(issueReportId)
533
+ });
534
+ }
535
+ });
536
+ }
460
537
  function IssueReportingProvider({
461
538
  client,
462
539
  isEligible,
@@ -486,6 +563,7 @@ function IssueReportingProvider({
486
563
  const [scope, setScope] = (0, import_react2.useState)(
487
564
  () => resolveInitialScope(defaultScope, allowTenantScope)
488
565
  );
566
+ const [needsResponseMap, setNeedsResponseMap] = (0, import_react2.useState)({});
489
567
  const [pageConfigs, setPageConfigs] = (0, import_react2.useState)([]);
490
568
  const [registeredTargets, setRegisteredTargets] = (0, import_react2.useState)(
491
569
  []
@@ -510,6 +588,21 @@ function IssueReportingProvider({
510
588
  (0, import_react2.useEffect)(() => {
511
589
  setScope(resolveInitialScope(defaultScope, allowTenantScope));
512
590
  }, [allowTenantScope, defaultScope]);
591
+ const setNeedsResponse = (0, import_react2.useCallback)(
592
+ (issueReportId, needsResponse) => {
593
+ setNeedsResponseMap((current) => {
594
+ if (Boolean(current[issueReportId]) === needsResponse) {
595
+ return current;
596
+ }
597
+ return { ...current, [issueReportId]: needsResponse };
598
+ });
599
+ },
600
+ []
601
+ );
602
+ const needsResponseIssueIds = (0, import_react2.useMemo)(
603
+ () => Object.entries(needsResponseMap).filter(([, needs]) => needs).map(([id]) => id),
604
+ [needsResponseMap]
605
+ );
513
606
  const closePopover = (0, import_react2.useCallback)(() => {
514
607
  setIsPopoverOpen(false);
515
608
  }, []);
@@ -697,7 +790,9 @@ function IssueReportingProvider({
697
790
  createMode,
698
791
  inputModes: resolvedInputModes,
699
792
  defaultInputMode: resolvedDefaultInputMode,
700
- voice: resolvedVoiceConfig
793
+ voice: resolvedVoiceConfig,
794
+ needsResponseIssueIds,
795
+ setNeedsResponse
701
796
  }),
702
797
  [
703
798
  allowTenantScope,
@@ -713,6 +808,7 @@ function IssueReportingProvider({
713
808
  isReportMode,
714
809
  mergedCopy,
715
810
  modalState,
811
+ needsResponseIssueIds,
716
812
  openExistingIssueModal,
717
813
  openPageIssueModal,
718
814
  openPopover,
@@ -724,6 +820,7 @@ function IssueReportingProvider({
724
820
  resolvedVoiceConfig,
725
821
  scope,
726
822
  selectPanel,
823
+ setNeedsResponse,
727
824
  startNewIssue
728
825
  ]
729
826
  );
@@ -1142,7 +1239,8 @@ function IssueReportModalBody({
1142
1239
  children: isSubmitting ? copy.submittingAction : copy.submitAction
1143
1240
  }
1144
1241
  )
1145
- ] })
1242
+ ] }),
1243
+ mode !== "create" && issue ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportMessageThread, { issueReportId: issue.id }) : null
1146
1244
  ] });
1147
1245
  }
1148
1246
  function useIssueReportVoiceCapture({
@@ -1496,6 +1594,237 @@ function useAttachmentState(existingAttachments) {
1496
1594
  reset
1497
1595
  };
1498
1596
  }
1597
+ var REPORTER_MESSAGE_MIN_LENGTH = 1;
1598
+ var REPORTER_MESSAGE_MAX_LENGTH = 2e3;
1599
+ function generateIdempotencyKey() {
1600
+ const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
1601
+ if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
1602
+ return `reporter-msg-${cryptoObj.randomUUID()}`;
1603
+ }
1604
+ return `reporter-msg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
1605
+ }
1606
+ function getMessageKindLabel(kind, copy) {
1607
+ switch (kind) {
1608
+ case "clarification_request":
1609
+ return copy.threadKindClarificationRequest;
1610
+ case "reporter_response":
1611
+ return copy.threadKindReporterResponse;
1612
+ case "final_response":
1613
+ return copy.threadKindFinalResponse;
1614
+ default:
1615
+ return kind;
1616
+ }
1617
+ }
1618
+ function getMessageKindClassName(kind) {
1619
+ switch (kind) {
1620
+ case "clarification_request":
1621
+ return "bg-amber-100 text-amber-700";
1622
+ case "final_response":
1623
+ return "bg-emerald-100 text-emerald-700";
1624
+ default:
1625
+ return "bg-slate-100 text-slate-600";
1626
+ }
1627
+ }
1628
+ function MessageBubble({
1629
+ message,
1630
+ copy
1631
+ }) {
1632
+ const isReporter = message.actor.author_type === "reporter";
1633
+ const isFinal = message.kind === "final_response";
1634
+ const authorLabel = isReporter ? copy.threadAuthorReporter : copy.threadAuthorOperator;
1635
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1636
+ "li",
1637
+ {
1638
+ className: cn(
1639
+ "flex flex-col gap-1",
1640
+ isReporter ? "items-end" : "items-start"
1641
+ ),
1642
+ children: [
1643
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
1644
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1645
+ "span",
1646
+ {
1647
+ className: cn(
1648
+ "rounded-full px-2 py-0.5 font-medium",
1649
+ BADGE_TEXT,
1650
+ getMessageKindClassName(message.kind)
1651
+ ),
1652
+ children: getMessageKindLabel(message.kind, copy)
1653
+ }
1654
+ ),
1655
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: cn("font-medium text-slate-600", LABEL_TEXT), children: authorLabel }),
1656
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1657
+ "time",
1658
+ {
1659
+ className: "text-xs text-slate-400",
1660
+ dateTime: message.created_at,
1661
+ children: formatRelativeTime(message.created_at)
1662
+ }
1663
+ )
1664
+ ] }),
1665
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1666
+ "div",
1667
+ {
1668
+ className: cn(
1669
+ "max-w-[85%] whitespace-pre-wrap rounded-2xl border px-3 py-2 text-sm",
1670
+ isReporter ? "border-slate-900 bg-slate-900 text-white" : isFinal ? "border-emerald-200 bg-emerald-50 text-emerald-950" : "border-slate-200 bg-white text-slate-800"
1671
+ ),
1672
+ children: message.body
1673
+ }
1674
+ )
1675
+ ]
1676
+ }
1677
+ );
1678
+ }
1679
+ function ReporterResponseComposer({
1680
+ issueReportId,
1681
+ copy
1682
+ }) {
1683
+ const mutation = useIssueReportingMessageMutation(issueReportId);
1684
+ const idempotencyKeyRef = (0, import_react5.useRef)(generateIdempotencyKey());
1685
+ const [body, setBody] = (0, import_react5.useState)("");
1686
+ const [submitError, setSubmitError] = (0, import_react5.useState)(null);
1687
+ const normalized = body.trim();
1688
+ const isValid = normalized.length >= REPORTER_MESSAGE_MIN_LENGTH && normalized.length <= REPORTER_MESSAGE_MAX_LENGTH;
1689
+ const isSubmitting = mutation.isPending;
1690
+ const handleSubmit = async () => {
1691
+ if (!isValid || isSubmitting) {
1692
+ return;
1693
+ }
1694
+ setSubmitError(null);
1695
+ try {
1696
+ await mutation.mutateAsync({
1697
+ body: normalized,
1698
+ idempotency_key: idempotencyKeyRef.current
1699
+ });
1700
+ setBody("");
1701
+ idempotencyKeyRef.current = generateIdempotencyKey();
1702
+ } catch (error) {
1703
+ if (isReporterMessageConflict(error)) {
1704
+ setSubmitError(copy.threadResponseConflict);
1705
+ idempotencyKeyRef.current = generateIdempotencyKey();
1706
+ return;
1707
+ }
1708
+ setSubmitError(resolveErrorMessage(error, copy.threadResponseFailed));
1709
+ }
1710
+ };
1711
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-3 space-y-2", children: [
1712
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1713
+ "label",
1714
+ {
1715
+ htmlFor: "issue-report-thread-response",
1716
+ className: cn(
1717
+ "block font-medium uppercase tracking-wide text-slate-500",
1718
+ LABEL_TEXT
1719
+ ),
1720
+ children: copy.threadResponseLabel
1721
+ }
1722
+ ),
1723
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1724
+ "textarea",
1725
+ {
1726
+ id: "issue-report-thread-response",
1727
+ value: body,
1728
+ onChange: (event) => setBody(event.target.value),
1729
+ onKeyDown: (event) => {
1730
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
1731
+ event.preventDefault();
1732
+ void handleSubmit();
1733
+ }
1734
+ },
1735
+ placeholder: copy.threadResponsePlaceholder,
1736
+ className: "h-24 w-full resize-none rounded-2xl border border-slate-300 px-3 py-2 text-sm text-slate-800 outline-none transition focus:border-slate-500 focus:ring-2 focus:ring-slate-200",
1737
+ disabled: isSubmitting
1738
+ }
1739
+ ),
1740
+ submitError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1741
+ "div",
1742
+ {
1743
+ className: "rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700",
1744
+ role: "alert",
1745
+ children: submitError
1746
+ }
1747
+ ) : null,
1748
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1749
+ "button",
1750
+ {
1751
+ type: "button",
1752
+ className: cn(
1753
+ "rounded-full px-4 py-2 text-sm font-semibold text-white transition",
1754
+ isValid && !isSubmitting ? "bg-slate-900 hover:bg-slate-800" : "cursor-not-allowed bg-slate-300"
1755
+ ),
1756
+ onClick: () => void handleSubmit(),
1757
+ disabled: !isValid || isSubmitting,
1758
+ children: isSubmitting ? copy.threadResponseSubmittingAction : copy.threadResponseSubmitAction
1759
+ }
1760
+ ) })
1761
+ ] });
1762
+ }
1763
+ function IssueReportMessageThread({
1764
+ issueReportId
1765
+ }) {
1766
+ const { copy, client, setNeedsResponse } = useIssueReporting();
1767
+ const supportsMessages = Boolean(client.issueReporting.listMessages);
1768
+ const supportsSubmit = Boolean(client.issueReporting.submitMessage);
1769
+ const query = useIssueReportingMessages(issueReportId);
1770
+ const needsResponse = query.data?.needs_response ?? false;
1771
+ (0, import_react5.useEffect)(() => {
1772
+ setNeedsResponse(issueReportId, needsResponse);
1773
+ return () => {
1774
+ setNeedsResponse(issueReportId, false);
1775
+ };
1776
+ }, [issueReportId, needsResponse, setNeedsResponse]);
1777
+ if (!supportsMessages) {
1778
+ return null;
1779
+ }
1780
+ const visibleMessages = selectReporterVisibleMessages(query.data?.items ?? []);
1781
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1782
+ "section",
1783
+ {
1784
+ "aria-label": copy.threadTitle,
1785
+ className: "mt-6 border-t border-slate-100 pt-5",
1786
+ children: [
1787
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
1788
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-sm font-semibold text-slate-900", children: copy.threadTitle }),
1789
+ needsResponse ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1790
+ "span",
1791
+ {
1792
+ className: cn(
1793
+ "rounded-full px-2 py-0.5 font-medium",
1794
+ BADGE_TEXT,
1795
+ "bg-amber-100 text-amber-700"
1796
+ ),
1797
+ children: copy.threadNeedsResponseBadge
1798
+ }
1799
+ ) : null
1800
+ ] }),
1801
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: copy.threadDescription }),
1802
+ query.isPending ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600", children: [
1803
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-4 w-4 animate-spin" }),
1804
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.threadLoading })
1805
+ ] }) : query.error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
1806
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: resolveErrorMessage(query.error, copy.threadLoadFailed) }),
1807
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1808
+ "button",
1809
+ {
1810
+ type: "button",
1811
+ className: "rounded-full border border-rose-300 px-3 py-1 font-medium transition hover:bg-rose-100",
1812
+ onClick: () => void query.refetch(),
1813
+ children: copy.retryAction
1814
+ }
1815
+ )
1816
+ ] }) : visibleMessages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-4 rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-6 text-center text-sm text-slate-500", children: copy.threadEmpty }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "mt-4 space-y-3", children: visibleMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MessageBubble, { message, copy }, message.id)) }),
1817
+ supportsSubmit ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1818
+ ReporterResponseComposer,
1819
+ {
1820
+ issueReportId,
1821
+ copy
1822
+ }
1823
+ ) : null
1824
+ ]
1825
+ }
1826
+ );
1827
+ }
1499
1828
  function IssueReportModeBanner() {
1500
1829
  const { copy } = useIssueReporting();
1501
1830
  const reportMode = useReportMode();
@@ -2047,33 +2376,44 @@ function FloatingIssueReportButton({
2047
2376
  isReportMode,
2048
2377
  isPopoverOpen,
2049
2378
  openPopover,
2050
- closePopover
2379
+ closePopover,
2380
+ needsResponseIssueIds
2051
2381
  } = useIssueReporting();
2052
2382
  const status = useIssueReportingStatus();
2053
2383
  const entryPointState = getEntryPointState(status.data);
2384
+ const needsResponse = needsResponseIssueIds.length > 0;
2054
2385
  if (!isEligible) {
2055
2386
  return null;
2056
2387
  }
2057
2388
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2058
2389
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportModeBanner, {}),
2059
- !isReportMode ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("fixed bottom-12 right-4", Z_FLOATING_BUTTON, positionClassName), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportPopover, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2390
+ !isReportMode ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("fixed bottom-12 right-4", Z_FLOATING_BUTTON, positionClassName), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportPopover, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2060
2391
  "button",
2061
2392
  {
2062
2393
  type: "button",
2063
- "aria-label": copy.entryAriaLabel,
2394
+ "aria-label": needsResponse ? `${copy.entryAriaLabel} (${copy.threadNeedsResponseBadge})` : copy.entryAriaLabel,
2064
2395
  onClick: () => isPopoverOpen ? closePopover() : openPopover(),
2065
2396
  className: cn(
2066
- "flex h-12 w-12 items-center justify-center rounded-full border border-slate-200 bg-white shadow-lg transition hover:-translate-y-0.5 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-slate-300",
2397
+ "relative flex h-12 w-12 items-center justify-center rounded-full border border-slate-200 bg-white shadow-lg transition hover:-translate-y-0.5 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-slate-300",
2067
2398
  status.isPending && "animate-pulse",
2068
2399
  className
2069
2400
  ),
2070
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2071
- import_react4.BugBeetle,
2072
- {
2073
- className: cn("h-6 w-6", getEntryPointClassName(entryPointState)),
2074
- weight: "fill"
2075
- }
2076
- )
2401
+ children: [
2402
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2403
+ import_react4.BugBeetle,
2404
+ {
2405
+ className: cn("h-6 w-6", getEntryPointClassName(entryPointState)),
2406
+ weight: "fill"
2407
+ }
2408
+ ),
2409
+ needsResponse ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2410
+ "span",
2411
+ {
2412
+ "data-testid": "issue-report-needs-response-badge",
2413
+ className: "absolute -right-0.5 -top-0.5 h-3 w-3 rounded-full border-2 border-white bg-amber-500"
2414
+ }
2415
+ ) : null
2416
+ ]
2077
2417
  }
2078
2418
  ) }) }) : null,
2079
2419
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportModal, {})
@@ -2126,6 +2466,7 @@ function ReportableSection({
2126
2466
  // Annotate the CommonJS export names for ESM import in node:
2127
2467
  0 && (module.exports = {
2128
2468
  FloatingIssueReportButton,
2469
+ IssueReportMessageThread,
2129
2470
  IssueReportingPageConfig,
2130
2471
  IssueReportingProvider,
2131
2472
  ReportModeContext,
@@ -2139,9 +2480,13 @@ function ReportableSection({
2139
2480
  getIssueStatusClassName,
2140
2481
  isClosedIssueStatus,
2141
2482
  isOpenIssueStatus,
2483
+ isReporterMessageConflict,
2142
2484
  issueReportingKeys,
2485
+ selectReporterVisibleMessages,
2143
2486
  useIssueReporting,
2144
2487
  useIssueReportingHistory,
2488
+ useIssueReportingMessageMutation,
2489
+ useIssueReportingMessages,
2145
2490
  useIssueReportingMutations,
2146
2491
  useIssueReportingStatus,
2147
2492
  useReportMode