@dynamicu/chromedebug-mcp 2.6.6 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +1 -1
  3. package/chrome-extension/activation-manager.js +18 -4
  4. package/chrome-extension/background.js +1044 -552
  5. package/chrome-extension/browser-recording-manager.js +256 -0
  6. package/chrome-extension/chrome-debug-logger.js +168 -0
  7. package/chrome-extension/console-interception-library.js +430 -0
  8. package/chrome-extension/content.css +16 -16
  9. package/chrome-extension/content.js +617 -215
  10. package/chrome-extension/data-buffer.js +206 -17
  11. package/chrome-extension/extension-config.js +1 -1
  12. package/chrome-extension/frame-capture.js +52 -15
  13. package/chrome-extension/license-helper.js +26 -0
  14. package/chrome-extension/manifest.free.json +3 -6
  15. package/chrome-extension/options.js +1 -1
  16. package/chrome-extension/popup.html +315 -181
  17. package/chrome-extension/popup.js +673 -526
  18. package/chrome-extension/pro/enhanced-capture.js +406 -0
  19. package/chrome-extension/pro/frame-editor.html +410 -0
  20. package/chrome-extension/pro/frame-editor.js +1496 -0
  21. package/chrome-extension/pro/function-tracker.js +843 -0
  22. package/chrome-extension/pro/jszip.min.js +13 -0
  23. package/config/chromedebug-config.json +101 -0
  24. package/dist/chromedebug-extension-free.zip +0 -0
  25. package/package.json +3 -1
  26. package/scripts/package-pro-extension.js +1 -1
  27. package/scripts/webpack.config.free.cjs +11 -8
  28. package/scripts/webpack.config.pro.cjs +5 -0
  29. package/src/chrome-controller.js +7 -7
  30. package/src/cli.js +2 -2
  31. package/src/database.js +61 -9
  32. package/src/http-server.js +3 -2
  33. package/src/index.js +9 -6
  34. package/src/mcp/server.js +2 -2
  35. package/src/services/process-manager.js +10 -6
  36. package/src/services/process-tracker.js +10 -5
  37. package/src/services/profile-manager.js +17 -2
  38. package/src/validation/schemas.js +36 -6
  39. package/src/index-direct.js +0 -157
  40. package/src/index-modular.js +0 -219
  41. package/src/index-monolithic-backup.js +0 -2230
  42. package/src/legacy/chrome-controller-old.js +0 -1406
  43. package/src/legacy/index-express.js +0 -625
  44. package/src/legacy/index-old.js +0 -977
  45. package/src/legacy/routes.js +0 -260
  46. package/src/legacy/shared-storage.js +0 -101
@@ -297,7 +297,7 @@ function getSelector(element) {
297
297
  }
298
298
 
299
299
  if (element.className && typeof element.className === 'string') {
300
- const classes = element.className.trim().split(/\s+/).filter(c => !c.startsWith('chrome-pilot'));
300
+ const classes = element.className.trim().split(/\s+/).filter(c => !c.startsWith('chrome-debug'));
301
301
  if (classes.length > 0) {
302
302
  return `${element.tagName.toLowerCase()}.${classes.join('.')}`;
303
303
  }
@@ -398,25 +398,25 @@ function getComponentInfo(element) {
398
398
  // Idempotent protection for cleanup function
399
399
  let cleanupExecuted = false;
400
400
 
401
- function cleanup() {
401
+ async function cleanup() {
402
402
  // Idempotent protection - only run cleanup once
403
403
  if (cleanupExecuted) {
404
404
  console.log('[ChromeDebug MCP] Cleanup already executed, skipping');
405
405
  return;
406
406
  }
407
-
407
+
408
408
  cleanupExecuted = true;
409
409
  console.log('[ChromeDebug MCP] Executing cleanup...');
410
-
410
+
411
411
  // Clean up recording indicators and event listeners
412
412
  try {
413
413
  // Legacy recording indicator cleanup removed - now handled by ScreenCaptureVisualFeedback
414
-
414
+
415
415
  // Stop workflow recording if active
416
416
  if (typeof isWorkflowRecording !== 'undefined' && isWorkflowRecording) {
417
417
  console.log('[ChromeDebug MCP] Stopping workflow recording during cleanup');
418
418
  if (typeof stopWorkflowRecording === 'function') {
419
- stopWorkflowRecording();
419
+ await stopWorkflowRecording();
420
420
  }
421
421
  }
422
422
 
@@ -460,7 +460,7 @@ if (isExtensionValid()) {
460
460
  // Show the recording ID in the floating UI
461
461
  console.log('Recording complete with ID:', request.recordingId);
462
462
  if (recordingOverlay) {
463
- const statusDiv = document.getElementById('chrome-pilot-recording-status');
463
+ const statusDiv = document.getElementById('chrome-debug-recording-status');
464
464
  if (statusDiv && request.recordingId) {
465
465
  statusDiv.innerHTML = `<strong style="color: #4CAF50;">Recording saved!</strong><br>ID: <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-family: monospace;">${request.recordingId}</code>`;
466
466
 
@@ -494,12 +494,34 @@ if (isExtensionValid()) {
494
494
  sendResponse({ success: true });
495
495
  } else if (request.action === 'startWorkflowRecording') {
496
496
  console.log('[ChromeDebug MCP] Starting workflow recording with settings:', request.screenshotSettings);
497
+ // Store tabId for use when stopping recording from mini-menu
498
+ workflowTabId = request.tabId;
497
499
  startWorkflowRecording(request.screenshotSettings);
498
500
  sendResponse({ success: true });
499
- } else if (request.action === 'stopWorkflowRecording') {
500
- console.log('[ChromeDebug MCP] Stopping workflow recording');
501
- const workflow = stopWorkflowRecording();
502
- sendResponse({ success: true, workflow: workflow });
501
+ } else if (request.action === 'getWorkflowData') {
502
+ // Called by background script to retrieve workflow data
503
+ // This happens when popup stops the recording (not mini-menu)
504
+ console.log('[ChromeDebug MCP] Getting workflow data');
505
+ stopWorkflowRecording().then(async workflow => {
506
+ sendResponse({ success: true, workflow: workflow });
507
+
508
+ // CRITICAL FIX: Show completion UI in mini-menu after data is sent
509
+ // When popup stops recording, the mini-menu needs to show the completion state
510
+ // Get the workflowId from storage (set by background.js when recording started)
511
+ try {
512
+ const storedData = await chrome.storage.local.get(['currentWorkflowId']);
513
+ const workflowId = storedData.currentWorkflowId || 'Recording saved';
514
+ const actionCount = workflow.actions?.length || 0;
515
+ console.log('[ChromeDebug MCP] Showing completion UI with workflowId:', workflowId, 'actionCount:', actionCount);
516
+ showWorkflowRecordingComplete(workflowId, actionCount);
517
+ } catch (e) {
518
+ console.log('[ChromeDebug MCP] Could not show completion UI:', e);
519
+ }
520
+ }).catch(error => {
521
+ console.error('[ChromeDebug MCP] Error getting workflow data:', error);
522
+ sendResponse({ success: false, error: error.message });
523
+ });
524
+ return true; // Keep channel open for async response
503
525
  } else if (request.action === 'createRestorePoint') {
504
526
  // Create restore point for current workflow
505
527
  createRestorePoint(request.actionIndex || workflowActions.length).then(restorePointId => {
@@ -629,7 +651,7 @@ async function playWorkflowRecording(workflow, actions) {
629
651
  try {
630
652
  // Show playback indicator
631
653
  const playbackIndicator = document.createElement('div');
632
- playbackIndicator.id = 'chrome-pilot-playback-indicator';
654
+ playbackIndicator.id = 'chrome-debug-playback-indicator';
633
655
  playbackIndicator.style.cssText = `
634
656
  position: fixed;
635
657
  top: 20px;
@@ -852,27 +874,73 @@ let isWorkflowRecording = false;
852
874
  let workflowActions = [];
853
875
  let workflowRecordingIndicator = null;
854
876
  let workflowScreenshotSettings = null;
877
+ let workflowTabId = null; // Store tabId for use when stopping recording
878
+ let lastScreenshotTime = 0;
879
+ let pendingScreenshots = []; // Track pending screenshot captures
880
+
881
+ // Async screenshot capture without blocking click recording
882
+ function captureScreenshotAsync(action, actionIndex) {
883
+ // Create the async capture promise
884
+ const capturePromise = (async () => {
885
+ try {
886
+ // Chrome API rate limit: ~1-2 screenshots per second
887
+ // We enforce 600ms minimum between screenshots to stay under the limit
888
+ const MIN_SCREENSHOT_INTERVAL = 600; // milliseconds
889
+ const now = Date.now();
890
+ const timeSinceLastScreenshot = now - lastScreenshotTime;
891
+
892
+ if (timeSinceLastScreenshot < MIN_SCREENSHOT_INTERVAL) {
893
+ // Wait until we can safely capture
894
+ const waitTime = MIN_SCREENSHOT_INTERVAL - timeSinceLastScreenshot;
895
+ await new Promise(resolve => setTimeout(resolve, waitTime));
896
+ }
897
+
898
+ const screenshotData = await captureWorkflowScreenshot();
899
+
900
+ if (screenshotData) {
901
+ // Add screenshot to the action object (it's already in the array)
902
+ action.screenshot_data = screenshotData;
903
+ lastScreenshotTime = Date.now(); // Update timestamp after successful capture
904
+ }
905
+ } catch (error) {
906
+ console.error(`Error capturing screenshot for action ${actionIndex}:`, error);
907
+ }
908
+ })();
909
+
910
+ // Track this promise in pendingScreenshots
911
+ pendingScreenshots.push(capturePromise);
912
+
913
+ // Remove from pending when done (success or failure)
914
+ capturePromise.finally(() => {
915
+ const index = pendingScreenshots.indexOf(capturePromise);
916
+ if (index > -1) {
917
+ pendingScreenshots.splice(index, 1);
918
+ }
919
+ });
920
+
921
+ return capturePromise;
922
+ }
855
923
 
856
924
  // Helper function to capture screenshot
857
925
  async function captureWorkflowScreenshot() {
858
926
  if (!workflowScreenshotSettings || !workflowScreenshotSettings.enabled) {
859
927
  return null;
860
928
  }
861
-
929
+
862
930
  try {
863
931
  // Send message to background script to capture screenshot
864
932
  const response = await chrome.runtime.sendMessage({
865
933
  action: 'captureWorkflowScreenshot',
866
934
  settings: workflowScreenshotSettings
867
935
  });
868
-
936
+
869
937
  if (response && response.success) {
870
938
  return response.screenshotData;
871
939
  }
872
940
  } catch (error) {
873
- console.error('[ChromeDebug MCP] Error capturing screenshot:', error);
941
+ console.error('Error in captureWorkflowScreenshot:', error);
874
942
  }
875
-
943
+
876
944
  return null;
877
945
  }
878
946
 
@@ -886,7 +954,7 @@ function getUniqueSelector(element) {
886
954
  // Try unique class combination
887
955
  if (element.className && typeof element.className === 'string') {
888
956
  const classes = element.className.trim().split(/\s+/)
889
- .filter(c => !c.startsWith('chrome-pilot'))
957
+ .filter(c => !c.startsWith('chrome-debug'))
890
958
  .filter(c => !c.includes(':')) // Filter out pseudo-class artifacts
891
959
  .map(c => {
892
960
  // Escape special characters in class names
@@ -935,18 +1003,17 @@ function getXPath(element) {
935
1003
 
936
1004
  // Create workflow recording indicator
937
1005
  function createWorkflowRecordingIndicator() {
938
- if (workflowRecordingIndicator) return;
939
-
1006
+ // CRITICAL FIX: If old indicator exists (showing completion), remove it completely
1007
+ // This ensures the old popup closes immediately before showing the new recording indicator
1008
+ if (workflowRecordingIndicator && workflowRecordingIndicator.parentNode) {
1009
+ workflowRecordingIndicator.parentNode.removeChild(workflowRecordingIndicator);
1010
+ workflowRecordingIndicator = null;
1011
+ }
1012
+
1013
+ // Always create fresh element for new recording
940
1014
  workflowRecordingIndicator = document.createElement('div');
941
- workflowRecordingIndicator.id = 'chrome-pilot-workflow-indicator';
942
- workflowRecordingIndicator.innerHTML = `
943
- <div style="display: flex; align-items: center; gap: 8px;">
944
- <span style="color: #9c27b0; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
945
- <span>Recording Workflow...</span>
946
- <span id="workflow-action-count" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
947
- </div>
948
- `;
949
-
1015
+ workflowRecordingIndicator.id = 'chrome-debug-workflow-indicator';
1016
+
950
1017
  Object.assign(workflowRecordingIndicator.style, {
951
1018
  position: 'fixed',
952
1019
  bottom: '20px',
@@ -962,11 +1029,77 @@ function createWorkflowRecordingIndicator() {
962
1029
  boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
963
1030
  backdropFilter: 'blur(10px)',
964
1031
  WebkitBackdropFilter: 'blur(10px)',
965
- pointerEvents: 'none',
1032
+ pointerEvents: 'auto',
966
1033
  userSelect: 'none'
967
1034
  });
968
-
1035
+
1036
+ workflowRecordingIndicator.innerHTML = `
1037
+ <div style="display: flex; align-items: center; gap: 12px;">
1038
+ <div style="display: flex; align-items: center; gap: 8px; flex: 1;">
1039
+ <span style="color: #9c27b0; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
1040
+ <span>Recording Workflow...</span>
1041
+ <span id="workflow-action-count" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
1042
+ </div>
1043
+ <button id="workflow-stop-btn" style="background: #f44336; color: white; border: none; padding: 6px 16px; border-radius: 15px; font-size: 12px; font-weight: 500; cursor: pointer; font-family: inherit;">Stop</button>
1044
+ <button id="workflow-close-btn" style="background: transparent; color: white; border: none; padding: 4px 8px; font-size: 16px; cursor: pointer; font-family: inherit; opacity: 0.7;" title="Close notification">×</button>
1045
+ </div>
1046
+ `;
1047
+
969
1048
  document.body.appendChild(workflowRecordingIndicator);
1049
+
1050
+ // Add stop button click handler
1051
+ const stopBtn = document.getElementById('workflow-stop-btn');
1052
+ const closeBtn = document.getElementById('workflow-close-btn');
1053
+
1054
+ if (stopBtn) {
1055
+ stopBtn.addEventListener('click', async (e) => {
1056
+ e.stopPropagation();
1057
+ stopBtn.disabled = true;
1058
+ stopBtn.textContent = 'Stopping...';
1059
+
1060
+ try {
1061
+ // Stop workflow recording via background script (which handles saving automatically)
1062
+ // Include tabId explicitly - sender.tab.id can be undefined in some edge cases
1063
+ chrome.runtime.sendMessage({
1064
+ action: 'stopWorkflowRecording',
1065
+ tabId: workflowTabId
1066
+ }, (response) => {
1067
+ if (chrome.runtime.lastError) {
1068
+ console.error('[WorkflowRecording] Error:', chrome.runtime.lastError.message);
1069
+ stopBtn.textContent = 'Error';
1070
+ stopBtn.style.background = '#666';
1071
+ return;
1072
+ }
1073
+
1074
+ if (response && response.success) {
1075
+ console.log('[WorkflowRecording] Stop response:', response);
1076
+ const workflowId = response.workflow?.sessionId || 'unknown';
1077
+ const actionCount = response.workflow?.actions?.length || 0;
1078
+ console.log('[WorkflowRecording] Extracted workflowId:', workflowId, 'actionCount:', actionCount);
1079
+ // Show completion with action buttons
1080
+ showWorkflowRecordingComplete(workflowId, actionCount);
1081
+ } else {
1082
+ console.error('[WorkflowRecording] Failed to save:', response?.error);
1083
+ // Don't remove indicator, just show error
1084
+ stopBtn.textContent = 'Error';
1085
+ stopBtn.style.background = '#666';
1086
+ }
1087
+ });
1088
+ } catch (error) {
1089
+ console.error('[WorkflowRecording] Error stopping:', error);
1090
+ // Don't remove indicator, just show error
1091
+ stopBtn.textContent = 'Error';
1092
+ stopBtn.style.background = '#666';
1093
+ }
1094
+ });
1095
+ }
1096
+
1097
+ if (closeBtn) {
1098
+ closeBtn.addEventListener('click', (e) => {
1099
+ e.stopPropagation();
1100
+ removeWorkflowRecordingIndicator();
1101
+ });
1102
+ }
970
1103
  }
971
1104
 
972
1105
  function removeWorkflowRecordingIndicator() {
@@ -983,6 +1116,97 @@ function updateWorkflowActionCount() {
983
1116
  }
984
1117
  }
985
1118
 
1119
+ // Show workflow recording completion with action buttons
1120
+ function showWorkflowRecordingComplete(workflowId, actionCount) {
1121
+ if (!workflowRecordingIndicator) return;
1122
+
1123
+ workflowRecordingIndicator.innerHTML = `
1124
+ <div style="display: flex; flex-direction: column; gap: 10px; position: relative; padding-right: 30px;">
1125
+ <button class="wf-close-complete" style="position: absolute; top: -8px; right: -8px; background: transparent; color: white; border: none; padding: 4px 8px; font-size: 20px; cursor: pointer; font-family: inherit; opacity: 0.7; line-height: 1;" title="Close notification">×</button>
1126
+
1127
+ <div style="display: flex; align-items: center; gap: 8px;">
1128
+ <span style="color: #4CAF50; font-size: 16px;">✓</span>
1129
+ <span style="font-weight: 600; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 400px;" title="${workflowId}">${workflowId}</span>
1130
+ </div>
1131
+
1132
+ <div style="font-size: 11px; color: rgba(255,255,255,0.7); margin-top: -5px;">
1133
+ ${actionCount} actions • ${new Date().toLocaleString()}
1134
+ </div>
1135
+
1136
+ <div style="display: flex; gap: 6px; flex-wrap: wrap;">
1137
+ <button class="wf-copy-id" data-id="${workflowId}" style="background: #2196F3; color: white; border: none; padding: 6px 12px; border-radius: 12px; font-size: 11px; cursor: pointer; font-family: inherit; font-weight: 500;">Copy ID</button>
1138
+ <button class="wf-copy-prompt" data-id="${workflowId}" style="background: #4CAF50; color: white; border: none; padding: 6px 12px; border-radius: 12px; font-size: 11px; cursor: pointer; font-family: inherit; font-weight: 500;">Copy Prompt</button>
1139
+ <button class="wf-view" data-id="${workflowId}" style="background: #9C27B0; color: white; border: none; padding: 6px 12px; border-radius: 12px; font-size: 11px; cursor: pointer; font-family: inherit; font-weight: 500;">View</button>
1140
+ <button class="wf-delete" data-id="${workflowId}" style="background: #f44336; color: white; border: none; padding: 6px 12px; border-radius: 12px; font-size: 11px; cursor: pointer; font-family: inherit; font-weight: 500;">Delete</button>
1141
+ </div>
1142
+ </div>
1143
+ `;
1144
+
1145
+ // Add event listeners for action buttons
1146
+ const copyIdBtn = workflowRecordingIndicator.querySelector('.wf-copy-id');
1147
+ const copyPromptBtn = workflowRecordingIndicator.querySelector('.wf-copy-prompt');
1148
+ const viewBtn = workflowRecordingIndicator.querySelector('.wf-view');
1149
+ const deleteBtn = workflowRecordingIndicator.querySelector('.wf-delete');
1150
+ const closeBtn = workflowRecordingIndicator.querySelector('.wf-close-complete');
1151
+
1152
+ if (copyIdBtn) {
1153
+ copyIdBtn.addEventListener('click', async () => {
1154
+ try {
1155
+ await navigator.clipboard.writeText(workflowId);
1156
+ copyIdBtn.textContent = 'Copied!';
1157
+ setTimeout(() => copyIdBtn.textContent = 'Copy ID', 2000);
1158
+ } catch (error) {
1159
+ console.error('[WorkflowRecording] Failed to copy ID:', error);
1160
+ }
1161
+ });
1162
+ }
1163
+
1164
+ if (copyPromptBtn) {
1165
+ copyPromptBtn.addEventListener('click', async () => {
1166
+ const prompt = `Please use the get_workflow_recording function in Chrome Debug to load the workflow recording "${workflowId}".`;
1167
+ try {
1168
+ await navigator.clipboard.writeText(prompt);
1169
+ copyPromptBtn.textContent = 'Copied!';
1170
+ setTimeout(() => copyPromptBtn.textContent = 'Copy Prompt', 2000);
1171
+ } catch (error) {
1172
+ console.error('[WorkflowRecording] Failed to copy prompt:', error);
1173
+ }
1174
+ });
1175
+ }
1176
+
1177
+ if (viewBtn) {
1178
+ viewBtn.addEventListener('click', () => {
1179
+ // Ask background to open frame editor (content scripts can't create tabs)
1180
+ chrome.runtime.sendMessage({
1181
+ action: 'openFrameEditor',
1182
+ sessionId: workflowId,
1183
+ type: 'workflow'
1184
+ });
1185
+ });
1186
+ }
1187
+
1188
+ if (deleteBtn) {
1189
+ deleteBtn.addEventListener('click', async () => {
1190
+ if (confirm('Delete this workflow recording?')) {
1191
+ chrome.runtime.sendMessage({
1192
+ action: 'deleteWorkflowRecording',
1193
+ workflowId: workflowId
1194
+ }, (response) => {
1195
+ if (response && response.success) {
1196
+ removeWorkflowRecordingIndicator();
1197
+ }
1198
+ });
1199
+ }
1200
+ });
1201
+ }
1202
+
1203
+ if (closeBtn) {
1204
+ closeBtn.addEventListener('click', () => {
1205
+ removeWorkflowRecordingIndicator();
1206
+ });
1207
+ }
1208
+ }
1209
+
986
1210
  // Enhanced click tracking helper functions - Pro Feature
987
1211
  // Try to import enhanced capture module (only exists in pro build)
988
1212
  let enhancedCaptureModule = null;
@@ -1024,16 +1248,13 @@ async function shouldEnhanceCapture() {
1024
1248
  }
1025
1249
 
1026
1250
  // Record click action
1027
- async function recordClick(event) {
1251
+ function recordClick(event) {
1028
1252
  if (!isWorkflowRecording) {
1029
- console.log('[ChromePilot] Click ignored - not recording');
1030
1253
  return;
1031
1254
  }
1032
-
1033
- console.log('[ChromePilot] Recording click event');
1034
-
1255
+
1035
1256
  let element = event.target;
1036
-
1257
+
1037
1258
  // If clicking on an SVG element or its children, find the parent button/link
1038
1259
  if (element.tagName === 'svg' || element.tagName === 'path' || element.tagName === 'g' || element.tagName === 'circle' || element.tagName === 'rect') {
1039
1260
  const clickableParent = element.closest('button, a, [role="button"], [onclick]');
@@ -1042,7 +1263,7 @@ async function recordClick(event) {
1042
1263
  element = clickableParent;
1043
1264
  }
1044
1265
  }
1045
-
1266
+
1046
1267
  const action = {
1047
1268
  type: 'click',
1048
1269
  selector: getUniqueSelector(element),
@@ -1057,92 +1278,89 @@ async function recordClick(event) {
1057
1278
  action.text = element.textContent.trim();
1058
1279
  }
1059
1280
 
1060
- // Enhanced click tracking data (conditional based on user setting)
1061
- const enhanceCapture = await shouldEnhanceCapture();
1062
- if (enhanceCapture) {
1063
- try {
1064
- console.log('[ChromePilot] Capturing enhanced click data...');
1281
+ // Record the action immediately (synchronously) - don't wait for anything
1282
+ workflowActions.push(action);
1283
+ const actionIndex = workflowActions.length - 1;
1065
1284
 
1066
- // Capture enhanced element data (only include fields with meaningful values)
1067
- const capturedHTML = captureElementHTML(element);
1068
- if (capturedHTML) {
1069
- action.element_html = capturedHTML;
1070
- }
1285
+ // Enhanced click tracking data (async, non-blocking)
1286
+ // Check setting and apply enhanced data asynchronously without blocking
1287
+ shouldEnhanceCapture().then(enhanceCapture => {
1288
+ if (enhanceCapture) {
1289
+ try {
1290
+ // Capture enhanced element data (only include fields with meaningful values)
1291
+ const capturedHTML = captureElementHTML(element);
1292
+ if (capturedHTML) {
1293
+ action.element_html = capturedHTML;
1294
+ }
1071
1295
 
1072
- const componentData = getEnhancedComponentInfo(element);
1073
- if (componentData) {
1074
- try {
1075
- JSON.stringify(componentData); // Test if serializable
1076
- action.component_data = componentData;
1077
- } catch (e) {
1078
- console.error('[ChromePilot] Component data cannot be serialized:', e.message);
1079
- action.component_data = { error: 'Serialization failed', type: typeof componentData };
1296
+ const componentData = getEnhancedComponentInfo(element);
1297
+ if (componentData) {
1298
+ try {
1299
+ JSON.stringify(componentData); // Test if serializable
1300
+ action.component_data = componentData;
1301
+ } catch (e) {
1302
+ console.error('[ChromePilot] Component data cannot be serialized:', e.message);
1303
+ action.component_data = { error: 'Serialization failed', type: typeof componentData };
1304
+ }
1080
1305
  }
1081
- }
1082
1306
 
1083
- const eventHandlers = extractEventHandlers(element);
1084
- if (eventHandlers) {
1085
- try {
1086
- JSON.stringify(eventHandlers); // Test if serializable
1087
- action.event_handlers = eventHandlers;
1088
- } catch (e) {
1089
- console.error('[ChromePilot] Event handlers cannot be serialized:', e.message);
1090
- action.event_handlers = { error: 'Serialization failed', type: typeof eventHandlers };
1307
+ const eventHandlers = extractEventHandlers(element);
1308
+ if (eventHandlers) {
1309
+ try {
1310
+ JSON.stringify(eventHandlers); // Test if serializable
1311
+ action.event_handlers = eventHandlers;
1312
+ } catch (e) {
1313
+ console.error('[ChromePilot] Event handlers cannot be serialized:', e.message);
1314
+ action.event_handlers = { error: 'Serialization failed', type: typeof eventHandlers };
1315
+ }
1091
1316
  }
1092
- }
1093
1317
 
1094
- const elementState = captureElementState(element);
1095
- if (elementState) {
1096
- try {
1097
- JSON.stringify(elementState); // Test if serializable
1098
- action.element_state = elementState;
1099
- } catch (e) {
1100
- console.error('[ChromePilot] Element state cannot be serialized:', e.message);
1101
- action.element_state = { error: 'Serialization failed', type: typeof elementState };
1318
+ const elementState = captureElementState(element);
1319
+ if (elementState) {
1320
+ try {
1321
+ JSON.stringify(elementState); // Test if serializable
1322
+ action.element_state = elementState;
1323
+ } catch (e) {
1324
+ console.error('[ChromePilot] Element state cannot be serialized:', e.message);
1325
+ action.element_state = { error: 'Serialization failed', type: typeof elementState };
1326
+ }
1102
1327
  }
1103
- }
1104
1328
 
1105
- const perfMetrics = getPerformanceMetrics();
1106
- if (perfMetrics) {
1107
- try {
1108
- JSON.stringify(perfMetrics); // Test if serializable
1109
- action.performance_metrics = perfMetrics;
1110
- } catch (e) {
1111
- console.error('[ChromePilot] Performance metrics cannot be serialized:', e.message);
1112
- action.performance_metrics = { error: 'Serialization failed', type: typeof perfMetrics };
1329
+ const perfMetrics = getPerformanceMetrics();
1330
+ if (perfMetrics) {
1331
+ try {
1332
+ JSON.stringify(perfMetrics); // Test if serializable
1333
+ action.performance_metrics = perfMetrics;
1334
+ } catch (e) {
1335
+ console.error('[ChromePilot] Performance metrics cannot be serialized:', e.message);
1336
+ action.performance_metrics = { error: 'Serialization failed', type: typeof perfMetrics };
1337
+ }
1113
1338
  }
1114
- }
1115
1339
 
1116
- console.log('[ChromePilot] Enhanced click data captured successfully');
1117
- } catch (error) {
1118
- console.warn('[ChromePilot] Error capturing enhanced click data:', error);
1119
- // Continue with basic click recording if enhanced capture fails
1340
+ // Enhanced click data captured successfully - no need to log
1341
+ } catch (error) {
1342
+ console.warn('[ChromePilot] Error capturing enhanced click data:', error);
1343
+ // Continue with basic click recording if enhanced capture fails
1344
+ }
1120
1345
  }
1121
- } else {
1122
- console.log('[ChromePilot] Enhanced click capture disabled - using basic capture only');
1123
- }
1124
-
1125
- // Capture screenshot if enabled
1346
+ // Enhanced capture setting checked - no need to log state
1347
+ }).catch(error => {
1348
+ console.error('[ChromePilot] Error checking enhanced capture setting:', error);
1349
+ });
1350
+
1351
+ // Capture screenshot asynchronously without blocking
1126
1352
  if (workflowScreenshotSettings && workflowScreenshotSettings.enabled) {
1127
- // Give a moment for any UI changes to settle
1128
- setTimeout(async () => {
1129
- const screenshotData = await captureWorkflowScreenshot();
1130
- if (screenshotData) {
1131
- action.screenshot_data = screenshotData;
1132
- }
1133
- }, 100);
1353
+ // Don't await - let screenshot capture happen in background
1354
+ captureScreenshotAsync(action, actionIndex);
1134
1355
  }
1135
-
1136
- workflowActions.push(action);
1137
- console.log('[ChromePilot] Action recorded, total actions:', workflowActions.length);
1138
1356
  updateWorkflowActionCount();
1139
-
1357
+
1140
1358
  // Visual feedback
1141
1359
  flashElement(element);
1142
1360
  }
1143
1361
 
1144
1362
  // Record input changes
1145
- async function recordInput(event) {
1363
+ function recordInput(event) {
1146
1364
  if (!isWorkflowRecording) return;
1147
1365
 
1148
1366
  const element = event.target;
@@ -1163,20 +1381,13 @@ async function recordInput(event) {
1163
1381
  if (element.placeholder) {
1164
1382
  action.placeholder = element.placeholder;
1165
1383
  }
1166
-
1167
- // Capture screenshot if enabled
1168
- if (workflowScreenshotSettings && workflowScreenshotSettings.enabled) {
1169
- setTimeout(async () => {
1170
- const screenshotData = await captureWorkflowScreenshot();
1171
- if (screenshotData) {
1172
- action.screenshot_data = screenshotData;
1173
- }
1174
- }, 100);
1175
- }
1176
-
1384
+
1385
+ // NOTE: Screenshots are only captured for clicks, not input events
1386
+ // This prevents multiple screenshot attempts for a single user action
1387
+
1177
1388
  workflowActions.push(action);
1178
1389
  updateWorkflowActionCount();
1179
-
1390
+
1180
1391
  // Visual feedback
1181
1392
  flashElement(element);
1182
1393
  }
@@ -1256,6 +1467,8 @@ function startWorkflowRecording(screenshotSettings) {
1256
1467
  isWorkflowRecording = true;
1257
1468
  workflowActions = [];
1258
1469
  workflowScreenshotSettings = screenshotSettings;
1470
+ lastScreenshotTime = 0; // Reset rate limit timer
1471
+ pendingScreenshots = []; // Clear any pending screenshots from previous recording
1259
1472
  createWorkflowRecordingIndicator();
1260
1473
 
1261
1474
  console.log('[ChromePilot] Workflow recording started with settings:', screenshotSettings);
@@ -1289,8 +1502,9 @@ function startWorkflowRecording(screenshotSettings) {
1289
1502
  timestamp: event.timestamp,
1290
1503
  stack: event.call_stack || null
1291
1504
  });
1292
-
1293
- console.log('[ChromePilot] Function trace captured for workflow:', event.function_name);
1505
+
1506
+ // REMOVED: console.log that logged EVERY function execution (thousands per second)
1507
+ // Function traces are already stored in workflowActions array - no need to log each one
1294
1508
  }
1295
1509
  });
1296
1510
  };
@@ -1299,7 +1513,8 @@ function startWorkflowRecording(screenshotSettings) {
1299
1513
  window.ChromePilotTracker._setRecordingStatus(true);
1300
1514
  console.log('[ChromePilot] Connected workflow recording to FunctionTracker');
1301
1515
  } else {
1302
- console.warn('[ChromePilot] FunctionTracker not available for workflow recording');
1516
+ // FunctionTracker is a PRO feature - use debug level to avoid confusing FREE users
1517
+ console.debug('[ChromePilot] FunctionTracker not available (PRO feature)');
1303
1518
  }
1304
1519
 
1305
1520
  // Add event listeners for recording
@@ -1326,11 +1541,22 @@ function startWorkflowRecording(screenshotSettings) {
1326
1541
  }
1327
1542
 
1328
1543
  // Stop workflow recording
1329
- function stopWorkflowRecording() {
1544
+ async function stopWorkflowRecording() {
1330
1545
  isWorkflowRecording = false;
1331
- removeWorkflowRecordingIndicator();
1332
-
1546
+ workflowTabId = null; // Reset tabId when recording stops
1547
+ // DON'T remove indicator here - let the completion screen handle it
1548
+ // removeWorkflowRecordingIndicator();
1549
+
1333
1550
  console.log(`[ChromePilot] Stopping recording. Captured ${workflowActions.length} actions`);
1551
+
1552
+ // Wait for all pending screenshots to complete
1553
+ if (pendingScreenshots.length > 0) {
1554
+ console.log(`[SCREENSHOT-DEBUG] Waiting for ${pendingScreenshots.length} pending screenshots to complete...`);
1555
+ await Promise.all(pendingScreenshots);
1556
+ console.log('[SCREENSHOT-DEBUG] All pending screenshots completed!');
1557
+ } else {
1558
+ console.log('[SCREENSHOT-DEBUG] No pending screenshots to wait for');
1559
+ }
1334
1560
 
1335
1561
  // Disconnect from FunctionTracker and disable recording
1336
1562
  if (window.ChromePilotTracker && window.ChromePilotTracker._functionTracker) {
@@ -1387,6 +1613,22 @@ function stopWorkflowRecording() {
1387
1613
  functionTraces = [...functionTraces, ...embeddedTraces];
1388
1614
  }
1389
1615
 
1616
+ // Log screenshot data before returning
1617
+ console.log('[SCREENSHOT-DEBUG] stopWorkflowRecording - Preparing to return workflow');
1618
+ console.log('[SCREENSHOT-DEBUG] stopWorkflowRecording - Total actions:', workflowActions.length);
1619
+
1620
+ const actionsWithScreenshots = workflowActions.filter(a => a.screenshot_data);
1621
+ console.log('[SCREENSHOT-DEBUG] stopWorkflowRecording - Actions WITH screenshot_data:', actionsWithScreenshots.length);
1622
+
1623
+ workflowActions.forEach((action, index) => {
1624
+ console.log(`[SCREENSHOT-DEBUG] stopWorkflowRecording - Action ${index}:`, {
1625
+ type: action.type,
1626
+ hasScreenshotData: !!action.screenshot_data,
1627
+ screenshotDataLength: action.screenshot_data?.length,
1628
+ screenshotPreview: action.screenshot_data?.substring(0, 50)
1629
+ });
1630
+ });
1631
+
1390
1632
  // Return the recorded workflow with proper functionTraces field
1391
1633
  return {
1392
1634
  actions: workflowActions,
@@ -1732,8 +1974,6 @@ async function recordScreenClick(event) {
1732
1974
  const enhanceCapture = await shouldEnhanceCapture();
1733
1975
  if (enhanceCapture) {
1734
1976
  try {
1735
- console.log('[ChromePilot] Capturing enhanced screen click data...');
1736
-
1737
1977
  // Capture enhanced element data (only include fields with meaningful values)
1738
1978
  const capturedHTML = captureElementHTML(element);
1739
1979
  if (capturedHTML) {
@@ -1784,14 +2024,13 @@ async function recordScreenClick(event) {
1784
2024
  }
1785
2025
  }
1786
2026
 
1787
- console.log('[ChromePilot] Enhanced screen click data captured successfully');
2027
+ // Enhanced screen click data captured successfully - no need to log
1788
2028
  } catch (error) {
1789
2029
  console.warn('[ChromePilot] Error capturing enhanced screen click data:', error);
1790
2030
  // Continue with basic click recording if enhanced capture fails
1791
2031
  }
1792
- } else {
1793
- console.log('[ChromePilot] Enhanced screen click capture disabled - using basic capture only');
1794
2032
  }
2033
+ // Enhanced capture setting checked - no need to log state
1795
2034
 
1796
2035
  sendScreenInteraction(interaction);
1797
2036
 
@@ -2087,7 +2326,7 @@ function showRecordingInterface() {
2087
2326
 
2088
2327
  // Create recording overlay
2089
2328
  recordingOverlay = document.createElement('div');
2090
- recordingOverlay.id = 'chrome-pilot-recording-overlay';
2329
+ recordingOverlay.id = 'chrome-debug-recording-overlay';
2091
2330
  recordingOverlay.style.cssText = `
2092
2331
  position: fixed;
2093
2332
  bottom: 20px;
@@ -2105,13 +2344,13 @@ function showRecordingInterface() {
2105
2344
  recordingOverlay.innerHTML = `
2106
2345
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
2107
2346
  <h3 style="margin: 0; font-size: 16px; color: #333;">Screen Recording</h3>
2108
- <button id="chrome-pilot-close-recording" style="background: none; border: none; font-size: 20px; cursor: pointer; color: #666;">&times;</button>
2347
+ <button id="chrome-debug-close-recording" style="background: none; border: none; font-size: 20px; cursor: pointer; color: #666;">&times;</button>
2109
2348
  </div>
2110
2349
  <p style="margin: 0 0 15px 0; font-size: 13px; color: #666;">
2111
2350
  Recording this tab with console logs
2112
2351
  </p>
2113
- <div id="chrome-pilot-recording-status" style="text-align: center; margin-bottom: 15px; font-size: 14px; color: #666;"></div>
2114
- <button id="chrome-pilot-record-btn" style="
2352
+ <div id="chrome-debug-recording-status" style="text-align: center; margin-bottom: 15px; font-size: 14px; color: #666;"></div>
2353
+ <button id="chrome-debug-record-btn" style="
2115
2354
  width: 100%;
2116
2355
  padding: 10px;
2117
2356
  background: #f44336;
@@ -2122,13 +2361,13 @@ function showRecordingInterface() {
2122
2361
  font-size: 14px;
2123
2362
  font-weight: 500;
2124
2363
  ">Start Recording</button>
2125
- <div id="chrome-pilot-recording-result" style="margin-top: 15px; display: none;"></div>
2364
+ <div id="chrome-debug-recording-result" style="margin-top: 15px; display: none;"></div>
2126
2365
  `;
2127
2366
 
2128
2367
  document.body.appendChild(recordingOverlay);
2129
2368
 
2130
2369
  // Add event listeners
2131
- document.getElementById('chrome-pilot-close-recording').addEventListener('click', () => {
2370
+ document.getElementById('chrome-debug-close-recording').addEventListener('click', () => {
2132
2371
  if (isRecording) {
2133
2372
  if (confirm('Stop recording and close?')) {
2134
2373
  stopRecording();
@@ -2141,7 +2380,7 @@ function showRecordingInterface() {
2141
2380
  }
2142
2381
  });
2143
2382
 
2144
- document.getElementById('chrome-pilot-record-btn').addEventListener('click', () => {
2383
+ document.getElementById('chrome-debug-record-btn').addEventListener('click', () => {
2145
2384
  if (!isRecording) {
2146
2385
  startRecording();
2147
2386
  } else {
@@ -2152,8 +2391,8 @@ function showRecordingInterface() {
2152
2391
 
2153
2392
  // Start recording from content script
2154
2393
  function startRecording() {
2155
- const recordBtn = document.getElementById('chrome-pilot-record-btn');
2156
- const statusDiv = document.getElementById('chrome-pilot-recording-status');
2394
+ const recordBtn = document.getElementById('chrome-debug-record-btn');
2395
+ const statusDiv = document.getElementById('chrome-debug-recording-status');
2157
2396
 
2158
2397
  recordBtn.disabled = true;
2159
2398
  recordBtn.textContent = 'Starting...';
@@ -2200,9 +2439,9 @@ function startRecording() {
2200
2439
 
2201
2440
  // Stop recording
2202
2441
  function stopRecording() {
2203
- const recordBtn = document.getElementById('chrome-pilot-record-btn');
2204
- const statusDiv = document.getElementById('chrome-pilot-recording-status');
2205
- const resultDiv = document.getElementById('chrome-pilot-recording-result');
2442
+ const recordBtn = document.getElementById('chrome-debug-record-btn');
2443
+ const statusDiv = document.getElementById('chrome-debug-recording-status');
2444
+ const resultDiv = document.getElementById('chrome-debug-recording-result');
2206
2445
 
2207
2446
  recordBtn.disabled = true;
2208
2447
  recordBtn.textContent = 'Stopping...';
@@ -2664,6 +2903,7 @@ class ScreenCaptureVisualFeedback {
2664
2903
  init() {
2665
2904
  // Listen for screen capture events
2666
2905
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
2906
+ console.log('[ScreenCapture] Received message:', message.type, message);
2667
2907
  switch (message.type) {
2668
2908
  case 'start-screen-capture-tracking':
2669
2909
  this.handleStartScreenCapture(message);
@@ -2677,11 +2917,27 @@ class ScreenCaptureVisualFeedback {
2677
2917
  case 'stop-screen-capture-tracking':
2678
2918
  this.handleStopScreenCapture(message);
2679
2919
  break;
2920
+ case 'recording-complete-show-ui':
2921
+ // Show completion UI when stopped from popup (not from mini menu button)
2922
+ console.log('[ScreenCapture] Processing recording-complete-show-ui, sessionId:', message.sessionId, 'actionCount:', this.actionCount);
2923
+ if (message.sessionId) {
2924
+ console.log('[ScreenCapture] Calling showScreenRecordingComplete with sessionId:', message.sessionId);
2925
+ this.showScreenRecordingComplete(message.sessionId, this.actionCount);
2926
+ } else {
2927
+ console.log('[ScreenCapture] ERROR: No sessionId in recording-complete-show-ui message!');
2928
+ }
2929
+ break;
2930
+ default:
2931
+ // Log unknown message types for debugging
2932
+ if (message.type && message.type.includes('screen-capture') || message.type && message.type.includes('recording')) {
2933
+ console.log('[ScreenCapture] Unknown screen capture message type:', message.type);
2934
+ }
2680
2935
  }
2681
2936
  });
2682
2937
  }
2683
2938
 
2684
2939
  handleStartScreenCapture(message) {
2940
+ console.log('[ScreenCapture] Starting new recording, current indicator exists:', !!this.recordingIndicatorElement);
2685
2941
  this.isRecording = true;
2686
2942
  this.actionCount = 0;
2687
2943
  this.showRecordingIndicator();
@@ -2728,9 +2984,11 @@ class ScreenCaptureVisualFeedback {
2728
2984
 
2729
2985
  handleStopScreenCapture(message) {
2730
2986
  this.isRecording = false;
2731
- this.hideRecordingIndicator();
2987
+ // DON'T hide indicator here - let the completion screen handle it
2988
+ // this.hideRecordingIndicator();
2732
2989
  this.hideActionCounter();
2733
- this.cleanup();
2990
+ // Don't cleanup yet - we want to keep the indicator visible
2991
+ // this.cleanup();
2734
2992
  }
2735
2993
 
2736
2994
  showClickHighlight(x, y) {
@@ -2766,80 +3024,96 @@ class ScreenCaptureVisualFeedback {
2766
3024
  }
2767
3025
 
2768
3026
  showRecordingIndicator() {
2769
- if (!this.recordingIndicatorElement) {
2770
- // Add required CSS styles if not already added
2771
- if (!document.getElementById('screen-capture-styles')) {
2772
- const style = document.createElement('style');
2773
- style.id = 'screen-capture-styles';
2774
- style.textContent = `
2775
- @keyframes pulse {
2776
- 0% { opacity: 1; }
2777
- 50% { opacity: 0.5; }
2778
- 100% { opacity: 1; }
2779
- }
2780
- @keyframes screen-capture-pulse {
2781
- 0% { opacity: 1; transform: scale(1); }
2782
- 50% { opacity: 0.7; transform: scale(1.1); }
2783
- 100% { opacity: 1; transform: scale(1); }
2784
- }
2785
- .screen-capture-click-highlight {
2786
- position: fixed;
2787
- width: 40px;
2788
- height: 40px;
2789
- border: 3px solid #4CAF50;
2790
- border-radius: 50%;
2791
- background: rgba(76, 175, 80, 0.3);
2792
- transform: translate(-50%, -50%);
2793
- pointer-events: none;
2794
- z-index: 2147483646;
2795
- animation: screen-capture-ripple 0.5s ease-out;
3027
+ // CRITICAL FIX: Clear any existing countdown interval FIRST
3028
+ if (this.countdownInterval) {
3029
+ console.log('[ScreenCapture] Clearing existing countdown interval');
3030
+ clearInterval(this.countdownInterval);
3031
+ this.countdownInterval = null;
3032
+ }
3033
+
3034
+ // CRITICAL FIX: If old indicator exists (showing completion), remove it completely
3035
+ // This ensures the old popup closes immediately before showing the new countdown
3036
+ if (this.recordingIndicatorElement && this.recordingIndicatorElement.parentNode) {
3037
+ console.log('[ScreenCapture] Removing old indicator element from DOM');
3038
+ this.recordingIndicatorElement.parentNode.removeChild(this.recordingIndicatorElement);
3039
+ this.recordingIndicatorElement = null;
3040
+ console.log('[ScreenCapture] Old indicator removed successfully');
3041
+ } else {
3042
+ console.log('[ScreenCapture] No old indicator to remove, creating fresh');
3043
+ }
3044
+
3045
+ // Add required CSS styles if not already added
3046
+ if (!document.getElementById('screen-capture-styles')) {
3047
+ const style = document.createElement('style');
3048
+ style.id = 'screen-capture-styles';
3049
+ style.textContent = `
3050
+ @keyframes pulse {
3051
+ 0% { opacity: 1; }
3052
+ 50% { opacity: 0.5; }
3053
+ 100% { opacity: 1; }
3054
+ }
3055
+ @keyframes screen-capture-pulse {
3056
+ 0% { opacity: 1; transform: scale(1); }
3057
+ 50% { opacity: 0.7; transform: scale(1.1); }
3058
+ 100% { opacity: 1; transform: scale(1); }
3059
+ }
3060
+ .screen-capture-click-highlight {
3061
+ position: fixed;
3062
+ width: 40px;
3063
+ height: 40px;
3064
+ border: 3px solid #4CAF50;
3065
+ border-radius: 50%;
3066
+ background: rgba(76, 175, 80, 0.3);
3067
+ transform: translate(-50%, -50%);
3068
+ pointer-events: none;
3069
+ z-index: 2147483646;
3070
+ animation: screen-capture-ripple 0.5s ease-out;
3071
+ }
3072
+ @keyframes screen-capture-ripple {
3073
+ 0% {
3074
+ transform: translate(-50%, -50%) scale(0);
3075
+ opacity: 1;
2796
3076
  }
2797
- @keyframes screen-capture-ripple {
2798
- 0% {
2799
- transform: translate(-50%, -50%) scale(0);
2800
- opacity: 1;
2801
- }
2802
- 100% {
2803
- transform: translate(-50%, -50%) scale(1);
2804
- opacity: 0;
2805
- }
3077
+ 100% {
3078
+ transform: translate(-50%, -50%) scale(1);
3079
+ opacity: 0;
2806
3080
  }
2807
- `;
2808
- document.head.appendChild(style);
2809
- }
2810
-
2811
- this.recordingIndicatorElement = document.createElement('div');
2812
- this.recordingIndicatorElement.className = 'screen-capture-recording-indicator-unified';
2813
-
2814
- // Use inline styles to match workflow recording exactly
2815
- Object.assign(this.recordingIndicatorElement.style, {
2816
- position: 'fixed',
2817
- bottom: '80px', // Keep different from workflow to avoid collision
2818
- left: '20px',
2819
- background: 'rgba(0, 0, 0, 0.9)',
2820
- color: 'white',
2821
- padding: '12px 20px',
2822
- borderRadius: '25px',
2823
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif',
2824
- fontSize: '14px',
2825
- fontWeight: '500',
2826
- zIndex: '2147483647',
2827
- display: 'flex',
2828
- alignItems: 'center',
2829
- gap: '8px',
2830
- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
2831
- backdropFilter: 'blur(10px)',
2832
- WebkitBackdropFilter: 'blur(10px)',
2833
- pointerEvents: 'none',
2834
- userSelect: 'none'
2835
- });
3081
+ }
3082
+ `;
3083
+ document.head.appendChild(style);
3084
+ }
3085
+
3086
+ // Always create fresh element for new recording
3087
+ this.recordingIndicatorElement = document.createElement('div');
3088
+ this.recordingIndicatorElement.className = 'screen-capture-recording-indicator-unified';
3089
+
3090
+ // Use inline styles to match workflow recording exactly
3091
+ Object.assign(this.recordingIndicatorElement.style, {
3092
+ position: 'fixed',
3093
+ bottom: '80px', // Keep different from workflow to avoid collision
3094
+ left: '20px',
3095
+ background: 'rgba(0, 0, 0, 0.9)',
3096
+ color: 'white',
3097
+ padding: '12px 20px',
3098
+ borderRadius: '25px',
3099
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif',
3100
+ fontSize: '14px',
3101
+ fontWeight: '500',
3102
+ zIndex: '2147483647',
3103
+ display: 'flex',
3104
+ alignItems: 'center',
3105
+ gap: '8px',
3106
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
3107
+ backdropFilter: 'blur(10px)',
3108
+ WebkitBackdropFilter: 'blur(10px)',
3109
+ pointerEvents: 'none',
3110
+ userSelect: 'none'
3111
+ });
2836
3112
 
2837
- // Start with countdown
2838
- this.startCountdownSequence();
2839
- document.body.appendChild(this.recordingIndicatorElement);
2840
- }
3113
+ document.body.appendChild(this.recordingIndicatorElement);
2841
3114
 
2842
- this.recordingIndicatorElement.style.display = 'flex';
3115
+ // Start countdown sequence
3116
+ this.startCountdownSequence();
2843
3117
  }
2844
3118
 
2845
3119
  startCountdownSequence() {
@@ -2862,18 +3136,146 @@ class ScreenCaptureVisualFeedback {
2862
3136
  <span class="action-count-badge" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
2863
3137
  `;
2864
3138
  } else {
2865
- // Switch to recording display
3139
+ // Switch to recording display with stop button
3140
+ this.recordingIndicatorElement.style.pointerEvents = 'auto'; // Enable clicking
2866
3141
  this.recordingIndicatorElement.innerHTML = `
2867
- <span style="color: #4CAF50; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
2868
- <span>Recording Screen Capture...</span>
2869
- <span class="action-count-badge" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
3142
+ <div style="display: flex; align-items: center; gap: 12px;">
3143
+ <div style="display: flex; align-items: center; gap: 8px; flex: 1;">
3144
+ <span style="color: #4CAF50; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
3145
+ <span>Recording Screen Capture...</span>
3146
+ <span class="action-count-badge" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
3147
+ </div>
3148
+ <button id="screen-stop-btn" style="background: #f44336; color: white; border: none; padding: 6px 16px; border-radius: 15px; font-size: 12px; font-weight: 500; cursor: pointer; font-family: inherit;">Stop</button>
3149
+ <button id="screen-close-btn" style="background: transparent; color: white; border: none; padding: 4px 8px; font-size: 16px; cursor: pointer; font-family: inherit; opacity: 0.7;" title="Close notification">×</button>
3150
+ </div>
2870
3151
  `;
2871
3152
  clearInterval(this.countdownInterval);
2872
3153
  this.countdownInterval = null;
3154
+
3155
+ // Add stop button click handler
3156
+ const stopBtn = document.getElementById('screen-stop-btn');
3157
+ const closeBtn = document.getElementById('screen-close-btn');
3158
+
3159
+ if (stopBtn) {
3160
+ stopBtn.addEventListener('click', async (e) => {
3161
+ e.stopPropagation();
3162
+ stopBtn.disabled = true;
3163
+ stopBtn.textContent = 'Stopping...';
3164
+
3165
+ // Send message to background to stop recording (correct action name)
3166
+ chrome.runtime.sendMessage({ action: 'stopRecording' }, (response) => {
3167
+ if (response && response.success && response.sessionId) {
3168
+ // Show completion with action buttons
3169
+ this.showScreenRecordingComplete(response.sessionId, this.actionCount);
3170
+ } else {
3171
+ console.error('[ScreenRecording] Failed to stop:', response?.error);
3172
+ // Don't hide indicator, just show error
3173
+ stopBtn.textContent = 'Error';
3174
+ stopBtn.style.background = '#666';
3175
+ }
3176
+ });
3177
+ });
3178
+ }
3179
+
3180
+ if (closeBtn) {
3181
+ closeBtn.addEventListener('click', (e) => {
3182
+ e.stopPropagation();
3183
+ this.hideRecordingIndicator();
3184
+ });
3185
+ }
2873
3186
  }
2874
3187
  }, 1000);
2875
3188
  }
2876
3189
 
3190
+ showScreenRecordingComplete(sessionId, actionCount) {
3191
+ if (!this.recordingIndicatorElement) return;
3192
+
3193
+ this.recordingIndicatorElement.innerHTML = `
3194
+ <div style="display: flex; flex-direction: column; gap: 10px; position: relative; padding-right: 30px;">
3195
+ <button class="sc-close-complete" style="position: absolute; top: -8px; right: -8px; background: transparent; color: white; border: none; padding: 4px 8px; font-size: 20px; cursor: pointer; font-family: inherit; opacity: 0.7; line-height: 1;" title="Close notification">×</button>
3196
+
3197
+ <div style="display: flex; align-items: center; gap: 8px;">
3198
+ <span style="color: #4CAF50; font-size: 16px;">🎬</span>
3199
+ <span style="font-weight: 600; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 400px;" title="${sessionId}">${sessionId} (frames)</span>
3200
+ </div>
3201
+
3202
+ <div style="font-size: 11px; color: rgba(255,255,255,0.7); margin-top: -5px;">
3203
+ ${actionCount} actions • ${new Date().toLocaleString()}
3204
+ </div>
3205
+
3206
+ <div style="display: flex; gap: 6px; flex-wrap: wrap;">
3207
+ <button class="sc-copy-id" data-id="${sessionId}" style="background: #2196F3; color: white; border: none; padding: 6px 12px; border-radius: 12px; font-size: 11px; cursor: pointer; font-family: inherit; font-weight: 500;">Copy ID</button>
3208
+ <button class="sc-copy-prompt" data-id="${sessionId}" style="background: #4CAF50; color: white; border: none; padding: 6px 12px; border-radius: 12px; font-size: 11px; cursor: pointer; font-family: inherit; font-weight: 500;">Copy Prompt</button>
3209
+ <button class="sc-view" data-id="${sessionId}" style="background: #9C27B0; color: white; border: none; padding: 6px 12px; border-radius: 12px; font-size: 11px; cursor: pointer; font-family: inherit; font-weight: 500;">View</button>
3210
+ <button class="sc-delete" data-id="${sessionId}" style="background: #f44336; color: white; border: none; padding: 6px 12px; border-radius: 12px; font-size: 11px; cursor: pointer; font-family: inherit; font-weight: 500;">Delete</button>
3211
+ </div>
3212
+ </div>
3213
+ `;
3214
+
3215
+ // Add event listeners for action buttons
3216
+ const copyIdBtn = this.recordingIndicatorElement.querySelector('.sc-copy-id');
3217
+ const copyPromptBtn = this.recordingIndicatorElement.querySelector('.sc-copy-prompt');
3218
+ const viewBtn = this.recordingIndicatorElement.querySelector('.sc-view');
3219
+ const deleteBtn = this.recordingIndicatorElement.querySelector('.sc-delete');
3220
+ const closeBtn = this.recordingIndicatorElement.querySelector('.sc-close-complete');
3221
+
3222
+ if (copyIdBtn) {
3223
+ copyIdBtn.addEventListener('click', async () => {
3224
+ try {
3225
+ await navigator.clipboard.writeText(sessionId);
3226
+ copyIdBtn.textContent = 'Copied!';
3227
+ setTimeout(() => copyIdBtn.textContent = 'Copy ID', 2000);
3228
+ } catch (error) {
3229
+ console.error('[ScreenRecording] Failed to copy ID:', error);
3230
+ }
3231
+ });
3232
+ }
3233
+
3234
+ if (copyPromptBtn) {
3235
+ copyPromptBtn.addEventListener('click', async () => {
3236
+ const prompt = `Please use the chrome_debug_show_frames function in Chrome Debug to display the screen recording "${sessionId}".`;
3237
+ try {
3238
+ await navigator.clipboard.writeText(prompt);
3239
+ copyPromptBtn.textContent = 'Copied!';
3240
+ setTimeout(() => copyPromptBtn.textContent = 'Copy Prompt', 2000);
3241
+ } catch (error) {
3242
+ console.error('[ScreenRecording] Failed to copy prompt:', error);
3243
+ }
3244
+ });
3245
+ }
3246
+
3247
+ if (viewBtn) {
3248
+ viewBtn.addEventListener('click', () => {
3249
+ // Ask background to open frame editor (content scripts can't create tabs)
3250
+ chrome.runtime.sendMessage({
3251
+ action: 'openFrameEditor',
3252
+ sessionId: sessionId
3253
+ });
3254
+ });
3255
+ }
3256
+
3257
+ if (deleteBtn) {
3258
+ deleteBtn.addEventListener('click', async () => {
3259
+ if (confirm('Delete this screen recording?')) {
3260
+ chrome.runtime.sendMessage({
3261
+ action: 'deleteScreenRecording',
3262
+ sessionId: sessionId
3263
+ }, (response) => {
3264
+ if (response && response.success) {
3265
+ this.hideRecordingIndicator();
3266
+ }
3267
+ });
3268
+ }
3269
+ });
3270
+ }
3271
+
3272
+ if (closeBtn) {
3273
+ closeBtn.addEventListener('click', () => {
3274
+ this.hideRecordingIndicator();
3275
+ });
3276
+ }
3277
+ }
3278
+
2877
3279
  hideRecordingIndicator() {
2878
3280
  // Clear countdown interval if running
2879
3281
  if (this.countdownInterval) {