@sailfish-ai/recorder 1.10.7 → 1.10.9

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.
Files changed (35) hide show
  1. package/dist/chunks/{chunkSerializer-BuEZW3N4.js → chunkSerializer-B_szIq8O.js} +1 -1
  2. package/dist/chunks/chunkSerializer-B_szIq8O.js.br +0 -0
  3. package/dist/chunks/chunkSerializer-B_szIq8O.js.gz +0 -0
  4. package/dist/chunks/{chunkSerializer-DM9muyGb.js → chunkSerializer-DdlgLmEH.js} +1 -1
  5. package/dist/chunks/chunkSerializer-DdlgLmEH.js.br +0 -0
  6. package/dist/chunks/chunkSerializer-DdlgLmEH.js.gz +0 -0
  7. package/dist/chunks/{index-soHaKXF4.js → index-B8gnDRst.js} +254 -236
  8. package/dist/chunks/index-B8gnDRst.js.br +0 -0
  9. package/dist/chunks/index-B8gnDRst.js.gz +0 -0
  10. package/dist/chunks/{index-BlPJWz9y.js → index-w2Ted1rp.js} +451 -376
  11. package/dist/chunks/index-w2Ted1rp.js.br +0 -0
  12. package/dist/chunks/index-w2Ted1rp.js.gz +0 -0
  13. package/dist/inAppReportIssueModal/index.js +106 -42
  14. package/dist/inAppReportIssueModal/integrations.js +0 -3
  15. package/dist/inAppReportIssueModal/types.js +1 -0
  16. package/dist/inAppReportIssueModal/ui.js +15 -8
  17. package/dist/index.js +15 -4
  18. package/dist/recorder.cjs +2 -2
  19. package/dist/recorder.js +12 -11
  20. package/dist/recorder.js.br +0 -0
  21. package/dist/recorder.js.gz +0 -0
  22. package/dist/types/inAppReportIssueModal/index.d.ts +5 -1
  23. package/dist/types/inAppReportIssueModal/types.d.ts +1 -0
  24. package/dist/types/index.d.ts +1 -0
  25. package/dist/types/websocket.d.ts +4 -0
  26. package/dist/websocket.js +11 -0
  27. package/package.json +1 -1
  28. package/dist/chunks/chunkSerializer-BuEZW3N4.js.br +0 -0
  29. package/dist/chunks/chunkSerializer-BuEZW3N4.js.gz +0 -0
  30. package/dist/chunks/chunkSerializer-DM9muyGb.js.br +0 -0
  31. package/dist/chunks/chunkSerializer-DM9muyGb.js.gz +0 -0
  32. package/dist/chunks/index-BlPJWz9y.js.br +0 -0
  33. package/dist/chunks/index-BlPJWz9y.js.gz +0 -0
  34. package/dist/chunks/index-soHaKXF4.js.br +0 -0
  35. package/dist/chunks/index-soHaKXF4.js.gz +0 -0
Binary file
Binary file
@@ -21,14 +21,66 @@ export const ReportIssueContext = {
21
21
  triageBaseUrl: "https://app.sailfishqa.com",
22
22
  deactivateIsolation: () => { },
23
23
  integrationData: null,
24
+ showEngTicketFieldsDefault: false,
24
25
  };
25
26
  let modalEl = null;
27
+ let effectiveShowEngTicketFields = false;
26
28
  // Function to set up custom multiselect listeners
27
29
  function setupCustomMultiSelectListeners(fieldId, onChange) {
28
30
  const container = document.getElementById(`${fieldId}-container`);
29
31
  const dropdown = document.getElementById(`${fieldId}-dropdown`);
30
32
  if (!container || !dropdown)
31
33
  return;
34
+ // Helper to rebuild chips display from current selection state
35
+ function updateChipsDisplay() {
36
+ const selectedValues = [];
37
+ const selectedChips = [];
38
+ dropdown.querySelectorAll(".sf-multiselect-option").forEach((opt) => {
39
+ const optEl = opt;
40
+ if (optEl.dataset.selected === "true") {
41
+ const value = optEl.dataset.value || "";
42
+ const label = optEl.textContent?.trim() || "";
43
+ selectedValues.push(value);
44
+ selectedChips.push(`<span class="sf-multiselect-chip" data-value="${value}" style="display:inline-flex; align-items:center; gap:4px; background:#e5e7eb; color:#374151; padding:2px 8px; border-radius:16px; font-size:13px; white-space:nowrap;">
45
+ ${label}
46
+ <span class="sf-multiselect-chip-remove" data-value="${value}" style="cursor:pointer; display:inline-flex; align-items:center; font-size:16px; line-height:1; color:#6b7280;" onclick="event.stopPropagation();">&times;</span>
47
+ </span>`);
48
+ }
49
+ });
50
+ const chipsContainer = container.querySelector(".sf-multiselect-chips");
51
+ if (chipsContainer) {
52
+ chipsContainer.innerHTML =
53
+ selectedChips.join("") ||
54
+ '<span style="color:#9ca3af;">Select...</span>';
55
+ // Re-bind chip remove listeners
56
+ chipsContainer
57
+ .querySelectorAll(".sf-multiselect-chip-remove")
58
+ .forEach((removeBtn) => {
59
+ removeBtn.addEventListener("click", (e) => {
60
+ e.stopPropagation();
61
+ const value = removeBtn.dataset.value || "";
62
+ // Deselect the option in the dropdown
63
+ const opt = dropdown.querySelector(`.sf-multiselect-option[data-value="${value}"]`);
64
+ if (opt) {
65
+ opt.dataset.selected = "false";
66
+ opt.style.backgroundColor = "";
67
+ }
68
+ updateChipsDisplay();
69
+ // Collect remaining selected values
70
+ const remaining = [];
71
+ dropdown
72
+ .querySelectorAll(".sf-multiselect-option")
73
+ .forEach((o) => {
74
+ if (o.dataset.selected === "true") {
75
+ remaining.push(o.dataset.value || "");
76
+ }
77
+ });
78
+ onChange(remaining);
79
+ });
80
+ });
81
+ }
82
+ return selectedValues;
83
+ }
32
84
  // Handle option clicks
33
85
  const options = dropdown.querySelectorAll(".sf-multiselect-option");
34
86
  options.forEach((option) => {
@@ -45,27 +97,12 @@ function setupCustomMultiSelectListeners(fieldId, onChange) {
45
97
  else {
46
98
  optionEl.style.backgroundColor = "";
47
99
  }
48
- // Get all selected values and labels
49
- const selectedValues = [];
50
- const selectedLabels = [];
51
- dropdown.querySelectorAll(".sf-multiselect-option").forEach((opt) => {
52
- const optEl = opt;
53
- if (optEl.dataset.selected === "true") {
54
- selectedValues.push(optEl.dataset.value || "");
55
- selectedLabels.push(optEl.textContent || "");
56
- }
57
- });
58
- // Update input display
59
- const input = container.querySelector(".sf-multiselect-input span");
60
- const displayText = selectedLabels.join(", ");
61
- if (input) {
62
- input.textContent = displayText || "Select...";
63
- input.style.color = displayText ? "#000" : "#9ca3af";
64
- }
65
- // Call onChange callback
100
+ const selectedValues = updateChipsDisplay();
66
101
  onChange(selectedValues);
67
102
  });
68
103
  });
104
+ // Bind chip remove listeners for any pre-selected values
105
+ updateChipsDisplay();
69
106
  // Close dropdown on outside click
70
107
  document.addEventListener("click", (e) => {
71
108
  const target = e.target;
@@ -126,7 +163,7 @@ function generateEngTicketFieldsHTML() {
126
163
  </label>
127
164
  <select id="sf-eng-ticket-team"
128
165
  style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; outline:none; appearance:none; cursor:pointer; background-color: white; color: #9ca3af;">
129
- <option value="" disabled selected style="color: #9ca3af;">Select team...</option>
166
+ <option value="" selected style="color: #9ca3af;">Select team...</option>
130
167
  </select>
131
168
  </div>
132
169
  `;
@@ -139,7 +176,7 @@ function generateEngTicketFieldsHTML() {
139
176
  </label>
140
177
  <select id="sf-eng-ticket-project"
141
178
  style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; outline:none; appearance:none; cursor:pointer; background-color: white; color: #9ca3af;">
142
- <option value="" disabled selected style="color: #9ca3af;">Select project...</option>
179
+ <option value="" selected style="color: #9ca3af;">Select project...</option>
143
180
  </select>
144
181
  </div>
145
182
  `;
@@ -152,7 +189,7 @@ function generateEngTicketFieldsHTML() {
152
189
  </label>
153
190
  <select id="sf-eng-ticket-type"
154
191
  style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; outline:none; appearance:none; cursor:pointer; background-color: white; color: #9ca3af;">
155
- <option value="" disabled selected style="color: #9ca3af;">Select project first...</option>
192
+ <option value="" selected style="color: #9ca3af;">Select project first...</option>
156
193
  </select>
157
194
  </div>
158
195
  `;
@@ -186,7 +223,7 @@ function generateEngTicketFieldsHTML() {
186
223
  </label>
187
224
  <select id="sf-eng-ticket-sprint"
188
225
  style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; outline:none; appearance:none; cursor:pointer; background-color: white; color: #9ca3af;">
189
- <option value="" disabled selected style="color: #9ca3af;">Select sprint...</option>
226
+ <option value="" selected style="color: #9ca3af;">Select sprint...</option>
190
227
  </select>
191
228
  </div>
192
229
  `;
@@ -267,6 +304,8 @@ export function setupIssueReporting(options) {
267
304
  ReportIssueContext.backendApi = options.backendApi;
268
305
  ReportIssueContext.resolveSessionId = options.getSessionId;
269
306
  ReportIssueContext.integrationData = options.integrationData || null;
307
+ ReportIssueContext.showEngTicketFieldsDefault =
308
+ options.showEngTicketFieldsInReportIssueModalDefault ?? false;
270
309
  if (options.customBaseUrl)
271
310
  ReportIssueContext.triageBaseUrl = options.customBaseUrl;
272
311
  ReportIssueContext.shortcuts = mergeShortcutsConfig(options.shortcuts);
@@ -340,11 +379,13 @@ function getSessionIdSafely() {
340
379
  }
341
380
  return ReportIssueContext.resolveSessionId();
342
381
  }
343
- export function openReportIssueModal() {
382
+ export function openReportIssueModal(options) {
344
383
  if (isRecording) {
345
384
  stopRecording();
346
385
  return;
347
386
  }
387
+ effectiveShowEngTicketFields =
388
+ options?.showEngTicketFields ?? ReportIssueContext.showEngTicketFieldsDefault;
348
389
  injectModalHTML();
349
390
  if (modalEl)
350
391
  document.body.appendChild(modalEl);
@@ -672,7 +713,7 @@ function injectModalHTML(initialMode = "lookback") {
672
713
  Create an Issue
673
714
  </label>
674
715
 
675
- <label id="sf-create-eng-ticket-label" style="display:${ReportIssueContext.integrationData ? "flex" : "none"}; align-items:center; gap:8px; font-size:14px; font-weight:500; cursor:pointer;">
716
+ <label id="sf-create-eng-ticket-label" style="display:${ReportIssueContext.integrationData && effectiveShowEngTicketFields ? "flex" : "none"}; align-items:center; gap:8px; font-size:14px; font-weight:500; cursor:pointer;">
676
717
  <input type="checkbox" id="sf-create-eng-ticket-checkbox" ${currentState.createEngTicket ? "checked" : ""}
677
718
  style="width:16px; height:16px; accent-color:#295DBF; cursor:pointer;">
678
719
  Create an Eng Ticket
@@ -694,7 +735,7 @@ function injectModalHTML(initialMode = "lookback") {
694
735
  </div>
695
736
 
696
737
  <!-- Engineering Ticket Fields (shown when create eng ticket is checked) -->
697
- <div id="sf-eng-ticket-fields-container" style="display:${currentState.createEngTicket ? "block" : "none"}; margin-top: ${currentState.createIssue ? "12px" : "0"};">
738
+ <div id="sf-eng-ticket-fields-container" style="display:${currentState.createEngTicket && ReportIssueContext.integrationData ? "block" : "none"}; margin-top: ${currentState.createIssue ? "12px" : "0"};">
698
739
  ${generateEngTicketFieldsHTML()}
699
740
  </div>
700
741
  </div>
@@ -769,10 +810,12 @@ function injectModalHTML(initialMode = "lookback") {
769
810
  renderDynamicFields(currentState.engTicketProject, currentState.engTicketIssueType);
770
811
  }
771
812
  }
772
- // If integration just became available, show the checkbox
773
- const label = document.getElementById("sf-create-eng-ticket-label");
774
- if (label) {
775
- label.style.display = "flex";
813
+ // If integration just became available, show the checkbox (only if eng ticket fields are enabled)
814
+ if (effectiveShowEngTicketFields) {
815
+ const label = document.getElementById("sf-create-eng-ticket-label");
816
+ if (label) {
817
+ label.style.display = "flex";
818
+ }
776
819
  }
777
820
  });
778
821
  }
@@ -1407,45 +1450,58 @@ async function createTriage(startTimestamp, endTimestamp, description) {
1407
1450
  try {
1408
1451
  showStatusModal(true);
1409
1452
  const response = await createTriageFromRecorder(ReportIssueContext.apiKey, ReportIssueContext.backendApi, getSessionIdSafely(), startTimestamp, endTimestamp, description);
1453
+ if (response?.errors?.length) {
1454
+ const errorMsg = response.errors.map((e) => e.message).join("; ");
1455
+ console.error("GraphQL error creating triage:", errorMsg);
1456
+ showStatusModal(false, null, errorMsg);
1457
+ return;
1458
+ }
1410
1459
  const triageId = response?.data?.createTriageFromRecorder?.id;
1411
1460
  if (triageId) {
1412
1461
  showStatusModal(false, { type: "triage", id: triageId });
1413
1462
  }
1414
1463
  else {
1415
1464
  console.error("No Triage ID returned from backend.");
1416
- showStatusModal(false, null);
1465
+ showStatusModal(false, null, "No triage was created. Please try again.");
1417
1466
  }
1418
1467
  }
1419
1468
  catch (error) {
1420
1469
  console.error("Error creating triage:", error);
1421
- showStatusModal(false, null);
1470
+ showStatusModal(false, null, "Something went wrong. Please try again.");
1422
1471
  }
1423
1472
  }
1424
1473
  async function createTriageAndIssue(startTimestamp, endTimestamp, description, issueName, issueDescription, createEngTicket, engTicketTeam, engTicketProject, engTicketPriority, engTicketLabels, engTicketIssueType, engTicketCustomFields) {
1425
1474
  try {
1426
1475
  showStatusModal(true);
1427
1476
  const response = await createTriageAndIssueFromRecorder(ReportIssueContext.apiKey, ReportIssueContext.backendApi, getSessionIdSafely(), startTimestamp, endTimestamp, description, issueName, issueDescription, createEngTicket, engTicketTeam, engTicketProject, engTicketPriority, engTicketLabels, engTicketIssueType, engTicketCustomFields);
1477
+ if (response?.errors?.length) {
1478
+ const errorMsg = response.errors.map((e) => e.message).join("; ");
1479
+ console.error("GraphQL error creating triage and issue:", errorMsg);
1480
+ showStatusModal(false, null, errorMsg);
1481
+ return;
1482
+ }
1428
1483
  const issueId = response?.data?.createTriageAndIssueFromRecorder?.id;
1429
1484
  if (issueId) {
1430
1485
  showStatusModal(false, { type: "issue", id: issueId });
1431
1486
  }
1432
1487
  else {
1433
1488
  console.error("No Issue ID returned from backend.");
1434
- showStatusModal(false, null);
1489
+ showStatusModal(false, null, "No issue was created. Please try again.");
1435
1490
  }
1436
1491
  }
1437
1492
  catch (error) {
1438
1493
  console.error("Error creating triage and issue:", error);
1439
- showStatusModal(false, null);
1494
+ showStatusModal(false, null, "Something went wrong. Please try again.");
1440
1495
  }
1441
1496
  }
1442
- function showStatusModal(isLoading, result) {
1497
+ function showStatusModal(isLoading, result, errorMessage) {
1443
1498
  const triageId = result?.type === "triage" ? result.id : undefined;
1444
1499
  const issueId = result?.type === "issue" ? result.id : undefined;
1445
- showTriageStatusModal(isLoading, triageId, issueId);
1500
+ showTriageStatusModal(isLoading, triageId, issueId, errorMessage);
1446
1501
  }
1447
- function showTriageStatusModal(isLoading, triageId, issueId) {
1502
+ function showTriageStatusModal(isLoading, triageId, issueId, errorMessage) {
1448
1503
  removeExistingModals();
1504
+ const isError = !isLoading && errorMessage;
1449
1505
  // Prefer issue URL if available, otherwise use triage URL
1450
1506
  const resultUrl = issueId
1451
1507
  ? `${ReportIssueContext.triageBaseUrl}/issues/${issueId}?from=inAppReportIssue`
@@ -1462,10 +1518,16 @@ function showTriageStatusModal(isLoading, triageId, issueId) {
1462
1518
  alignItems: "center",
1463
1519
  justifyContent: "center",
1464
1520
  });
1465
- const statusTitle = isLoading ? "Reporting Issue..." : "Issue reported!";
1521
+ const statusTitle = isLoading
1522
+ ? "Reporting Issue..."
1523
+ : isError
1524
+ ? "Failed to report issue"
1525
+ : "Issue reported!";
1466
1526
  const statusSubtitle = isLoading
1467
1527
  ? `<p style="font-size:14px; color:#64748b; line-height:20px;">This may take ~10 seconds</p>`
1468
- : "";
1528
+ : isError
1529
+ ? `<p style="font-size:14px; color:#ef4444; line-height:20px;">${errorMessage}</p>`
1530
+ : "";
1469
1531
  const spinner = isLoading
1470
1532
  ? `<div style="display:flex; justify-content:center; align-items:center; padding: 40px 0;">
1471
1533
  <div style="width:24px; height:24px; border:2px solid #295dbf; border-top:2px solid white; border-radius:50%; animation:spin 1s linear infinite;"></div>
@@ -1496,11 +1558,11 @@ function showTriageStatusModal(isLoading, triageId, issueId) {
1496
1558
  </svg>
1497
1559
  </button>
1498
1560
 
1499
- <h2 style="font-size:18px; font-weight:600; margin-bottom:${isLoading ? 8 : 40}px; line-height:28px;">${statusTitle}</h2>
1561
+ <h2 style="font-size:18px; font-weight:600; margin-bottom:${isLoading || isError ? 8 : 40}px; line-height:28px;">${statusTitle}</h2>
1500
1562
  ${statusSubtitle}
1501
1563
  ${spinner}
1502
1564
 
1503
- <div style="display:flex; justify-content:flex-end; align-items:center; gap:8px;">
1565
+ <div style="display:${isError ? "none" : "flex"}; justify-content:flex-end; align-items:center; gap:8px;">
1504
1566
  <button id="sf-copy-triage-link"
1505
1567
  style="background:white; border:1px solid #e2e8f0; padding:8px 16px; border-radius:6px;
1506
1568
  font-size:14px; color: #0f172a; cursor:pointer;">
@@ -1555,8 +1617,10 @@ function showTriageStatusModal(isLoading, triageId, issueId) {
1555
1617
  window.open(resultUrl, "_blank");
1556
1618
  }
1557
1619
  });
1558
- // Auto-hide after success
1559
- setTimeout(() => fadeCardAndRemove(container, card, 300), 10000);
1620
+ // Auto-hide after success (not on error, so user can read the message)
1621
+ if (!isError) {
1622
+ setTimeout(() => fadeCardAndRemove(container, card, 300), 10000);
1623
+ }
1560
1624
  }
1561
1625
  }
1562
1626
  function fadeCardAndRemove(container, card, durationMs = 300) {
@@ -61,7 +61,6 @@ export async function refreshIntegrationData(apiKey, backendApi) {
61
61
  export function populateSelectOptions(selectElement, options, defaultValue) {
62
62
  const placeholderOption = document.createElement("option");
63
63
  placeholderOption.value = "";
64
- placeholderOption.disabled = true;
65
64
  placeholderOption.selected = !defaultValue;
66
65
  placeholderOption.textContent = "Select...";
67
66
  placeholderOption.style.color = "#9ca3af";
@@ -129,7 +128,6 @@ export function populateSprintOptions(selectElement, sprints, currentValue) {
129
128
  // Placeholder option
130
129
  const placeholderOption = document.createElement("option");
131
130
  placeholderOption.value = "";
132
- placeholderOption.disabled = true;
133
131
  placeholderOption.selected = !currentValue;
134
132
  placeholderOption.textContent = "Select sprint...";
135
133
  placeholderOption.style.color = "#9ca3af";
@@ -194,7 +192,6 @@ export function updateIssueTypeOptions(selectElement, projectId) {
194
192
  selectElement.innerHTML = "";
195
193
  const placeholderOption = document.createElement("option");
196
194
  placeholderOption.value = "";
197
- placeholderOption.disabled = true;
198
195
  placeholderOption.textContent = "Select...";
199
196
  placeholderOption.style.color = "#9ca3af";
200
197
  selectElement.appendChild(placeholderOption);
@@ -14,4 +14,5 @@ export const ReportIssueContext = {
14
14
  triageBaseUrl: "https://app.sailfishqa.com",
15
15
  deactivateIsolation: () => { },
16
16
  integrationData: null,
17
+ showEngTicketFieldsDefault: false,
17
18
  };
@@ -3,10 +3,17 @@ export function renderCustomMultiSelect(fieldId, fieldName, options, selectedVal
3
3
  const requiredMark = isRequired
4
4
  ? '<span style="color:#ef4444;">*</span>'
5
5
  : "";
6
- const selectedLabels = options
6
+ const selectedChips = options
7
7
  .filter((opt) => selectedValues.includes(opt.id || opt.value || opt.name || opt))
8
- .map((opt) => opt.name || opt.value || opt)
9
- .join(", ");
8
+ .map((opt) => {
9
+ const value = opt.id || opt.value || opt.name || opt;
10
+ const label = opt.name || opt.value || opt;
11
+ return `<span class="sf-multiselect-chip" data-value="${value}" style="display:inline-flex; align-items:center; gap:4px; background:#e5e7eb; color:#374151; padding:2px 8px; border-radius:16px; font-size:13px; white-space:nowrap;">
12
+ ${label}
13
+ <span class="sf-multiselect-chip-remove" data-value="${value}" style="cursor:pointer; display:inline-flex; align-items:center; font-size:16px; line-height:1; color:#6b7280;" onclick="event.stopPropagation();">&times;</span>
14
+ </span>`;
15
+ })
16
+ .join("");
10
17
  const optionsHTML = options
11
18
  .map((opt) => {
12
19
  const value = opt.id || opt.value || opt.name || opt;
@@ -25,9 +32,9 @@ export function renderCustomMultiSelect(fieldId, fieldName, options, selectedVal
25
32
  ${fieldName} ${requiredMark}
26
33
  </label>
27
34
  <div class="sf-custom-multiselect" id="${fieldId}-container" data-field-id="${fieldId}" style="position:relative;">
28
- <div class="sf-multiselect-input" style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; background-color:white; cursor:pointer; min-height:38px; display:flex; align-items:center;" onclick="document.getElementById('${fieldId}-dropdown').style.display = document.getElementById('${fieldId}-dropdown').style.display === 'none' ? 'block' : 'none';">
29
- <span style="color:${selectedLabels ? "#000" : "#9ca3af"}; flex:1;">${selectedLabels || "Select..."}</span>
30
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="margin-left:8px;">
35
+ <div class="sf-multiselect-input" style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; background-color:white; cursor:pointer; min-height:38px; display:flex; align-items:center; flex-wrap:wrap; gap:4px;" onclick="document.getElementById('${fieldId}-dropdown').style.display = document.getElementById('${fieldId}-dropdown').style.display === 'none' ? 'block' : 'none';">
36
+ <span class="sf-multiselect-chips" style="display:flex; flex-wrap:wrap; gap:4px; flex:1;">${selectedChips || `<span style="color:#9ca3af;">Select...</span>`}</span>
37
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="margin-left:8px; flex-shrink:0;">
31
38
  <path d="M4 6L8 10L12 6" stroke="#64748B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
32
39
  </svg>
33
40
  </div>
@@ -183,7 +190,7 @@ export function renderDynamicField(field, fieldValue, users = []) {
183
190
  style="${baseInputStyle} appearance:none; cursor:pointer; background-color: white; ${!hasValue ? "color: #9ca3af;" : ""}"
184
191
  ${isRequired ? "required" : ""}
185
192
  >
186
- <option value="" disabled ${!hasValue ? "selected" : ""} style="color: #9ca3af;">Select ${fieldName.toLowerCase()}...</option>
193
+ <option value="" ${!hasValue ? "selected" : ""} style="color: #9ca3af;">Select ${fieldName.toLowerCase()}...</option>
187
194
  ${options}
188
195
  </select>
189
196
  </div>
@@ -214,7 +221,7 @@ export function renderDynamicField(field, fieldValue, users = []) {
214
221
  style="${baseInputStyle} appearance:none; cursor:pointer; background-color: white; ${!hasValue ? "color: #9ca3af;" : ""}"
215
222
  ${isRequired ? "required" : ""}
216
223
  >
217
- <option value="" disabled ${!hasValue ? "selected" : ""} style="color: #9ca3af;">Select ${fieldName.toLowerCase()}...</option>
224
+ <option value="" ${!hasValue ? "selected" : ""} style="color: #9ca3af;">Select ${fieldName.toLowerCase()}...</option>
218
225
  ${options}
219
226
  </select>
220
227
  </div>
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import { getUrlAndStoredUuids, initializeConsolePlugin, initializeDomContentEven
12
12
  import { HAS_DOCUMENT, HAS_LOCAL_STORAGE, HAS_SESSION_STORAGE, HAS_WINDOW, } from "./runtimeEnv";
13
13
  import { ensureSessionListeners, getOrSetSessionId } from "./session";
14
14
  import { withAppUrlMetadata } from "./utils";
15
+ import { onNavigationChange } from "./websocket";
15
16
  import { clearStaleFuncSpanState, getFuncSpanHeader, isFunctionSpanTrackingEnabled, restoreFuncSpanState, sendEvent, sendMessage, } from "./websocket";
16
17
  import { yieldToMain } from "./scheduler";
17
18
  const DEBUG = readDebugFlag(); // A wrapper around fetch that suppresses connection refused errors
@@ -146,7 +147,7 @@ function trackDomainChangesOnce() {
146
147
  if (forceSend || currentDomain !== lastDomain) {
147
148
  lastDomain = currentDomain;
148
149
  const pageVisitUUID = uuidv4();
149
- const prevPageVisitUUID = sessionStorage.getItem("pageVisitUUID");
150
+ const prevPageVisitUUID = sessionStorage.getItem("pageVisitUUID") ?? "";
150
151
  sessionStorage.setItem("pageVisitUUID", pageVisitUUID);
151
152
  sessionStorage.setItem("prevPageVisitUUID", prevPageVisitUUID);
152
153
  invalidateUrlCache();
@@ -163,6 +164,8 @@ function trackDomainChangesOnce() {
163
164
  }
164
165
  };
165
166
  const debouncedCheck = debounce(() => checkDomainChange(), 500);
167
+ // Fire immediately on pushState/replaceState/popstate — don't wait for interval
168
+ onNavigationChange(() => checkDomainChange());
166
169
  // Force the first check to send the initial URL on load
167
170
  checkDomainChange(true);
168
171
  // Start a single interval and remember it
@@ -1044,13 +1047,20 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
1044
1047
  captureStreamPrefixKb,
1045
1048
  captureStreamTimeoutMs,
1046
1049
  };
1047
- // 1a. XHR interceptor
1050
+ // Seed pageVisitUUID in sessionStorage so the fetch/XHR interceptors can
1051
+ // build a complete X-Sf3-Rid header on the very first request.
1052
+ // trackDomainChangesOnce() sets this later (after WS connect), but that's
1053
+ // too late — requests fire before the first await completes.
1054
+ if (!sessionStorage.getItem("pageVisitUUID")) {
1055
+ sessionStorage.setItem("pageVisitUUID", uuidv4());
1056
+ invalidateUrlCache();
1057
+ }
1058
+ // 1a. XHR + Fetch interceptors (must both run synchronously before any yield,
1059
+ // so fire-and-forget callers patch fetch before frameworks capture it)
1048
1060
  if (!g.xhrPatched) {
1049
1061
  setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo, bodyCaptureConfig);
1050
1062
  g.xhrPatched = true;
1051
1063
  }
1052
- await yieldToMain();
1053
- // 1b. Fetch interceptor
1054
1064
  if (!g.fetchPatched) {
1055
1065
  setupFetchInterceptor(domainsToNotPropagateHeaderTo, bodyCaptureConfig);
1056
1066
  g.fetchPatched = true;
@@ -1215,6 +1225,7 @@ export const initRecorder = async (options) => {
1215
1225
  shortcuts: options.reportIssueShortcuts,
1216
1226
  customBaseUrl: options.customBaseUrl,
1217
1227
  integrationData,
1228
+ showEngTicketFieldsInReportIssueModalDefault: options.showEngTicketFieldsInReportIssueModalDefault,
1218
1229
  });
1219
1230
  g.issueReportingInit = true;
1220
1231
  }
package/dist/recorder.cjs CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const e = require("./chunks/index-BlPJWz9y.js");
4
- exports.DEFAULT_CAPTURE_SETTINGS = e.DEFAULT_CAPTURE_SETTINGS, exports.DEFAULT_CONSOLE_RECORDING_SETTINGS = e.DEFAULT_CONSOLE_RECORDING_SETTINGS, exports.STORAGE_VERSION = e.STORAGE_VERSION, exports.addOrUpdateMetadata = e.addOrUpdateMetadata, exports.buildBatches = e.buildBatches, exports.clearStaleFuncSpanState = e.clearStaleFuncSpanState, exports.createTriageAndIssueFromRecorder = e.createTriageAndIssueFromRecorder, exports.createTriageFromRecorder = e.createTriageFromRecorder, exports.disableFunctionSpanTracking = e.disableFunctionSpanTracking, exports.enableFunctionSpanTracking = e.enableFunctionSpanTracking, exports.ensureHrefCache = e.ensureHrefCache, exports.eventSize = e.eventSize, exports.fetchAndSendIp = e.fetchAndSendIp, exports.fetchCaptureSettings = e.fetchCaptureSettings, exports.fetchEngineeringTicketPlatformIntegrations = e.fetchEngineeringTicketPlatformIntegrations, exports.fetchFunctionSpanTrackingEnabled = e.fetchFunctionSpanTrackingEnabled, exports.flushBufferedEvents = e.flushBufferedEvents, exports.getCachedHref = e.getCachedHref, exports.getCachedHrefNoQuery = e.getCachedHrefNoQuery, exports.getFuncSpanHeader = e.getFuncSpanHeader, exports.getOrSetSessionId = e.getOrSetSessionId, exports.getUrlAndStoredUuids = e.getUrlAndStoredUuids, exports.identify = e.identify, exports.initRecorder = e.initRecorder, exports.initializeConsolePlugin = e.initializeConsolePlugin, exports.initializeDomContentEvents = e.initializeDomContentEvents, exports.initializeFunctionSpanTrackingFromApi = e.initializeFunctionSpanTrackingFromApi, exports.initializeRecording = e.initializeRecording, exports.initializeWebSocket = e.initializeWebSocket, exports.invalidateUrlCache = e.invalidateUrlCache, exports.isFunctionSpanTrackingEnabled = e.isFunctionSpanTrackingEnabled, exports.matchUrlWithWildcard = e.matchUrlWithWildcard, Object.defineProperty(exports, "nowTimestamp", { enumerable: true, get: () => e.nowTimestamp }), exports.openReportIssueModal = e.openReportIssueModal, exports.restoreFuncSpanState = e.restoreFuncSpanState, exports.sendDomainsToNotPropagateHeaderTo = e.sendDomainsToNotPropagateHeaderTo, exports.sendEvent = e.sendEvent, exports.sendGraphQLRequest = e.sendGraphQLRequest, exports.sendMessage = e.sendMessage, exports.startRecording = e.startRecording, exports.startRecordingSession = e.startRecordingSession, exports.trackingEvent = e.trackingEvent, exports.withAppUrlMetadata = e.withAppUrlMetadata;
3
+ const e = require("./chunks/index-B8gnDRst.js");
4
+ exports.DEFAULT_CAPTURE_SETTINGS = e.DEFAULT_CAPTURE_SETTINGS, exports.DEFAULT_CONSOLE_RECORDING_SETTINGS = e.DEFAULT_CONSOLE_RECORDING_SETTINGS, exports.STORAGE_VERSION = e.STORAGE_VERSION, exports.addOrUpdateMetadata = e.addOrUpdateMetadata, exports.buildBatches = e.buildBatches, exports.clearStaleFuncSpanState = e.clearStaleFuncSpanState, exports.createTriageAndIssueFromRecorder = e.createTriageAndIssueFromRecorder, exports.createTriageFromRecorder = e.createTriageFromRecorder, exports.disableFunctionSpanTracking = e.disableFunctionSpanTracking, exports.enableFunctionSpanTracking = e.enableFunctionSpanTracking, exports.ensureHrefCache = e.ensureHrefCache, exports.eventSize = e.eventSize, exports.fetchAndSendIp = e.fetchAndSendIp, exports.fetchCaptureSettings = e.fetchCaptureSettings, exports.fetchEngineeringTicketPlatformIntegrations = e.fetchEngineeringTicketPlatformIntegrations, exports.fetchFunctionSpanTrackingEnabled = e.fetchFunctionSpanTrackingEnabled, exports.flushBufferedEvents = e.flushBufferedEvents, exports.getCachedHref = e.getCachedHref, exports.getCachedHrefNoQuery = e.getCachedHrefNoQuery, exports.getFuncSpanHeader = e.getFuncSpanHeader, exports.getOrSetSessionId = e.getOrSetSessionId, exports.getUrlAndStoredUuids = e.getUrlAndStoredUuids, exports.identify = e.identify, exports.initRecorder = e.initRecorder, exports.initializeConsolePlugin = e.initializeConsolePlugin, exports.initializeDomContentEvents = e.initializeDomContentEvents, exports.initializeFunctionSpanTrackingFromApi = e.initializeFunctionSpanTrackingFromApi, exports.initializeRecording = e.initializeRecording, exports.initializeWebSocket = e.initializeWebSocket, exports.invalidateUrlCache = e.invalidateUrlCache, exports.isFunctionSpanTrackingEnabled = e.isFunctionSpanTrackingEnabled, exports.matchUrlWithWildcard = e.matchUrlWithWildcard, Object.defineProperty(exports, "nowTimestamp", { enumerable: true, get: () => e.nowTimestamp }), exports.onNavigationChange = e.onNavigationChange, exports.openReportIssueModal = e.openReportIssueModal, exports.restoreFuncSpanState = e.restoreFuncSpanState, exports.sendDomainsToNotPropagateHeaderTo = e.sendDomainsToNotPropagateHeaderTo, exports.sendEvent = e.sendEvent, exports.sendGraphQLRequest = e.sendGraphQLRequest, exports.sendMessage = e.sendMessage, exports.startRecording = e.startRecording, exports.startRecordingSession = e.startRecordingSession, exports.trackingEvent = e.trackingEvent, exports.withAppUrlMetadata = e.withAppUrlMetadata;
package/dist/recorder.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D, a, S, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, z, A, B, C, E, F, G, H, I, J, K, L, M, N, O, P, Q } from "./chunks/index-soHaKXF4.js";
1
+ import { D, a, S, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, z, A, B, C, E, F, G, H, I, J, K, L, M, N, O, P, Q, R } from "./chunks/index-w2Ted1rp.js";
2
2
  export {
3
3
  D as DEFAULT_CAPTURE_SETTINGS,
4
4
  a as DEFAULT_CONSOLE_RECORDING_SETTINGS,
@@ -33,14 +33,15 @@ export {
33
33
  E as isFunctionSpanTrackingEnabled,
34
34
  F as matchUrlWithWildcard,
35
35
  G as nowTimestamp,
36
- H as openReportIssueModal,
37
- I as restoreFuncSpanState,
38
- J as sendDomainsToNotPropagateHeaderTo,
39
- K as sendEvent,
40
- L as sendGraphQLRequest,
41
- M as sendMessage,
42
- N as startRecording,
43
- O as startRecordingSession,
44
- P as trackingEvent,
45
- Q as withAppUrlMetadata
36
+ H as onNavigationChange,
37
+ I as openReportIssueModal,
38
+ J as restoreFuncSpanState,
39
+ K as sendDomainsToNotPropagateHeaderTo,
40
+ L as sendEvent,
41
+ M as sendGraphQLRequest,
42
+ N as sendMessage,
43
+ O as startRecording,
44
+ P as startRecordingSession,
45
+ Q as trackingEvent,
46
+ R as withAppUrlMetadata
46
47
  };
Binary file
Binary file
@@ -27,6 +27,7 @@ export declare const ReportIssueContext: {
27
27
  triageBaseUrl: string;
28
28
  deactivateIsolation: () => void;
29
29
  integrationData: any;
30
+ showEngTicketFieldsDefault: boolean;
30
31
  };
31
32
  export declare function setupIssueReporting(options: {
32
33
  apiKey: string;
@@ -35,6 +36,9 @@ export declare function setupIssueReporting(options: {
35
36
  shortcuts?: Partial<ShortcutsConfig>;
36
37
  customBaseUrl?: string;
37
38
  integrationData?: any;
39
+ showEngTicketFieldsInReportIssueModalDefault?: boolean;
40
+ }): void;
41
+ export declare function openReportIssueModal(options?: {
42
+ showEngTicketFields?: boolean;
38
43
  }): void;
39
- export declare function openReportIssueModal(): void;
40
44
  export {};
@@ -36,4 +36,5 @@ export declare const ReportIssueContext: {
36
36
  triageBaseUrl: string;
37
37
  deactivateIsolation: () => void;
38
38
  integrationData: any;
39
+ showEngTicketFieldsDefault: boolean;
39
40
  };
@@ -73,6 +73,7 @@ export declare const initRecorder: (options: {
73
73
  */
74
74
  chunkSnapshot?: boolean;
75
75
  useWsWorker?: boolean;
76
+ showEngTicketFieldsInReportIssueModalDefault?: boolean;
76
77
  }) => Promise<void>;
77
78
  export * from "./graphql";
78
79
  export { openReportIssueModal } from "./inAppReportIssueModal";
@@ -3,6 +3,9 @@ export interface WsHandle {
3
3
  readyState: number;
4
4
  close: () => void;
5
5
  }
6
+ type NavigationCallback = () => void;
7
+ /** Register a callback to be invoked immediately on pushState/replaceState/popstate/hashchange. */
8
+ export declare function onNavigationChange(cb: NavigationCallback): void;
6
9
  /** Install navigation listeners to keep the cached URL fresh. */
7
10
  export declare function ensureHrefCache(): void;
8
11
  /** Get cached href (falls back to live read if cache not initialized). */
@@ -47,3 +50,4 @@ export declare function getFuncSpanHeader(): {
47
50
  name: string;
48
51
  value: string;
49
52
  } | null;
53
+ export {};
package/dist/websocket.js CHANGED
@@ -102,9 +102,20 @@ let flushIntervalId = null;
102
102
  let _cachedHref = "";
103
103
  let _cachedHrefNoQuery = "";
104
104
  let _hrefListenersInstalled = false;
105
+ const _navigationCallbacks = [];
106
+ /** Register a callback to be invoked immediately on pushState/replaceState/popstate/hashchange. */
107
+ export function onNavigationChange(cb) {
108
+ _navigationCallbacks.push(cb);
109
+ }
105
110
  function _updateHrefCache() {
106
111
  _cachedHref = window.location.href;
107
112
  _cachedHrefNoQuery = window.location.origin + window.location.pathname;
113
+ for (const cb of _navigationCallbacks) {
114
+ try {
115
+ cb();
116
+ }
117
+ catch (_) { /* never break navigation */ }
118
+ }
108
119
  }
109
120
  /** Install navigation listeners to keep the cached URL fresh. */
110
121
  export function ensureHrefCache() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailfish-ai/recorder",
3
- "version": "1.10.7",
3
+ "version": "1.10.9",
4
4
  "publishPublicly": true,
5
5
  "type": "module",
6
6
  "main": "dist/recorder.cjs",
Binary file
Binary file
Binary file
Binary file