spaps-issue-reporting-react 0.1.2 → 0.1.4

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
+ IssueReportingPageConfig: () => IssueReportingPageConfig,
34
35
  IssueReportingProvider: () => IssueReportingProvider,
35
36
  ReportModeContext: () => ReportModeContext,
36
37
  ReportableSection: () => ReportableSection,
@@ -55,9 +56,10 @@ module.exports = __toCommonJS(index_exports);
55
56
  // src/components.tsx
56
57
  var Dialog = __toESM(require("@radix-ui/react-dialog"));
57
58
  var Popover = __toESM(require("@radix-ui/react-popover"));
58
- var import_react3 = require("@phosphor-icons/react");
59
+ var import_react3 = require("@elevenlabs/react");
60
+ var import_react4 = require("@phosphor-icons/react");
59
61
  var import_date_fns = require("date-fns");
60
- var import_react4 = require("react");
62
+ var import_react5 = __toESM(require("react"));
61
63
 
62
64
  // src/provider.tsx
63
65
  var import_react2 = require("react");
@@ -77,6 +79,9 @@ var import_jsx_runtime = require("react/jsx-runtime");
77
79
  var LIST_LIMIT = 200;
78
80
  var NOTE_MIN_LENGTH = 10;
79
81
  var NOTE_MAX_LENGTH = 2e3;
82
+ var DEFAULT_INPUT_MODES = ["text"];
83
+ var DEFAULT_VOICE_PROVIDER = "elevenlabs_scribe_realtime";
84
+ var DEFAULT_VOICE_MODEL_ID = "scribe_v2_realtime";
80
85
  var initialModalState = {
81
86
  isOpen: false,
82
87
  mode: "create",
@@ -89,10 +94,24 @@ var initialModalState = {
89
94
  var IssueReportingContext = (0, import_react2.createContext)(
90
95
  void 0
91
96
  );
97
+ var IssueReportingPageConfigRegistryContext = (0, import_react2.createContext)(void 0);
98
+ var hiddenMarkerStyle = {
99
+ position: "absolute",
100
+ width: "1px",
101
+ height: "1px",
102
+ padding: 0,
103
+ margin: "-1px",
104
+ overflow: "hidden",
105
+ clip: "rect(0, 0, 0, 0)",
106
+ whiteSpace: "nowrap",
107
+ border: 0
108
+ };
92
109
  var defaultIssueReportingCopy = {
93
110
  entryAriaLabel: "Report issue",
94
111
  popoverTitle: "Issue Reports",
95
112
  reportNewAction: "Report New Issue",
113
+ reportPageAction: "Report This Page",
114
+ reportSpecificAction: "Pick Specific Section",
96
115
  filtersAll: "All",
97
116
  filtersUnresolved: "Unresolved",
98
117
  filtersResolved: "Resolved",
@@ -109,6 +128,9 @@ var defaultIssueReportingCopy = {
109
128
  reportModeTitle: "Report mode is active",
110
129
  reportModeDescription: "Click a highlighted section to capture the broken surface.",
111
130
  reportModeCancelAction: "Cancel",
131
+ generalPageTargetLabel: "Current Page",
132
+ surfaceRequiredDescription: "This page requires selecting a specific section before you submit a report.",
133
+ specificSectionUnavailableDescription: "No specific sections are registered on this page right now.",
112
134
  createTitlePrefix: "Report Issue",
113
135
  editTitlePrefix: "Edit Issue",
114
136
  replyTitlePrefix: "Reply to",
@@ -152,6 +174,25 @@ function resolveInitialScope(defaultScope, allowTenantScope) {
152
174
  }
153
175
  return "mine";
154
176
  }
177
+ function normalizeInputModes(inputModes) {
178
+ if (!inputModes || inputModes.length === 0) {
179
+ return DEFAULT_INPUT_MODES;
180
+ }
181
+ const unique = Array.from(new Set(inputModes));
182
+ const supported = unique.filter(
183
+ (mode) => mode === "text" || mode === "voice"
184
+ );
185
+ if (supported.length === 0) {
186
+ return DEFAULT_INPUT_MODES;
187
+ }
188
+ return supported;
189
+ }
190
+ function resolveDefaultInputMode(defaultInputMode, inputModes) {
191
+ if (defaultInputMode && inputModes.includes(defaultInputMode)) {
192
+ return defaultInputMode;
193
+ }
194
+ return inputModes[0] ?? "text";
195
+ }
155
196
  function normalizeTarget(target, getPageUrl) {
156
197
  if (typeof target === "string") {
157
198
  const normalized = target.trim();
@@ -173,6 +214,44 @@ function normalizeTarget(target, getPageUrl) {
173
214
  metadata: target.metadata ?? {}
174
215
  };
175
216
  }
217
+ function summarizeTarget(target) {
218
+ if (typeof target === "string") {
219
+ const normalized = target.trim();
220
+ return {
221
+ component_key: normalized,
222
+ component_label: normalized,
223
+ surface_ref: null,
224
+ metadata: {}
225
+ };
226
+ }
227
+ const key = target.componentKey.trim();
228
+ return {
229
+ component_key: key,
230
+ component_label: (target.componentLabel ?? key).trim(),
231
+ surface_ref: target.surfaceRef ?? null,
232
+ metadata: target.metadata ?? {}
233
+ };
234
+ }
235
+ function isElementVisibleForReporting(element) {
236
+ if (!element || typeof window === "undefined") {
237
+ return true;
238
+ }
239
+ let current = element;
240
+ while (current) {
241
+ if (current.getAttribute("aria-hidden") === "true") {
242
+ return false;
243
+ }
244
+ if (current instanceof HTMLElement && (current.hidden || current.inert)) {
245
+ return false;
246
+ }
247
+ const computedStyle = window.getComputedStyle(current);
248
+ if (computedStyle.display === "none" || computedStyle.visibility === "hidden" || computedStyle.visibility === "collapse") {
249
+ return false;
250
+ }
251
+ current = current.parentElement;
252
+ }
253
+ return true;
254
+ }
176
255
  function normalizeIssueTarget(issue) {
177
256
  return {
178
257
  component_key: issue.target.component_key,
@@ -254,6 +333,23 @@ function getIssueNoteLengthMessage(note, copy) {
254
333
  }
255
334
  return `${note.length}/${NOTE_MAX_LENGTH}`;
256
335
  }
336
+ function buildGeneralPageTarget(copy, registeredTargets, getPageUrl) {
337
+ return {
338
+ component_key: "general_page",
339
+ component_label: copy.generalPageTargetLabel,
340
+ page_url: resolvePageUrl(getPageUrl),
341
+ surface_ref: null,
342
+ metadata: {
343
+ capture_mode: "general_page",
344
+ registered_target_count: registeredTargets.length,
345
+ ...registeredTargets.length > 0 ? {
346
+ registered_targets: registeredTargets.map(
347
+ ({ target }) => summarizeTarget(target)
348
+ )
349
+ } : {}
350
+ }
351
+ };
352
+ }
257
353
  function useIssueReporting() {
258
354
  const context = (0, import_react2.useContext)(IssueReportingContext);
259
355
  if (!context) {
@@ -263,6 +359,25 @@ function useIssueReporting() {
263
359
  }
264
360
  return context;
265
361
  }
362
+ function IssueReportingPageConfig({
363
+ createMode
364
+ }) {
365
+ const registry = (0, import_react2.useContext)(IssueReportingPageConfigRegistryContext);
366
+ const configId = (0, import_react2.useRef)(/* @__PURE__ */ Symbol("issue-reporting-page-config"));
367
+ const markerRef = (0, import_react2.useRef)(null);
368
+ if (!registry) {
369
+ throw new Error(
370
+ "IssueReportingPageConfig must be used within an IssueReportingProvider"
371
+ );
372
+ }
373
+ (0, import_react2.useEffect)(() => {
374
+ registry.registerPageConfig(configId.current, createMode, () => markerRef.current);
375
+ return () => {
376
+ registry.unregisterPageConfig(configId.current);
377
+ };
378
+ }, [createMode, registry]);
379
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ref: markerRef, style: hiddenMarkerStyle });
380
+ }
266
381
  function useIssueReportingStatus() {
267
382
  const { client, isEligible, scope } = useIssueReporting();
268
383
  return (0, import_react_query.useQuery)({
@@ -335,6 +450,10 @@ function IssueReportingProvider({
335
450
  getPageUrl,
336
451
  defaultScope,
337
452
  allowTenantScope = false,
453
+ defaultCreateMode = "general_page",
454
+ inputModes,
455
+ defaultInputMode,
456
+ voice,
338
457
  copy,
339
458
  children
340
459
  }) {
@@ -352,6 +471,27 @@ function IssueReportingProvider({
352
471
  const [scope, setScope] = (0, import_react2.useState)(
353
472
  () => resolveInitialScope(defaultScope, allowTenantScope)
354
473
  );
474
+ const [pageConfigs, setPageConfigs] = (0, import_react2.useState)([]);
475
+ const [registeredTargets, setRegisteredTargets] = (0, import_react2.useState)(
476
+ []
477
+ );
478
+ const resolvedInputModes = (0, import_react2.useMemo)(
479
+ () => normalizeInputModes(inputModes),
480
+ [inputModes]
481
+ );
482
+ const resolvedDefaultInputMode = (0, import_react2.useMemo)(
483
+ () => resolveDefaultInputMode(defaultInputMode, resolvedInputModes),
484
+ [defaultInputMode, resolvedInputModes]
485
+ );
486
+ const resolvedVoiceConfig = (0, import_react2.useMemo)(
487
+ () => ({
488
+ provider: voice?.provider ?? DEFAULT_VOICE_PROVIDER,
489
+ modelId: voice?.modelId ?? DEFAULT_VOICE_MODEL_ID,
490
+ requireMicrophonePermission: voice?.requireMicrophonePermission ?? true,
491
+ microphone: voice?.microphone
492
+ }),
493
+ [voice]
494
+ );
355
495
  (0, import_react2.useEffect)(() => {
356
496
  setScope(resolveInitialScope(defaultScope, allowTenantScope));
357
497
  }, [allowTenantScope, defaultScope]);
@@ -361,6 +501,51 @@ function IssueReportingProvider({
361
501
  const openPopover = (0, import_react2.useCallback)(() => {
362
502
  setIsPopoverOpen(true);
363
503
  }, []);
504
+ const openCreateModal = (0, import_react2.useCallback)((target) => {
505
+ setIsPopoverOpen(false);
506
+ setIsReportMode(false);
507
+ setModalState({
508
+ isOpen: true,
509
+ mode: "create",
510
+ issueReportId: null,
511
+ issue: null,
512
+ target,
513
+ error: null,
514
+ isHydrating: false
515
+ });
516
+ }, []);
517
+ const registerPageConfig = (0, import_react2.useCallback)(
518
+ (id, createMode2, getElement) => {
519
+ setPageConfigs((current) => [
520
+ ...current.filter((entry) => entry.id !== id),
521
+ { id, createMode: createMode2, getElement }
522
+ ]);
523
+ },
524
+ []
525
+ );
526
+ const unregisterPageConfig = (0, import_react2.useCallback)((id) => {
527
+ setPageConfigs((current) => current.filter((entry) => entry.id !== id));
528
+ }, []);
529
+ const registerTarget = (0, import_react2.useCallback)(
530
+ (id, target, getElement) => {
531
+ setRegisteredTargets((current) => [
532
+ ...current.filter((entry) => entry.id !== id),
533
+ { id, target, getElement }
534
+ ]);
535
+ },
536
+ []
537
+ );
538
+ const unregisterTarget = (0, import_react2.useCallback)((id) => {
539
+ setRegisteredTargets((current) => current.filter((entry) => entry.id !== id));
540
+ }, []);
541
+ const visiblePageConfigs = pageConfigs.filter(
542
+ ({ getElement }) => isElementVisibleForReporting(getElement())
543
+ );
544
+ const visibleRegisteredTargets = registeredTargets.filter(
545
+ ({ getElement }) => isElementVisibleForReporting(getElement())
546
+ );
547
+ const createMode = visiblePageConfigs.length > 0 ? visiblePageConfigs[visiblePageConfigs.length - 1].createMode : defaultCreateMode;
548
+ const hasRegisteredTargets = visibleRegisteredTargets.length > 0;
364
549
  const enterReportMode = (0, import_react2.useCallback)(() => {
365
550
  setIsReportMode(true);
366
551
  setIsPopoverOpen(false);
@@ -372,6 +557,42 @@ function IssueReportingProvider({
372
557
  setModalState(initialModalState);
373
558
  setIsPopoverOpen(true);
374
559
  }, []);
560
+ const openPageIssueModal = (0, import_react2.useCallback)(() => {
561
+ if (!isEligible) {
562
+ return;
563
+ }
564
+ openCreateModal(
565
+ buildGeneralPageTarget(mergedCopy, visibleRegisteredTargets, getPageUrl)
566
+ );
567
+ }, [
568
+ getPageUrl,
569
+ isEligible,
570
+ mergedCopy,
571
+ openCreateModal,
572
+ visibleRegisteredTargets
573
+ ]);
574
+ const startNewIssue = (0, import_react2.useCallback)(() => {
575
+ if (!isEligible) {
576
+ return;
577
+ }
578
+ if (createMode === "surface_required") {
579
+ if (hasRegisteredTargets) {
580
+ enterReportMode();
581
+ }
582
+ return;
583
+ }
584
+ if (createMode === "surface_preferred" && hasRegisteredTargets) {
585
+ enterReportMode();
586
+ return;
587
+ }
588
+ openPageIssueModal();
589
+ }, [
590
+ createMode,
591
+ enterReportMode,
592
+ hasRegisteredTargets,
593
+ isEligible,
594
+ openPageIssueModal
595
+ ]);
375
596
  const hydrateIssueIntoModal = (0, import_react2.useCallback)(
376
597
  async (issueReportId, mode) => {
377
598
  setModalState({
@@ -430,18 +651,9 @@ function IssueReportingProvider({
430
651
  return;
431
652
  }
432
653
  const normalizedTarget = normalizeTarget(target, getPageUrl);
433
- setIsReportMode(false);
434
- setModalState({
435
- isOpen: true,
436
- mode: "create",
437
- issueReportId: null,
438
- issue: null,
439
- target: normalizedTarget,
440
- error: null,
441
- isHydrating: false
442
- });
654
+ openCreateModal(normalizedTarget);
443
655
  },
444
- [getPageUrl, isEligible]
656
+ [getPageUrl, isEligible, openCreateModal]
445
657
  );
446
658
  const contextValue = (0, import_react2.useMemo)(
447
659
  () => ({
@@ -451,9 +663,12 @@ function IssueReportingProvider({
451
663
  principalId: principalId ?? null,
452
664
  copy: mergedCopy,
453
665
  isReportMode,
666
+ hasRegisteredTargets,
454
667
  enterReportMode,
455
668
  cancelReportMode,
456
669
  selectPanel,
670
+ openPageIssueModal,
671
+ startNewIssue,
457
672
  isPopoverOpen,
458
673
  openPopover,
459
674
  closePopover,
@@ -463,7 +678,11 @@ function IssueReportingProvider({
463
678
  retryModalHydration,
464
679
  scope,
465
680
  setScope,
466
- allowTenantScope
681
+ allowTenantScope,
682
+ createMode,
683
+ inputModes: resolvedInputModes,
684
+ defaultInputMode: resolvedDefaultInputMode,
685
+ voice: resolvedVoiceConfig
467
686
  }),
468
687
  [
469
688
  allowTenantScope,
@@ -471,30 +690,60 @@ function IssueReportingProvider({
471
690
  client,
472
691
  closeModal,
473
692
  closePopover,
693
+ createMode,
474
694
  enterReportMode,
695
+ hasRegisteredTargets,
475
696
  isEligible,
476
697
  isPopoverOpen,
477
698
  isReportMode,
478
699
  mergedCopy,
479
700
  modalState,
480
701
  openExistingIssueModal,
702
+ openPageIssueModal,
481
703
  openPopover,
482
704
  principalId,
483
705
  reporterRoleHint,
484
706
  retryModalHydration,
707
+ resolvedDefaultInputMode,
708
+ resolvedInputModes,
709
+ resolvedVoiceConfig,
485
710
  scope,
486
- selectPanel
711
+ selectPanel,
712
+ startNewIssue
487
713
  ]
488
714
  );
489
715
  const reportModeValue = (0, import_react2.useMemo)(
490
716
  () => ({
491
717
  isReportMode,
492
718
  selectPanel,
493
- cancelReportMode
719
+ cancelReportMode,
720
+ hasRegisteredTargets,
721
+ registerTarget,
722
+ unregisterTarget
723
+ }),
724
+ [
725
+ cancelReportMode,
726
+ hasRegisteredTargets,
727
+ isReportMode,
728
+ registerTarget,
729
+ selectPanel,
730
+ unregisterTarget
731
+ ]
732
+ );
733
+ const pageConfigRegistryValue = (0, import_react2.useMemo)(
734
+ () => ({
735
+ registerPageConfig,
736
+ unregisterPageConfig
494
737
  }),
495
- [cancelReportMode, isReportMode, selectPanel]
738
+ [registerPageConfig, unregisterPageConfig]
739
+ );
740
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
741
+ IssueReportingPageConfigRegistryContext.Provider,
742
+ {
743
+ value: pageConfigRegistryValue,
744
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReportModeContext.Provider, { value: reportModeValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IssueReportingContext.Provider, { value: contextValue, children }) })
745
+ }
496
746
  );
497
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReportModeContext.Provider, { value: reportModeValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IssueReportingContext.Provider, { value: contextValue, children }) });
498
747
  }
499
748
 
500
749
  // src/components.tsx
@@ -502,6 +751,18 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
502
751
  function cn(...values) {
503
752
  return values.filter(Boolean).join(" ");
504
753
  }
754
+ var Z_FLOATING_BUTTON = "z-[65]";
755
+ var Z_BANNER = "z-[70]";
756
+ var Z_POPOVER = "z-[70]";
757
+ var Z_MODAL_OVERLAY = "z-[80]";
758
+ var Z_MODAL_CONTENT = "z-[81]";
759
+ var POPOVER_WIDTH = "w-[360px]";
760
+ var MODAL_WIDTH = "w-[calc(100vw-2rem)]";
761
+ var MODAL_RADIUS = "rounded-[28px]";
762
+ var POPOVER_SHADOW = "shadow-[0_18px_48px_rgba(15,23,42,0.18)]";
763
+ var MODAL_SHADOW = "shadow-[0_28px_80px_rgba(15,23,42,0.24)]";
764
+ var BADGE_TEXT = "text-[11px]";
765
+ var LABEL_TEXT = "text-[11px]";
505
766
  function truncate(value, max = 80) {
506
767
  if (value.length <= max) {
507
768
  return value;
@@ -522,6 +783,16 @@ function resolveErrorMessage(error, fallback) {
522
783
  }
523
784
  return fallback;
524
785
  }
786
+ function getCommittedTranscriptText(committedTranscripts) {
787
+ return committedTranscripts.map((segment) => segment.text.trim()).filter(Boolean).join(" ").trim();
788
+ }
789
+ function appendTranscriptToNote(current, transcript) {
790
+ const normalizedCurrent = current.trim();
791
+ if (!normalizedCurrent) {
792
+ return transcript;
793
+ }
794
+ return `${normalizedCurrent} ${transcript}`;
795
+ }
525
796
  function resolveReporterId(issue) {
526
797
  return issue.reporter_principal_id ?? issue.reporter_user_id ?? null;
527
798
  }
@@ -554,13 +825,390 @@ function getIssueOriginText(issue, copy) {
554
825
  const humanName = issue.reporter_display_name?.trim();
555
826
  return humanName ? `${copy.originHumanLabel} \xB7 ${humanName}` : copy.originHumanLabel;
556
827
  }
828
+ function IssueReportVoicePanel({
829
+ canUseText,
830
+ effectiveInputMode,
831
+ isSubmitting,
832
+ isVoiceActive,
833
+ isVoiceConnecting,
834
+ voice,
835
+ voiceTokenResult,
836
+ committedTranscript,
837
+ voiceError,
838
+ scribeError,
839
+ onSelectText,
840
+ onSelectVoice,
841
+ onStartVoiceInput,
842
+ onStopVoiceInput,
843
+ onAppendTranscript
844
+ }) {
845
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
846
+ canUseText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 flex gap-2", children: [
847
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
848
+ "button",
849
+ {
850
+ type: "button",
851
+ className: cn(
852
+ "inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
853
+ effectiveInputMode === "text" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
854
+ ),
855
+ onClick: onSelectText,
856
+ disabled: isSubmitting,
857
+ children: [
858
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.TextT, { className: "h-3.5 w-3.5" }),
859
+ "Text Input"
860
+ ]
861
+ }
862
+ ),
863
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
864
+ "button",
865
+ {
866
+ type: "button",
867
+ className: cn(
868
+ "inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
869
+ effectiveInputMode === "voice" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
870
+ ),
871
+ onClick: onSelectVoice,
872
+ disabled: isSubmitting,
873
+ children: [
874
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Microphone, { className: "h-3.5 w-3.5" }),
875
+ "Voice Input"
876
+ ]
877
+ }
878
+ )
879
+ ] }) : null,
880
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
881
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
882
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
883
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Voice Input" }),
884
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "mt-1 text-xs text-slate-600", children: [
885
+ "Provider: ",
886
+ voiceTokenResult?.provider ?? voice.provider,
887
+ " \xB7 Model:",
888
+ " ",
889
+ voiceTokenResult?.model_id ?? voice.modelId
890
+ ] }),
891
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: voice.requireMicrophonePermission ? "Microphone access is required to transcribe." : "Microphone access policy is optional." })
892
+ ] }),
893
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
894
+ isVoiceActive || isVoiceConnecting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
895
+ "button",
896
+ {
897
+ type: "button",
898
+ className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100",
899
+ onClick: onStopVoiceInput,
900
+ disabled: isSubmitting,
901
+ children: "Stop Voice Input"
902
+ }
903
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
904
+ "button",
905
+ {
906
+ type: "button",
907
+ className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
908
+ onClick: onStartVoiceInput,
909
+ disabled: isSubmitting,
910
+ children: "Start Voice Input"
911
+ }
912
+ ),
913
+ canUseText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
914
+ "button",
915
+ {
916
+ type: "button",
917
+ className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-60",
918
+ onClick: onAppendTranscript,
919
+ disabled: isSubmitting || !committedTranscript.trim(),
920
+ children: "Append Transcript"
921
+ }
922
+ ) : null
923
+ ] })
924
+ ] }),
925
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-3 rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-700", children: committedTranscript ? committedTranscript : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-slate-500", children: "No committed transcript yet." }) }),
926
+ voiceError || scribeError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-2 rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700", children: voiceError ?? scribeError }) : isVoiceConnecting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-2 text-xs text-slate-500", children: "Connecting voice transcription..." }) : null
927
+ ] })
928
+ ] });
929
+ }
930
+ function IssueReportNoteEditor({
931
+ canUseText,
932
+ note,
933
+ normalizedNote,
934
+ isSubmitting,
935
+ copy,
936
+ onNoteChange,
937
+ onSubmit
938
+ }) {
939
+ if (!canUseText) {
940
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-600", children: [
941
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: getIssueNoteLengthMessage(normalizedNote, copy) }),
942
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-1", children: "Submit is enabled after a committed transcript reaches 10-2000 characters." })
943
+ ] });
944
+ }
945
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 space-y-2", children: [
946
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
947
+ "textarea",
948
+ {
949
+ value: note,
950
+ onChange: (event) => onNoteChange(event.target.value),
951
+ onKeyDown: (event) => {
952
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
953
+ event.preventDefault();
954
+ onSubmit();
955
+ }
956
+ },
957
+ placeholder: copy.notePlaceholder,
958
+ className: "h-36 w-full resize-none rounded-2xl border border-slate-300 px-4 py-3 text-sm text-slate-800 outline-none transition focus:border-slate-500 focus:ring-2 focus:ring-slate-200",
959
+ disabled: isSubmitting,
960
+ autoFocus: true
961
+ }
962
+ ),
963
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between text-xs text-slate-500", children: [
964
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: getIssueNoteLengthMessage(note, copy) }),
965
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.keyboardShortcutHint })
966
+ ] })
967
+ ] });
968
+ }
969
+ function IssueReportModalDescription({
970
+ copy,
971
+ mode,
972
+ target
973
+ }) {
974
+ if (mode === "create" && target) {
975
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
976
+ copy.createDescriptionPrefix,
977
+ " ",
978
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { className: "rounded bg-slate-100 px-1.5 py-0.5 text-xs text-slate-700", children: target.page_url })
979
+ ] });
980
+ }
981
+ return mode === "edit" ? copy.editDescription : copy.replyDescription;
982
+ }
983
+ function IssueReportModalBody({
984
+ copy,
985
+ mode,
986
+ issue,
987
+ isHydrating,
988
+ error,
989
+ canUseVoice,
990
+ canUseText,
991
+ effectiveInputMode,
992
+ note,
993
+ normalizedNote,
994
+ isValid,
995
+ isSubmitting,
996
+ isVoiceActive,
997
+ isVoiceConnecting,
998
+ voice,
999
+ voiceTokenResult,
1000
+ committedTranscript,
1001
+ voiceError,
1002
+ scribeError,
1003
+ submitError,
1004
+ onRetryHydration,
1005
+ onClose,
1006
+ onSelectText,
1007
+ onSelectVoice,
1008
+ onStartVoiceInput,
1009
+ onStopVoiceInput,
1010
+ onAppendTranscript,
1011
+ onNoteChange,
1012
+ onSubmit
1013
+ }) {
1014
+ if (isHydrating) {
1015
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600", children: [
1016
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-4 w-4 animate-spin" }),
1017
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.hydrateLoading })
1018
+ ] });
1019
+ }
1020
+ if (error) {
1021
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
1022
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: error }),
1023
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
1024
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1025
+ "button",
1026
+ {
1027
+ type: "button",
1028
+ className: "rounded-full border border-rose-300 px-3 py-1 font-medium transition hover:bg-rose-100",
1029
+ onClick: onRetryHydration,
1030
+ children: copy.retryAction
1031
+ }
1032
+ ),
1033
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1034
+ "button",
1035
+ {
1036
+ type: "button",
1037
+ className: "rounded-full border border-slate-300 px-3 py-1 font-medium text-slate-700 transition hover:bg-slate-50",
1038
+ onClick: onClose,
1039
+ children: copy.cancelAction
1040
+ }
1041
+ )
1042
+ ] })
1043
+ ] });
1044
+ }
1045
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1046
+ mode === "create" && canUseVoice ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1047
+ IssueReportVoicePanel,
1048
+ {
1049
+ canUseText,
1050
+ effectiveInputMode,
1051
+ isSubmitting,
1052
+ isVoiceActive,
1053
+ isVoiceConnecting,
1054
+ voice,
1055
+ voiceTokenResult,
1056
+ committedTranscript,
1057
+ voiceError,
1058
+ scribeError,
1059
+ onSelectText,
1060
+ onSelectVoice,
1061
+ onStartVoiceInput,
1062
+ onStopVoiceInput,
1063
+ onAppendTranscript
1064
+ }
1065
+ ) : null,
1066
+ mode === "reply" && issue ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
1067
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: copy.originalIssueLabel }),
1068
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-sm text-slate-700", children: issue.note })
1069
+ ] }) : null,
1070
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1071
+ IssueReportNoteEditor,
1072
+ {
1073
+ canUseText,
1074
+ note,
1075
+ normalizedNote,
1076
+ isSubmitting,
1077
+ copy,
1078
+ onNoteChange,
1079
+ onSubmit
1080
+ }
1081
+ ),
1082
+ submitError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-4 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700", children: submitError }) : null,
1083
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-6 flex justify-end gap-3", children: [
1084
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1085
+ "button",
1086
+ {
1087
+ type: "button",
1088
+ className: "rounded-full border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50",
1089
+ onClick: onClose,
1090
+ disabled: isSubmitting,
1091
+ children: copy.cancelAction
1092
+ }
1093
+ ),
1094
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1095
+ "button",
1096
+ {
1097
+ type: "button",
1098
+ className: cn(
1099
+ "rounded-full px-4 py-2 text-sm font-semibold text-white transition",
1100
+ isValid && !isSubmitting ? "bg-slate-900 hover:bg-slate-800" : "cursor-not-allowed bg-slate-300"
1101
+ ),
1102
+ onClick: onSubmit,
1103
+ disabled: !isValid || isSubmitting,
1104
+ children: isSubmitting ? copy.submittingAction : copy.submitAction
1105
+ }
1106
+ )
1107
+ ] })
1108
+ ] });
1109
+ }
1110
+ function useIssueReportVoiceCapture({
1111
+ canUseVoice,
1112
+ defaultInputMode,
1113
+ isSubmitting,
1114
+ voice
1115
+ }) {
1116
+ const [inputMode, setInputMode] = (0, import_react5.useState)(defaultInputMode);
1117
+ const [voiceTokenResult, setVoiceTokenResult] = (0, import_react5.useState)(null);
1118
+ const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react5.useState)(null);
1119
+ const [voiceError, setVoiceError] = (0, import_react5.useState)(null);
1120
+ const {
1121
+ status: scribeStatus,
1122
+ isConnected,
1123
+ isTranscribing,
1124
+ committedTranscripts,
1125
+ error: scribeError,
1126
+ connect: connectScribe,
1127
+ disconnect: disconnectScribe
1128
+ } = (0, import_react3.useScribe)({
1129
+ autoConnect: false,
1130
+ modelId: voice.modelId,
1131
+ microphone: voice.microphone
1132
+ });
1133
+ const committedTranscript = (0, import_react5.useMemo)(
1134
+ () => getCommittedTranscriptText(committedTranscripts),
1135
+ [committedTranscripts]
1136
+ );
1137
+ const isVoiceConnecting = scribeStatus === "connecting";
1138
+ const isVoiceActive = isConnected || isTranscribing;
1139
+ const resetVoiceCapture = (0, import_react5.useCallback)(() => {
1140
+ disconnectScribe();
1141
+ setInputMode(defaultInputMode);
1142
+ setVoiceTokenResult(null);
1143
+ setVoiceSubmitMetadata(null);
1144
+ setVoiceError(null);
1145
+ }, [defaultInputMode, disconnectScribe]);
1146
+ const startVoiceInput = (0, import_react5.useCallback)(
1147
+ async (createVoiceToken) => {
1148
+ if (!canUseVoice || isSubmitting || isVoiceActive || isVoiceConnecting) {
1149
+ return;
1150
+ }
1151
+ if (!createVoiceToken) {
1152
+ setVoiceError("Voice input is unavailable for this client.");
1153
+ return;
1154
+ }
1155
+ setVoiceError(null);
1156
+ try {
1157
+ const tokenResult = await createVoiceToken();
1158
+ setVoiceTokenResult(tokenResult);
1159
+ setVoiceSubmitMetadata(tokenResult);
1160
+ await connectScribe({
1161
+ token: tokenResult.token,
1162
+ modelId: tokenResult.model_id,
1163
+ microphone: voice.microphone
1164
+ });
1165
+ setInputMode("voice");
1166
+ } catch (startError) {
1167
+ setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
1168
+ }
1169
+ },
1170
+ [
1171
+ canUseVoice,
1172
+ connectScribe,
1173
+ isSubmitting,
1174
+ isVoiceActive,
1175
+ isVoiceConnecting,
1176
+ voice.microphone
1177
+ ]
1178
+ );
1179
+ const appendTranscript = (0, import_react5.useCallback)(
1180
+ (setNote) => {
1181
+ if (!committedTranscript.trim()) {
1182
+ return;
1183
+ }
1184
+ setNote((current) => appendTranscriptToNote(current, committedTranscript));
1185
+ setInputMode("text");
1186
+ },
1187
+ [committedTranscript]
1188
+ );
1189
+ return {
1190
+ inputMode,
1191
+ setInputMode,
1192
+ voiceTokenResult,
1193
+ voiceSubmitMetadata,
1194
+ committedTranscript,
1195
+ voiceError,
1196
+ scribeError,
1197
+ isVoiceConnecting,
1198
+ isVoiceActive,
1199
+ resetVoiceCapture,
1200
+ startVoiceInput,
1201
+ stopVoiceInput: disconnectScribe,
1202
+ appendTranscript
1203
+ };
1204
+ }
557
1205
  function IssueReportModeBanner() {
558
1206
  const { copy } = useIssueReporting();
559
1207
  const reportMode = useReportMode();
560
1208
  if (!reportMode?.isReportMode) {
561
1209
  return null;
562
1210
  }
563
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fixed inset-x-4 top-4 z-[70] flex justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "max-w-xl rounded-full border border-amber-300 bg-amber-50/95 px-4 py-3 text-sm text-amber-950 shadow-lg backdrop-blur", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
1211
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("fixed inset-x-4 top-4 flex justify-center", Z_BANNER), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "max-w-xl rounded-full border border-amber-300 bg-amber-50/95 px-4 py-3 text-sm text-amber-950 shadow-lg backdrop-blur", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
564
1212
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "font-medium", children: copy.reportModeTitle }),
565
1213
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
566
1214
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -583,7 +1231,7 @@ function IssueList({
583
1231
  const history = useIssueReportingHistory(filter);
584
1232
  if (history.isPending) {
585
1233
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-5 text-sm text-slate-600", children: [
586
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.Spinner, { className: "h-4 w-4 animate-spin" }),
1234
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-4 w-4 animate-spin" }),
587
1235
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.historyLoading })
588
1236
  ] });
589
1237
  }
@@ -613,7 +1261,7 @@ function IssueList({
613
1261
  {
614
1262
  className: "rounded-2xl border border-slate-200 bg-white px-3 py-3 shadow-sm",
615
1263
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-start gap-3", children: [
616
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-0.5 flex-shrink-0", children: isClosedIssueStatus(issue.status) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.CheckCircle, { className: "h-4 w-4 text-emerald-600", weight: "fill" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.Circle, { className: "h-4 w-4 text-rose-600", weight: "fill" }) }),
1264
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-0.5 flex-shrink-0", children: isClosedIssueStatus(issue.status) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.CheckCircle, { className: "h-4 w-4 text-emerald-600", weight: "fill" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Circle, { className: "h-4 w-4 text-rose-600", weight: "fill" }) }),
617
1265
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0 flex-1", children: [
618
1266
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
619
1267
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0", children: [
@@ -623,13 +1271,14 @@ function IssueList({
623
1271
  "span",
624
1272
  {
625
1273
  className: cn(
626
- "rounded-full px-2 py-0.5 text-[11px] font-medium",
1274
+ "rounded-full px-2 py-0.5 font-medium",
1275
+ BADGE_TEXT,
627
1276
  getIssueStatusClassName(issue.status)
628
1277
  ),
629
1278
  children: getIssueStatusBadgeLabel(issue.status)
630
1279
  }
631
1280
  ),
632
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-600", children: getIssueOriginText(issue, copy) }),
1281
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: cn("rounded-full bg-slate-100 px-2 py-0.5 font-medium text-slate-600", BADGE_TEXT), children: getIssueOriginText(issue, copy) }),
633
1282
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs text-slate-400", children: formatRelativeTime(issue.updated_at) })
634
1283
  ] })
635
1284
  ] }),
@@ -653,22 +1302,25 @@ function IssueList({
653
1302
  }) });
654
1303
  }
655
1304
  function IssueReportPopover({ children }) {
656
- const [filter, setFilter] = (0, import_react4.useState)("all");
1305
+ const [filter, setFilter] = (0, import_react5.useState)("all");
657
1306
  const {
658
1307
  copy,
659
1308
  isPopoverOpen,
660
1309
  openPopover,
661
1310
  closePopover,
662
1311
  enterReportMode,
1312
+ openPageIssueModal,
663
1313
  openExistingIssueModal,
664
1314
  scope,
665
1315
  setScope,
666
- allowTenantScope
1316
+ allowTenantScope,
1317
+ createMode,
1318
+ hasRegisteredTargets
667
1319
  } = useIssueReporting();
668
1320
  const history = useIssueReportingHistory("all");
669
1321
  const status = useIssueReportingStatus();
670
1322
  const allItems = history.data?.items ?? [];
671
- const counts = (0, import_react4.useMemo)(
1323
+ const counts = (0, import_react5.useMemo)(
672
1324
  () => ({
673
1325
  all: allItems.length,
674
1326
  unresolved: filterIssueReports(allItems, "unresolved").length,
@@ -677,6 +1329,9 @@ function IssueReportPopover({ children }) {
677
1329
  [allItems]
678
1330
  );
679
1331
  const statusSummary = status.data ? `${status.data.open_count} open \xB7 ${status.data.recent_resolved_count} recently resolved` : "Status reflects the active scope.";
1332
+ const showSectionFirst = createMode === "surface_required" || createMode === "surface_preferred" && hasRegisteredTargets;
1333
+ const sectionActionDisabled = !hasRegisteredTargets;
1334
+ const helperText = createMode === "surface_required" ? hasRegisteredTargets ? copy.surfaceRequiredDescription : copy.specificSectionUnavailableDescription : createMode === "surface_preferred" && !hasRegisteredTargets ? copy.specificSectionUnavailableDescription : null;
680
1335
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
681
1336
  Popover.Root,
682
1337
  {
@@ -690,26 +1345,78 @@ function IssueReportPopover({ children }) {
690
1345
  align: "end",
691
1346
  side: "top",
692
1347
  sideOffset: 12,
693
- className: "z-[70] w-[360px] rounded-3xl border border-slate-200 bg-white p-4 shadow-[0_18px_48px_rgba(15,23,42,0.18)]",
1348
+ className: cn(
1349
+ "rounded-3xl border border-slate-200 bg-white p-4",
1350
+ Z_POPOVER,
1351
+ POPOVER_WIDTH,
1352
+ POPOVER_SHADOW
1353
+ ),
694
1354
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4", children: [
695
1355
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
696
1356
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
697
1357
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-sm font-semibold text-slate-900", children: copy.popoverTitle }),
698
1358
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: statusSummary })
699
1359
  ] }),
700
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
701
- "button",
702
- {
703
- type: "button",
704
- className: "rounded-full bg-slate-900 px-3 py-2 text-xs font-semibold text-white transition hover:bg-slate-800",
705
- onClick: () => {
706
- closePopover();
707
- enterReportMode();
708
- },
709
- children: copy.reportNewAction
710
- }
711
- )
1360
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-wrap justify-end gap-2", children: showSectionFirst ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1361
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1362
+ "button",
1363
+ {
1364
+ type: "button",
1365
+ className: cn(
1366
+ "rounded-full px-3 py-2 text-xs font-semibold transition",
1367
+ sectionActionDisabled ? "cursor-not-allowed bg-slate-200 text-slate-500" : "bg-slate-900 text-white hover:bg-slate-800"
1368
+ ),
1369
+ onClick: () => {
1370
+ if (sectionActionDisabled) {
1371
+ return;
1372
+ }
1373
+ closePopover();
1374
+ enterReportMode();
1375
+ },
1376
+ disabled: sectionActionDisabled,
1377
+ children: createMode === "surface_required" ? copy.reportNewAction : copy.reportSpecificAction
1378
+ }
1379
+ ),
1380
+ createMode !== "surface_required" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1381
+ "button",
1382
+ {
1383
+ type: "button",
1384
+ className: "rounded-full border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-50",
1385
+ onClick: () => {
1386
+ closePopover();
1387
+ openPageIssueModal();
1388
+ },
1389
+ children: copy.reportPageAction
1390
+ }
1391
+ ) : null
1392
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1393
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1394
+ "button",
1395
+ {
1396
+ type: "button",
1397
+ className: "rounded-full bg-slate-900 px-3 py-2 text-xs font-semibold text-white transition hover:bg-slate-800",
1398
+ onClick: () => {
1399
+ closePopover();
1400
+ openPageIssueModal();
1401
+ },
1402
+ children: createMode === "general_page" ? copy.reportPageAction : copy.reportNewAction
1403
+ }
1404
+ ),
1405
+ hasRegisteredTargets ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1406
+ "button",
1407
+ {
1408
+ type: "button",
1409
+ className: "rounded-full border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-50",
1410
+ onClick: () => {
1411
+ closePopover();
1412
+ enterReportMode();
1413
+ },
1414
+ children: copy.reportSpecificAction
1415
+ }
1416
+ ) : null
1417
+ ] }) })
712
1418
  ] }),
1419
+ helperText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-xs text-amber-900", children: helperText }) : null,
713
1420
  status.error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
714
1421
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: status.error.message || copy.statusLoadFailed }),
715
1422
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -723,7 +1430,7 @@ function IssueReportPopover({ children }) {
723
1430
  )
724
1431
  ] }) : null,
725
1432
  allowTenantScope ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-2", children: [
726
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-[11px] font-medium uppercase tracking-wide text-slate-500", children: copy.scopeLabel }),
1433
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("font-medium uppercase tracking-wide text-slate-500", LABEL_TEXT), children: copy.scopeLabel }),
727
1434
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex gap-2", children: [
728
1435
  ["tenant", copy.scopeTenant],
729
1436
  ["mine", copy.scopeMine]
@@ -779,18 +1486,46 @@ function IssueReportPopover({ children }) {
779
1486
  }
780
1487
  function IssueReportModal() {
781
1488
  const {
1489
+ client,
782
1490
  copy,
783
1491
  reporterRoleHint,
784
1492
  modalState,
785
1493
  closeModal,
786
- retryModalHydration
1494
+ retryModalHydration,
1495
+ inputModes,
1496
+ defaultInputMode,
1497
+ voice
787
1498
  } = useIssueReporting();
788
1499
  const { createMutation, updateMutation, replyMutation } = useIssueReportingMutations();
789
- const [note, setNote] = (0, import_react4.useState)("");
790
- const [submitError, setSubmitError] = (0, import_react4.useState)(null);
1500
+ const [note, setNote] = (0, import_react5.useState)("");
1501
+ const [submitError, setSubmitError] = (0, import_react5.useState)(null);
791
1502
  const { isOpen, mode, issue, target, isHydrating, error } = modalState;
792
- (0, import_react4.useEffect)(() => {
1503
+ const canUseVoice = mode === "create" && inputModes.includes("voice");
1504
+ const canUseText = mode !== "create" || inputModes.includes("text");
1505
+ const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending;
1506
+ const {
1507
+ inputMode,
1508
+ setInputMode,
1509
+ voiceTokenResult,
1510
+ voiceSubmitMetadata,
1511
+ committedTranscript,
1512
+ voiceError,
1513
+ scribeError,
1514
+ isVoiceConnecting,
1515
+ isVoiceActive,
1516
+ resetVoiceCapture,
1517
+ startVoiceInput,
1518
+ stopVoiceInput,
1519
+ appendTranscript
1520
+ } = useIssueReportVoiceCapture({
1521
+ canUseVoice,
1522
+ defaultInputMode,
1523
+ isSubmitting,
1524
+ voice
1525
+ });
1526
+ (0, import_react5.useEffect)(() => {
793
1527
  if (!isOpen) {
1528
+ resetVoiceCapture();
794
1529
  setNote("");
795
1530
  setSubmitError(null);
796
1531
  return;
@@ -800,140 +1535,145 @@ function IssueReportModal() {
800
1535
  } else {
801
1536
  setNote("");
802
1537
  }
1538
+ resetVoiceCapture();
803
1539
  setSubmitError(null);
804
- }, [isOpen, mode, issue]);
805
- const isValid = note.trim().length >= 10 && note.trim().length <= 2e3;
806
- const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending;
1540
+ }, [isOpen, issue, mode, resetVoiceCapture]);
1541
+ const effectiveInputMode = mode !== "create" ? "text" : canUseVoice && inputMode === "voice" ? "voice" : "text";
1542
+ const normalizedNote = effectiveInputMode === "voice" ? committedTranscript.trim() : note.trim();
1543
+ const isValid = normalizedNote.length >= 10 && normalizedNote.length <= 2e3;
807
1544
  const title = mode === "create" ? `${copy.createTitlePrefix}: ${target?.component_label ?? ""}` : mode === "edit" ? `${copy.editTitlePrefix}: ${target?.component_label ?? ""}` : `${copy.replyTitlePrefix}: ${target?.component_label ?? ""}`;
1545
+ const handleCloseModal = () => {
1546
+ resetVoiceCapture();
1547
+ closeModal();
1548
+ };
1549
+ const handleStartVoiceInput = async () => {
1550
+ await startVoiceInput(client.issueReporting.createVoiceToken);
1551
+ };
1552
+ const handleStopVoiceInput = () => {
1553
+ stopVoiceInput();
1554
+ };
1555
+ const handleAppendTranscript = () => {
1556
+ appendTranscript(setNote);
1557
+ };
808
1558
  const handleSubmit = async () => {
809
1559
  if (!target || !isValid || isSubmitting) {
810
1560
  return;
811
1561
  }
812
1562
  setSubmitError(null);
813
1563
  try {
814
- const normalizedNote = note.trim();
1564
+ const noteForSubmit = normalizedNote;
1565
+ const effectiveVoiceMetadata = effectiveInputMode === "voice" ? voiceSubmitMetadata ?? {
1566
+ provider: voice.provider,
1567
+ token: "",
1568
+ model_id: voice.modelId,
1569
+ expires_in_seconds: 0,
1570
+ retain_audio: false,
1571
+ attach_transcript: true
1572
+ } : null;
815
1573
  if (mode === "create") {
816
1574
  await createMutation.mutateAsync({
817
- target,
818
- note: normalizedNote,
1575
+ target: effectiveVoiceMetadata && effectiveInputMode === "voice" ? {
1576
+ ...target,
1577
+ metadata: {
1578
+ ...target.metadata ?? {},
1579
+ capture: {
1580
+ input_mode: "voice",
1581
+ provider: effectiveVoiceMetadata.provider,
1582
+ model_id: effectiveVoiceMetadata.model_id,
1583
+ retain_audio: effectiveVoiceMetadata.retain_audio,
1584
+ attach_transcript: effectiveVoiceMetadata.attach_transcript
1585
+ }
1586
+ }
1587
+ } : target,
1588
+ note: noteForSubmit,
819
1589
  reporter_role_hint: reporterRoleHint
820
1590
  });
821
1591
  } else if (mode === "edit" && issue) {
822
1592
  await updateMutation.mutateAsync({
823
1593
  issueReportId: issue.id,
824
- note: normalizedNote
1594
+ note: noteForSubmit
825
1595
  });
826
1596
  } else if (mode === "reply" && issue) {
827
1597
  await replyMutation.mutateAsync({
828
1598
  issueReportId: issue.id,
829
- note: normalizedNote,
1599
+ note: noteForSubmit,
830
1600
  reporterRoleHint
831
1601
  });
832
1602
  }
1603
+ resetVoiceCapture();
833
1604
  closeModal();
834
1605
  } catch (submissionError) {
835
1606
  setSubmitError(resolveErrorMessage(submissionError, "Failed to submit issue report"));
836
1607
  }
837
1608
  };
838
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open && closeModal(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Dialog.Portal, { children: [
839
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Overlay, { className: "fixed inset-0 z-[80] bg-slate-950/45 backdrop-blur-sm" }),
840
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Dialog.Content, { className: "fixed left-1/2 top-1/2 z-[81] w-[calc(100vw-2rem)] max-w-xl -translate-x-1/2 -translate-y-1/2 rounded-[28px] border border-slate-200 bg-white p-6 shadow-[0_28px_80px_rgba(15,23,42,0.24)] focus:outline-none", children: [
841
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Title, { className: "text-lg font-semibold text-slate-950", children: title }),
842
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Description, { className: "mt-2 text-sm text-slate-600", children: mode === "create" && target ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
843
- copy.createDescriptionPrefix,
844
- " ",
845
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { className: "rounded bg-slate-100 px-1.5 py-0.5 text-xs text-slate-700", children: target.page_url })
846
- ] }) : mode === "edit" ? copy.editDescription : copy.replyDescription }),
847
- isHydrating ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600", children: [
848
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.Spinner, { className: "h-4 w-4 animate-spin" }),
849
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.hydrateLoading })
850
- ] }) : error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
851
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: error }),
852
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
853
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
854
- "button",
855
- {
856
- type: "button",
857
- className: "rounded-full border border-rose-300 px-3 py-1 font-medium transition hover:bg-rose-100",
858
- onClick: () => retryModalHydration(),
859
- children: copy.retryAction
860
- }
861
- ),
862
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
863
- "button",
1609
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open && handleCloseModal(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Dialog.Portal, { children: [
1610
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Overlay, { className: cn("fixed inset-0 bg-slate-950/45 backdrop-blur-sm", Z_MODAL_OVERLAY) }),
1611
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1612
+ Dialog.Content,
1613
+ {
1614
+ className: cn(
1615
+ "fixed left-1/2 top-1/2 max-w-xl -translate-x-1/2 -translate-y-1/2 border border-slate-200 bg-white p-6 focus:outline-none",
1616
+ Z_MODAL_CONTENT,
1617
+ MODAL_WIDTH,
1618
+ MODAL_RADIUS,
1619
+ MODAL_SHADOW
1620
+ ),
1621
+ children: [
1622
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Title, { className: "text-lg font-semibold text-slate-950", children: title }),
1623
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Description, { className: "mt-2 text-sm text-slate-600", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1624
+ IssueReportModalDescription,
864
1625
  {
865
- type: "button",
866
- className: "rounded-full border border-slate-300 px-3 py-1 font-medium text-slate-700 transition hover:bg-slate-50",
867
- onClick: closeModal,
868
- children: copy.cancelAction
1626
+ copy,
1627
+ mode,
1628
+ target
869
1629
  }
870
- )
871
- ] })
872
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
873
- mode === "reply" && issue ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
874
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: copy.originalIssueLabel }),
875
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-sm text-slate-700", children: issue.note })
876
- ] }) : null,
877
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 space-y-2", children: [
1630
+ ) }),
878
1631
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
879
- "textarea",
1632
+ IssueReportModalBody,
880
1633
  {
881
- value: note,
882
- onChange: (event) => setNote(event.target.value),
883
- onKeyDown: (event) => {
884
- if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
885
- event.preventDefault();
886
- void handleSubmit();
887
- }
888
- },
889
- placeholder: copy.notePlaceholder,
890
- className: "h-36 w-full resize-none rounded-2xl border border-slate-300 px-4 py-3 text-sm text-slate-800 outline-none transition focus:border-slate-500 focus:ring-2 focus:ring-slate-200",
891
- disabled: isSubmitting,
892
- autoFocus: true
893
- }
894
- ),
895
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between text-xs text-slate-500", children: [
896
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: getIssueNoteLengthMessage(note, copy) }),
897
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.keyboardShortcutHint })
898
- ] })
899
- ] }),
900
- submitError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-4 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700", children: submitError }) : null,
901
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-6 flex justify-end gap-3", children: [
902
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
903
- "button",
904
- {
905
- type: "button",
906
- className: "rounded-full border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50",
907
- onClick: closeModal,
908
- disabled: isSubmitting,
909
- children: copy.cancelAction
1634
+ copy,
1635
+ mode,
1636
+ issue,
1637
+ isHydrating,
1638
+ error,
1639
+ canUseVoice,
1640
+ canUseText,
1641
+ effectiveInputMode,
1642
+ note,
1643
+ normalizedNote,
1644
+ isValid,
1645
+ isSubmitting,
1646
+ isVoiceActive,
1647
+ isVoiceConnecting,
1648
+ voice,
1649
+ voiceTokenResult,
1650
+ committedTranscript,
1651
+ voiceError,
1652
+ scribeError,
1653
+ submitError,
1654
+ onRetryHydration: () => void retryModalHydration(),
1655
+ onClose: handleCloseModal,
1656
+ onSelectText: () => setInputMode("text"),
1657
+ onSelectVoice: () => setInputMode("voice"),
1658
+ onStartVoiceInput: () => void handleStartVoiceInput(),
1659
+ onStopVoiceInput: handleStopVoiceInput,
1660
+ onAppendTranscript: handleAppendTranscript,
1661
+ onNoteChange: setNote,
1662
+ onSubmit: () => void handleSubmit()
910
1663
  }
911
1664
  ),
912
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1665
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
913
1666
  "button",
914
1667
  {
915
1668
  type: "button",
916
- className: cn(
917
- "rounded-full px-4 py-2 text-sm font-semibold text-white transition",
918
- isValid && !isSubmitting ? "bg-slate-900 hover:bg-slate-800" : "cursor-not-allowed bg-slate-300"
919
- ),
920
- onClick: () => void handleSubmit(),
921
- disabled: !isValid || isSubmitting,
922
- children: isSubmitting ? copy.submittingAction : copy.submitAction
1669
+ className: "absolute right-4 top-4 inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 text-slate-500 transition hover:bg-slate-50 hover:text-slate-900",
1670
+ "aria-label": "Close issue report modal",
1671
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.X, { className: "h-4 w-4" })
923
1672
  }
924
- )
925
- ] })
926
- ] }),
927
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
928
- "button",
929
- {
930
- type: "button",
931
- className: "absolute right-4 top-4 inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 text-slate-500 transition hover:bg-slate-50 hover:text-slate-900",
932
- "aria-label": "Close issue report modal",
933
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.X, { className: "h-4 w-4" })
934
- }
935
- ) })
936
- ] })
1673
+ ) })
1674
+ ]
1675
+ }
1676
+ )
937
1677
  ] }) });
938
1678
  }
939
1679
  function FloatingIssueReportButton({
@@ -955,7 +1695,7 @@ function FloatingIssueReportButton({
955
1695
  }
956
1696
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
957
1697
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportModeBanner, {}),
958
- !isReportMode ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("fixed bottom-12 right-4 z-[65]", positionClassName), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportPopover, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1698
+ !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)(
959
1699
  "button",
960
1700
  {
961
1701
  type: "button",
@@ -967,7 +1707,7 @@ function FloatingIssueReportButton({
967
1707
  className
968
1708
  ),
969
1709
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
970
- import_react3.BugBeetle,
1710
+ import_react4.BugBeetle,
971
1711
  {
972
1712
  className: cn("h-6 w-6", getEntryPointClassName(entryPointState)),
973
1713
  weight: "fill"
@@ -986,9 +1726,23 @@ function ReportableSection({
986
1726
  }) {
987
1727
  const reportMode = useReportMode();
988
1728
  const isSelectable = Boolean(reportMode?.isReportMode);
989
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1729
+ const targetId = import_react5.default.useRef(/* @__PURE__ */ Symbol("reportable-section"));
1730
+ const elementRef = import_react5.default.useRef(null);
1731
+ (0, import_react5.useEffect)(() => {
1732
+ if (!reportMode) {
1733
+ return;
1734
+ }
1735
+ reportMode.registerTarget(targetId.current, reportableName, () => elementRef.current);
1736
+ return () => {
1737
+ reportMode.unregisterTarget(targetId.current);
1738
+ };
1739
+ }, [reportMode, reportableName]);
1740
+ return import_react5.default.createElement(
990
1741
  Component,
991
1742
  {
1743
+ ref: (node) => {
1744
+ elementRef.current = node;
1745
+ },
992
1746
  className: cn(
993
1747
  className,
994
1748
  isSelectable && "cursor-pointer ring-2 ring-amber-400 transition hover:ring-amber-500"
@@ -1003,14 +1757,15 @@ function ReportableSection({
1003
1757
  }
1004
1758
  } : void 0,
1005
1759
  role: isSelectable ? "button" : void 0,
1006
- tabIndex: isSelectable ? 0 : void 0,
1007
- children
1008
- }
1760
+ tabIndex: isSelectable ? 0 : void 0
1761
+ },
1762
+ children
1009
1763
  );
1010
1764
  }
1011
1765
  // Annotate the CommonJS export names for ESM import in node:
1012
1766
  0 && (module.exports = {
1013
1767
  FloatingIssueReportButton,
1768
+ IssueReportingPageConfig,
1014
1769
  IssueReportingProvider,
1015
1770
  ReportModeContext,
1016
1771
  ReportableSection,