@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
@@ -2,29 +2,9 @@
2
2
  const EXTENSION_VERSION = '2.0.4-BUILD-20250119';
3
3
  console.log(`[popup.js] Loaded version: ${EXTENSION_VERSION}`);
4
4
 
5
- // Import Firebase license client with graceful fallback
6
- let FirebaseLicenseClient = null;
7
- let LEMONSQUEEZY_CHECKOUT_URL = 'https://chromedebug.com/buy/996773cb-682b-430f-b9e3-9ce2130bd967';
8
- let licenseClient = null;
9
- let currentUserId = null;
10
-
11
- // Load Firebase asynchronously to avoid blocking module execution
12
- (async () => {
13
- try {
14
- const firebaseClientModule = await import('./firebase-client.js');
15
- FirebaseLicenseClient = firebaseClientModule.FirebaseLicenseClient;
16
-
17
- const firebaseConfigModule = await import('./firebase-config.public.js');
18
- LEMONSQUEEZY_CHECKOUT_URL = firebaseConfigModule.LEMONSQUEEZY_CHECKOUT_URL;
19
-
20
- // Initialize license client if Firebase loaded successfully
21
- licenseClient = new FirebaseLicenseClient();
22
- console.log('[popup.js] Firebase license client initialized');
23
- } catch (error) {
24
- console.warn('[popup.js] Firebase not available - license features disabled:', error);
25
- // Extension will work without Firebase, just no license validation
26
- }
27
- })();
5
+ // Chrome Web Store URLs for upgrade flow
6
+ const CHROME_STORE_PRO_URL = 'https://chrome.google.com/webstore/detail/chromedebug-mcp-pro/[PRO_EXTENSION_ID]';
7
+ const IS_PRO_VERSION = chrome.runtime.getManifest().name.includes('PRO');
28
8
 
29
9
  // Global variables for recording functionality
30
10
  let isRecording = false;
@@ -46,6 +26,69 @@ let workflowStartTime = null;
46
26
  // Debounce timer for screenshot quality slider
47
27
  let screenshotQualityDebounceTimer = null;
48
28
 
29
+ // Toast notification function
30
+ function showToast(message, duration = 2000, type = 'info') {
31
+ // Create toast element
32
+ const toast = document.createElement('div');
33
+ toast.className = `toast toast-${type}`;
34
+ toast.textContent = message;
35
+ toast.style.cssText = `
36
+ position: fixed;
37
+ top: 20px;
38
+ right: 20px;
39
+ padding: 12px 20px;
40
+ background: ${type === 'error' ? '#f44336' : type === 'success' ? '#4CAF50' : '#2196F3'};
41
+ color: white;
42
+ border-radius: 4px;
43
+ box-shadow: 0 2px 5px rgba(0,0,0,0.3);
44
+ z-index: 10000;
45
+ font-size: 14px;
46
+ animation: slideInRight 0.3s ease-out;
47
+ `;
48
+
49
+ document.body.appendChild(toast);
50
+
51
+ // Auto remove after duration
52
+ setTimeout(() => {
53
+ toast.style.animation = 'slideOutRight 0.3s ease-in';
54
+ setTimeout(() => toast.remove(), 300);
55
+ }, duration);
56
+ }
57
+
58
+ // Loading overlay helper functions
59
+ function showSectionLoading(sectionId, message = 'Loading...') {
60
+ console.log(`showSectionLoading called for: ${sectionId} with message: ${message}`);
61
+ const section = document.getElementById(sectionId);
62
+ if (!section) {
63
+ console.error(`Section not found: ${sectionId}`);
64
+ return;
65
+ }
66
+ console.log(`Section found:`, section);
67
+
68
+ // Remove any existing overlay first
69
+ hideSectionLoading(sectionId);
70
+
71
+ const overlay = document.createElement('div');
72
+ overlay.className = 'recording-section-overlay';
73
+ overlay.innerHTML = `
74
+ <div class="spinner"></div>
75
+ <div class="loading-text">${message}</div>
76
+ `;
77
+ overlay.setAttribute('data-loading-overlay', 'true');
78
+ section.appendChild(overlay);
79
+ console.log(`Overlay appended to section. Overlay:`, overlay);
80
+ }
81
+
82
+ function hideSectionLoading(sectionId) {
83
+ const section = document.getElementById(sectionId);
84
+ if (!section) return;
85
+
86
+ const overlay = section.querySelector('[data-loading-overlay="true"]');
87
+ if (overlay) {
88
+ overlay.remove();
89
+ }
90
+ }
91
+
49
92
  // Workflow recording functions
50
93
  function updateWorkflowRecordingUI() {
51
94
  const workflowBtn = document.getElementById('workflowRecordBtn');
@@ -319,10 +362,13 @@ async function checkServerStatus() {
319
362
  });
320
363
 
321
364
  clearTimeout(timeoutId);
322
- console.log(`[popup.js] Port ${port} responded: ${response.ok}`);
365
+ // Only log successful connections to reduce console noise
366
+ if (response.ok) {
367
+ console.log(`[popup.js] ✓ Connected to server on port ${port}`);
368
+ }
323
369
  return response.ok;
324
370
  } catch (error) {
325
- console.log(`[popup.js] Port ${port} failed:`, error.message);
371
+ // Silent failure - port discovery is expected to fail for most ports
326
372
  return false;
327
373
  }
328
374
  };
@@ -333,7 +379,6 @@ async function checkServerStatus() {
333
379
  if (await tryPort(cachedPort.lastSuccessfulPort, 500)) {
334
380
  connected = true;
335
381
  connectedPort = cachedPort.lastSuccessfulPort;
336
- console.log(`[popup.js] Connected to cached port ${connectedPort}`);
337
382
  }
338
383
  }
339
384
 
@@ -344,7 +389,6 @@ async function checkServerStatus() {
344
389
  if (await tryPort(port, 500)) {
345
390
  connected = true;
346
391
  connectedPort = port;
347
- console.log(`[popup.js] Connected to common port ${connectedPort}`);
348
392
  break;
349
393
  }
350
394
  }
@@ -356,7 +400,6 @@ async function checkServerStatus() {
356
400
  if (await tryPort(port, 1000)) {
357
401
  connected = true;
358
402
  connectedPort = port;
359
- console.log(`[popup.js] Connected to extended port ${connectedPort}`);
360
403
  break;
361
404
  }
362
405
  }
@@ -368,7 +411,6 @@ async function checkServerStatus() {
368
411
  if (await tryPort(port, 1000)) {
369
412
  connected = true;
370
413
  connectedPort = port;
371
- console.log(`[popup.js] Connected to alternative port ${connectedPort}`);
372
414
  break;
373
415
  }
374
416
  }
@@ -377,18 +419,13 @@ async function checkServerStatus() {
377
419
  // Cache the successful port for faster connection next time
378
420
  if (connected && connectedPort) {
379
421
  await chrome.storage.local.set({ lastSuccessfulPort: connectedPort });
422
+ } else {
423
+ console.log('[popup.js] No server found - browser-only mode active');
380
424
  }
381
425
 
382
426
  if (connected) {
383
427
  statusEl.className = 'server-status connected';
384
- statusTextEl.innerHTML = `
385
- Server connected (port ${connectedPort})
386
- <div style="margin-top: 4px;">
387
- <a href="https://www.npmjs.com/package/@dynamicu/chromedebug-mcp" target="_blank" style="font-size: 10px; color: #666; text-decoration: none;">
388
- 📚 Documentation
389
- </a>
390
- </div>
391
- `;
428
+ statusTextEl.textContent = `Server connected (port ${connectedPort})`;
392
429
  } else {
393
430
  statusEl.className = 'server-status disconnected';
394
431
 
@@ -408,9 +445,6 @@ async function checkServerStatus() {
408
445
  <button id="copyInstallCmd" style="padding: 4px 8px; font-size: 11px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer;">
409
446
  📋 Copy Commands
410
447
  </button>
411
- <a href="https://www.npmjs.com/package/@dynamicu/chromedebug-mcp" target="_blank" style="font-size: 11px; color: #2196F3; text-decoration: none;">
412
- 📚 Full Documentation
413
- </a>
414
448
  </div>
415
449
  `;
416
450
 
@@ -865,7 +899,7 @@ function updateRecordingsDisplay(recordings) {
865
899
  if (type === 'snapshot') {
866
900
  prompt = `Please use the get_frame_screenshot function in Chrome Debug to view the snapshot "${recordingId}"${portText}. This is a single-frame screenshot with console logs captured for debugging.`;
867
901
  } else {
868
- prompt = `Please use the chrome_pilot_show_frames function in Chrome Debug to load the recording "${recordingId}"${portText}.`;
902
+ prompt = `Please use the chrome_debug_show_frames function in Chrome Debug to load the recording "${recordingId}"${portText}.`;
869
903
  }
870
904
 
871
905
  navigator.clipboard.writeText(prompt).then(() => {
@@ -902,7 +936,16 @@ function updateRecordingsDisplay(recordings) {
902
936
  const recordingId = e.target.getAttribute('data-id');
903
937
  const type = e.target.getAttribute('data-type');
904
938
 
905
- // Check if pro version (frame editor) is available
939
+ // First check if server is available (for server recordings)
940
+ const serverStatus = await checkServerStatus();
941
+
942
+ if (!serverStatus.connected) {
943
+ // No server - this is a server recording that requires server to view
944
+ alert('This recording requires Chrome Debug server to view.\n\nStart the server with: chromedebug-mcp-server\n\nOr delete this old recording and create new browser-only recordings with Export functionality.');
945
+ return;
946
+ }
947
+
948
+ // Server available - check if pro version (frame editor) is available
906
949
  try {
907
950
  const frameEditorUrl = type === 'snapshot'
908
951
  ? chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recordingId}&type=snapshot`)
@@ -929,350 +972,71 @@ function updateRecordingsDisplay(recordings) {
929
972
 
930
973
 
931
974
 
932
- // Initialize when DOM is ready
933
- // License UI initialization function
934
- async function initializeLicenseUI() {
935
- console.log('[License] Initializing license UI');
936
-
937
- // Get or create userId
938
- const stored = await chrome.storage.local.get('chromedebug_user_id');
939
- currentUserId = stored.chromedebug_user_id || crypto.randomUUID();
940
- if (!stored.chromedebug_user_id) {
941
- await chrome.storage.local.set({chromedebug_user_id: currentUserId});
942
- console.log('[License] Created new user ID:', currentUserId);
943
- }
944
-
945
- // Check for license activation file and pre-fill license key
946
- try {
947
- const activationFileUrl = chrome.runtime.getURL('license-activation.json');
948
- const response = await fetch(activationFileUrl);
949
- if (response.ok) {
950
- const activationData = await response.json();
951
- if (activationData.license_key) {
952
- const licenseKeyInput = document.getElementById('license-key-input');
953
- if (licenseKeyInput) {
954
- licenseKeyInput.value = activationData.license_key;
955
- console.log('[License UI] Pre-filled license key from activation file');
956
- }
957
- }
975
+ // Initialize tier status display for FREE version
976
+ function initializeTierStatus() {
977
+ if (IS_PRO_VERSION) {
978
+ // Hide license section for PRO version
979
+ const licenseSection = document.getElementById('license-section');
980
+ if (licenseSection) {
981
+ licenseSection.style.display = 'none';
982
+ console.log('[Tier] PRO version detected - hiding license section');
958
983
  }
959
- } catch (error) {
960
- // Activation file doesn't exist or is inaccessible - this is normal for FREE version
961
- console.log('[License UI] No activation file found - normal for FREE version');
962
- }
963
-
964
- // Check license status
965
- const licenseStatus = await licenseClient.getCachedLicenseStatus();
966
- console.log('[License] License status:', licenseStatus);
967
-
968
- // Update workflow recording PRO badge visibility
969
- const workflowProBadge = document.getElementById('workflowProBadge');
970
-
971
- if (licenseStatus.valid && licenseStatus.tier === 'pro') {
972
- // Show pro status
973
- document.getElementById('pro-tier-status').style.display = 'block';
974
- document.getElementById('free-tier-status').style.display = 'none';
975
- // Hide license activation input/button when Pro license is active
976
- document.getElementById('license-activation').style.display = 'none';
977
- // Hide PRO badge for Pro users
978
- if (workflowProBadge) workflowProBadge.style.display = 'none';
979
- console.log('[License] Displaying Pro tier status');
980
984
  } else {
981
- // Show free tier + usage
982
- const usage = await licenseClient.checkUsageLimit(currentUserId);
983
- console.log('[License] Usage check result:', usage);
984
-
985
- // Handle both API response format (currentUsage, dailyLimit) and offline format (count, limit)
986
- document.getElementById('usage-count').textContent = usage.currentUsage ?? usage.count ?? 0;
987
- document.getElementById('usage-limit').textContent = usage.dailyLimit ?? usage.limit ?? 50;
988
- document.getElementById('free-tier-status').style.display = 'block';
989
- document.getElementById('pro-tier-status').style.display = 'none';
990
- // Show license activation input/button for free tier
991
- document.getElementById('license-activation').style.display = 'block';
992
- // Show PRO badge for free users
993
- if (workflowProBadge) workflowProBadge.style.display = 'block';
994
- console.log('[License] Displaying Free tier status');
995
- }
996
- }
997
-
998
- // Inline Activation Manager Functions
999
- /**
1000
- * Show the inline activation manager with slide-down animation
1001
- */
1002
- async function showInlineActivationManager(licenseKey) {
1003
- const inlineManager = document.getElementById('inline-activation-manager');
1004
- const activationsList = document.getElementById('activations-list-inline');
1005
-
1006
- // Show the container
1007
- inlineManager.style.display = 'block';
1008
-
1009
- // Trigger animation
1010
- setTimeout(() => {
1011
- inlineManager.style.maxHeight = '400px';
1012
- }, 10);
1013
-
1014
- // Load activations
1015
- try {
1016
- activationsList.innerHTML = '<div style="padding: 15px; text-align: center; color: #666;">Loading activations...</div>';
1017
-
1018
- // Get current instance ID
1019
- const stored = await chrome.storage.local.get(['chromedebug_instance_id']);
1020
- const currentInstanceId = stored.chromedebug_instance_id;
985
+ // Show FREE tier status (no license validation)
986
+ const freeTierStatus = document.getElementById('free-tier-status');
987
+ const proTierStatus = document.getElementById('pro-tier-status');
988
+ const licenseActivation = document.getElementById('license-activation');
1021
989
 
1022
- // Fetch activations from Firebase
1023
- const data = await licenseClient.listActivations(licenseKey);
1024
- const activations = data.activations || [];
990
+ if (freeTierStatus) freeTierStatus.style.display = 'block';
991
+ if (proTierStatus) proTierStatus.style.display = 'none';
992
+ if (licenseActivation) licenseActivation.style.display = 'none';
1025
993
 
1026
- // Render activations
1027
- renderInlineActivations(activations, currentInstanceId, licenseKey);
994
+ // Fetch and display current usage count
995
+ updateUsageDisplay();
1028
996
 
1029
- } catch (error) {
1030
- console.error('[License] Error loading activations:', error);
1031
- activationsList.innerHTML = `<div style="padding: 15px; text-align: center; color: #f44336;">Failed to load activations: ${error.message}</div>`;
997
+ console.log('[Tier] FREE version - showing usage counter');
1032
998
  }
1033
999
  }
1034
1000
 
1035
1001
  /**
1036
- * Hide the inline activation manager with slide-up animation
1037
- */
1038
- function hideInlineActivationManager() {
1039
- const inlineManager = document.getElementById('inline-activation-manager');
1040
- inlineManager.style.maxHeight = '0';
1041
-
1042
- setTimeout(() => {
1043
- inlineManager.style.display = 'none';
1044
- }, 300);
1045
- }
1046
-
1047
- /**
1048
- * Render activations in the inline manager
1002
+ * Fetch usage from background and update the display
1049
1003
  */
1050
- function renderInlineActivations(activations, currentInstanceId, licenseKey) {
1051
- const container = document.getElementById('activations-list-inline');
1052
- container.innerHTML = '';
1053
-
1054
- if (activations.length === 0) {
1055
- container.innerHTML = '<div style="padding: 15px; text-align: center; color: #666;">No activations found.</div>';
1056
- return;
1057
- }
1058
-
1059
- activations.forEach((activation, index) => {
1060
- const item = document.createElement('div');
1061
- item.style.cssText = 'padding: 12px; border-bottom: 1px solid #e0e0e0; transition: background 0.2s;';
1062
- item.onmouseenter = () => item.style.background = '#f9f9f9';
1063
- item.onmouseleave = () => item.style.background = isCurrentDevice ? '#e8f5e9' : 'white';
1064
-
1065
- // Check if this is the current device
1066
- const isCurrentDevice = activation.name === currentInstanceId || activation.identifier === currentInstanceId;
1067
-
1068
- if (isCurrentDevice) {
1069
- item.style.background = '#e8f5e9';
1070
- item.style.borderLeft = '3px solid #4caf50';
1071
- }
1072
-
1073
- // Format date
1074
- const date = new Date(activation.createdAt);
1075
- const formattedDate = date.toLocaleString('en-US', {
1076
- month: 'short',
1077
- day: 'numeric',
1078
- year: 'numeric',
1079
- hour: '2-digit',
1080
- minute: '2-digit'
1081
- });
1082
-
1083
- // Build device info
1084
- let deviceDisplay = 'Unknown Device';
1085
- if (activation.deviceInfo) {
1086
- deviceDisplay = activation.deviceInfo.deviceName ||
1087
- `${activation.deviceInfo.platform || 'Unknown'} • ${activation.deviceInfo.browser || 'Unknown'}`;
1088
- } else if (activation.name && activation.name !== currentInstanceId) {
1089
- deviceDisplay = activation.name;
1090
- }
1091
-
1092
- item.innerHTML = `
1093
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
1094
- <span style="font-weight: 500; font-size: 13px; color: #333;">Activation ${index + 1}</span>
1095
- ${isCurrentDevice ? '<span style="background: #4caf50; color: white; padding: 2px 6px; border-radius: 10px; font-size: 10px; font-weight: bold;">CURRENT</span>' : ''}
1096
- </div>
1097
- <div style="font-size: 12px; color: #666; margin-bottom: 4px;">
1098
- <strong style="color: #333;">${deviceDisplay}</strong>
1099
- </div>
1100
- <div style="font-size: 11px; color: #999; margin-bottom: 8px;">
1101
- ${formattedDate}
1102
- </div>
1103
- <button
1104
- class="deactivate-btn-inline"
1105
- data-instance-id="${activation.identifier}"
1106
- data-activation-number="${index + 1}"
1107
- data-license-key="${licenseKey}"
1108
- style="padding: 5px 10px; background: ${isCurrentDevice ? '#ccc' : '#f44336'}; color: white; border: none; border-radius: 3px; cursor: ${isCurrentDevice ? 'not-allowed' : 'pointer'}; font-size: 11px; width: 100%;"
1109
- ${isCurrentDevice ? 'disabled' : ''}
1110
- >
1111
- ${isCurrentDevice ? '✓ Current Device' : 'Deactivate'}
1112
- </button>
1113
- `;
1114
-
1115
- // Add click handler for deactivate button
1116
- if (!isCurrentDevice) {
1117
- const deactivateBtn = item.querySelector('.deactivate-btn-inline');
1118
- deactivateBtn.addEventListener('click', () => {
1119
- handleInlineDeactivate(activation.identifier, index + 1, licenseKey);
1120
- });
1121
- }
1122
-
1123
- container.appendChild(item);
1124
- });
1125
- }
1126
-
1127
- /**
1128
- * Handle deactivation of an instance (inline)
1129
- */
1130
- async function handleInlineDeactivate(instanceId, activationNumber, licenseKey) {
1131
- const confirmed = confirm(
1132
- `Are you sure you want to deactivate Activation ${activationNumber}?\n\n` +
1133
- `This will free up an activation slot and automatically activate on this device.`
1134
- );
1135
-
1136
- if (!confirmed) {
1137
- return;
1138
- }
1139
-
1140
- const messageDiv = document.getElementById('license-message');
1141
-
1004
+ async function updateUsageDisplay() {
1142
1005
  try {
1143
- // Disable all deactivate buttons
1144
- const buttons = document.querySelectorAll('.deactivate-btn-inline');
1145
- buttons.forEach(btn => btn.disabled = true);
1146
-
1147
- messageDiv.textContent = 'Deactivating...';
1148
- messageDiv.style.color = '#2196F3';
1149
-
1150
- // Deactivate the instance
1151
- const result = await licenseClient.deactivateInstance(licenseKey, instanceId);
1152
- console.log('[License] Deactivation result:', result);
1153
-
1154
- if (result.deactivated) {
1155
- messageDiv.textContent = 'Deactivated! Activating on this device...';
1156
- messageDiv.style.color = '#4caf50';
1006
+ const response = await chrome.runtime.sendMessage({ action: 'getUsage' });
1157
1007
 
1158
- // Hide inline activation manager
1159
- hideInlineActivationManager();
1160
-
1161
- // Wait a moment, then retry activation
1162
- setTimeout(async () => {
1163
- // Set the license key in the input
1164
- document.getElementById('license-key-input').value = licenseKey;
1008
+ if (response && response.success) {
1009
+ const usageCountEl = document.getElementById('usage-count');
1010
+ const usageLimitEl = document.getElementById('usage-limit');
1165
1011
 
1166
- // Retry activation automatically
1167
- await handleLicenseActivation();
1168
- }, 1000);
1012
+ if (usageCountEl) {
1013
+ usageCountEl.textContent = response.count || 0;
1014
+ }
1015
+ if (usageLimitEl) {
1016
+ usageLimitEl.textContent = response.limit || 5;
1017
+ }
1169
1018
 
1170
- } else {
1171
- throw new Error(result.error || 'Deactivation failed');
1019
+ console.log(`[Usage] Display updated: ${response.count}/${response.limit}`);
1172
1020
  }
1173
-
1174
1021
  } catch (error) {
1175
- console.error('[License] Deactivation error:', error);
1176
- messageDiv.textContent = `Deactivation failed: ${error.message}`;
1177
- messageDiv.style.color = '#f44336';
1178
-
1179
- // Re-enable buttons
1180
- const buttons = document.querySelectorAll('.deactivate-btn-inline:not([disabled])');
1181
- buttons.forEach(btn => btn.disabled = false);
1022
+ console.error('[Usage] Failed to fetch usage:', error);
1182
1023
  }
1183
1024
  }
1184
1025
 
1185
- // License activation handler
1186
- async function handleLicenseActivation() {
1187
- const licenseKey = document.getElementById('license-key-input').value.trim();
1188
- const messageDiv = document.getElementById('license-message');
1189
-
1190
- if (!licenseKey) {
1191
- messageDiv.textContent = 'Please enter a license key';
1192
- messageDiv.style.color = '#f44336';
1193
- return;
1194
- }
1195
-
1196
- messageDiv.textContent = 'Validating...';
1197
- messageDiv.style.color = '#2196F3';
1198
- console.log('[License] Validating license key...');
1199
-
1200
- // Clear any existing instance ID to force fresh activation
1201
- // This ensures we don't try to validate with stale/deleted instance IDs
1202
- await chrome.storage.local.remove(['ls_instance_id']);
1203
- console.log('[License] Cleared existing instance ID for fresh activation');
1204
-
1205
- const result = await licenseClient.validateLicense(licenseKey);
1206
- console.log('[License] Validation result:', result);
1207
- console.log('[License] Error value:', result.error);
1208
- console.log('[License] Error type:', typeof result.error);
1209
-
1210
- if (result.valid) {
1211
- messageDiv.textContent = 'License activated successfully!';
1212
- messageDiv.style.color = '#4caf50';
1213
- document.getElementById('license-key-input').value = '';
1214
- await initializeLicenseUI(); // Refresh UI
1215
- } else {
1216
- // Check if activation limit reached
1217
- console.log('[License] Checking if activation limit reached...');
1218
- console.log('[License] result.error === "ACTIVATION_LIMIT_REACHED":', result.error === 'ACTIVATION_LIMIT_REACHED');
1219
- console.log('[License] result.error includes "activation limit":', result.error && result.error.toLowerCase().includes('activation limit'));
1220
-
1221
- if (result.error === 'ACTIVATION_LIMIT_REACHED' ||
1222
- (result.error && result.error.toLowerCase().includes('activation limit'))) {
1223
- console.log('[License] Activation limit reached, showing inline activation manager');
1224
- messageDiv.textContent = 'Please deactivate an existing activation to continue';
1225
- messageDiv.style.color = '#ff9800';
1226
-
1227
- // Show inline activation manager
1228
- await showInlineActivationManager(licenseKey.trim());
1229
- } else {
1230
- messageDiv.textContent = result.error || 'Invalid license key';
1231
- messageDiv.style.color = '#f44336';
1232
- }
1233
- }
1234
- }
1235
-
1236
- // Upgrade button handler
1026
+ // Upgrade button handler - redirect to Chrome Web Store PRO version
1237
1027
  function handleUpgradeClick() {
1238
- console.log('[License] Opening upgrade page:', LEMONSQUEEZY_CHECKOUT_URL);
1239
- chrome.tabs.create({url: LEMONSQUEEZY_CHECKOUT_URL});
1028
+ console.log('[Upgrade] Opening Chrome Web Store PRO version');
1029
+ chrome.tabs.create({url: CHROME_STORE_PRO_URL});
1240
1030
  }
1241
1031
 
1242
- // Listen for messages from activation manager
1243
- chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1244
- if (message.type === 'RETRY_ACTIVATION' && message.licenseKey) {
1245
- console.log('[License] Retrying activation after deactivation');
1246
-
1247
- // Set the license key in the input
1248
- const licenseKeyInput = document.getElementById('license-key-input');
1249
- if (licenseKeyInput) {
1250
- licenseKeyInput.value = message.licenseKey;
1251
- }
1252
-
1253
- // Retry activation
1254
- handleLicenseActivation().then(() => {
1255
- console.log('[License] Retry activation complete');
1256
- });
1257
- }
1258
- });
1259
-
1260
1032
  document.addEventListener('DOMContentLoaded', () => {
1261
1033
  console.log('DOM loaded, initializing popup');
1262
1034
 
1263
- // Initialize license UI first
1264
- initializeLicenseUI().catch(error => {
1265
- console.error('[License] Failed to initialize license UI:', error);
1266
- });
1035
+ // Initialize tier status display
1036
+ initializeTierStatus();
1267
1037
 
1268
- // License event handlers
1269
- const activateLicenseBtn = document.getElementById('activate-license-btn');
1038
+ // Upgrade button handler
1270
1039
  const upgradeBtn = document.getElementById('upgrade-btn');
1271
-
1272
- if (activateLicenseBtn) {
1273
- activateLicenseBtn.addEventListener('click', handleLicenseActivation);
1274
- }
1275
-
1276
1040
  if (upgradeBtn) {
1277
1041
  upgradeBtn.addEventListener('click', handleUpgradeClick);
1278
1042
  }
@@ -1282,18 +1046,37 @@ document.addEventListener('DOMContentLoaded', () => {
1282
1046
  const serverStatus = await checkServerStatus();
1283
1047
  const recordBtn = document.getElementById('recordBtn');
1284
1048
  const workflowRecordBtn = document.getElementById('workflowRecordBtn');
1285
-
1049
+ const browserOnlyBanner = document.getElementById('browserOnlyBanner');
1050
+ const quotaMeter = document.getElementById('quotaMeter');
1051
+
1286
1052
  if (!serverStatus.connected) {
1287
- // Disable recording buttons when server is not running
1288
- if (recordBtn && !isRecording) {
1289
- recordBtn.style.opacity = '0.6';
1290
- recordBtn.title = 'Server not running - please start Chrome Debug server';
1053
+ // Show browser-only mode UI
1054
+ if (browserOnlyBanner) {
1055
+ browserOnlyBanner.style.display = 'block';
1056
+ }
1057
+ if (quotaMeter) {
1058
+ quotaMeter.style.display = 'block';
1059
+ updateQuotaUI(); // Update quota display
1291
1060
  }
1292
- if (workflowRecordBtn && !isWorkflowRecording) {
1293
- workflowRecordBtn.style.opacity = '0.6';
1294
- workflowRecordBtn.title = 'Server not running - please start Chrome Debug server';
1061
+
1062
+ // Enable recording buttons in browser-only mode
1063
+ if (recordBtn) {
1064
+ recordBtn.style.opacity = '1';
1065
+ recordBtn.title = 'Recording will be stored in browser storage';
1066
+ }
1067
+ if (workflowRecordBtn) {
1068
+ workflowRecordBtn.style.opacity = '1';
1069
+ workflowRecordBtn.title = 'Recording will be stored in browser storage';
1295
1070
  }
1296
1071
  } else {
1072
+ // Hide browser-only mode UI when server is connected
1073
+ if (browserOnlyBanner) {
1074
+ browserOnlyBanner.style.display = 'none';
1075
+ }
1076
+ if (quotaMeter) {
1077
+ quotaMeter.style.display = 'none';
1078
+ }
1079
+
1297
1080
  // Enable recording buttons when server is running
1298
1081
  if (recordBtn) {
1299
1082
  recordBtn.style.opacity = '1';
@@ -1305,11 +1088,127 @@ document.addEventListener('DOMContentLoaded', () => {
1305
1088
  }
1306
1089
  }
1307
1090
  }
1091
+
1092
+ // Update quota UI for browser-only mode
1093
+ async function updateQuotaUI() {
1094
+ try {
1095
+ // Request quota info from background script
1096
+ const response = await chrome.runtime.sendMessage({
1097
+ action: 'getQuotaInfo'
1098
+ });
1099
+
1100
+ if (response && response.quotaInfo) {
1101
+ const { percentage, usageMB, quotaMB, availableMB } = response.quotaInfo;
1102
+
1103
+ const quotaBar = document.getElementById('quotaBar');
1104
+ const quotaText = document.getElementById('quotaText');
1105
+ const quotaAvailable = document.getElementById('quotaAvailable');
1106
+ const quotaUsage = document.getElementById('quotaUsage');
1107
+
1108
+ if (quotaBar && quotaText && quotaAvailable) {
1109
+ const percentageValue = Math.round(percentage * 100);
1110
+ quotaBar.style.width = `${percentageValue}%`;
1111
+
1112
+ // Change color based on usage
1113
+ if (percentage > 0.8) {
1114
+ quotaBar.style.background = '#f44336'; // Red
1115
+ } else if (percentage > 0.6) {
1116
+ quotaBar.style.background = '#ff9800'; // Orange
1117
+ } else {
1118
+ quotaBar.style.background = '#4CAF50'; // Green
1119
+ }
1120
+
1121
+ quotaText.textContent = `${percentageValue}% used`;
1122
+ quotaAvailable.textContent = `${availableMB || 0} MB available`;
1123
+ }
1124
+
1125
+ if (quotaUsage) {
1126
+ quotaUsage.textContent = `${usageMB || 0} MB / ${quotaMB || 0} MB`;
1127
+ }
1128
+ }
1129
+ } catch (error) {
1130
+ console.error('Failed to update quota UI:', error);
1131
+ }
1132
+ }
1308
1133
 
1309
- // Initialize server status check
1310
- updateButtonStatesForServerStatus();
1311
- setInterval(updateButtonStatesForServerStatus, 3000);
1312
-
1134
+ // Server polling control
1135
+ let serverPollingInterval = null;
1136
+
1137
+ function startPolling() {
1138
+ if (serverPollingInterval) {
1139
+ clearInterval(serverPollingInterval);
1140
+ }
1141
+ serverPollingInterval = setInterval(updateButtonStatesForServerStatus, 3000);
1142
+ console.log('Server polling started (every 3 seconds)');
1143
+ }
1144
+
1145
+ function stopPolling() {
1146
+ if (serverPollingInterval) {
1147
+ clearInterval(serverPollingInterval);
1148
+ serverPollingInterval = null;
1149
+ console.log('Server polling stopped');
1150
+ }
1151
+ }
1152
+
1153
+ async function checkServerOnce() {
1154
+ console.log('Running single server check...');
1155
+ await updateButtonStatesForServerStatus();
1156
+ }
1157
+
1158
+ // Initialize polling controls
1159
+ const pollContinuouslyCheckbox = document.getElementById('pollContinuouslyCheckbox');
1160
+ const retryConnectionBtn = document.getElementById('retryConnectionBtn');
1161
+
1162
+ if (pollContinuouslyCheckbox && retryConnectionBtn) {
1163
+ // Load saved polling preference (default: true)
1164
+ chrome.storage.local.get(['pollContinuously'], (result) => {
1165
+ const shouldPoll = result.pollContinuously !== false; // Default to true
1166
+ pollContinuouslyCheckbox.checked = shouldPoll;
1167
+
1168
+ // Show/hide retry button based on preference
1169
+ retryConnectionBtn.style.display = shouldPoll ? 'none' : 'inline-block';
1170
+
1171
+ // Initial server check
1172
+ updateButtonStatesForServerStatus();
1173
+
1174
+ // Start polling if enabled
1175
+ if (shouldPoll) {
1176
+ startPolling();
1177
+ }
1178
+ });
1179
+
1180
+ // Checkbox change handler
1181
+ pollContinuouslyCheckbox.addEventListener('change', () => {
1182
+ const isChecked = pollContinuouslyCheckbox.checked;
1183
+
1184
+ // Save preference
1185
+ chrome.storage.local.set({ pollContinuously: isChecked });
1186
+
1187
+ // Show/hide retry button
1188
+ retryConnectionBtn.style.display = isChecked ? 'none' : 'inline-block';
1189
+
1190
+ // Start or stop polling
1191
+ if (isChecked) {
1192
+ startPolling();
1193
+ } else {
1194
+ stopPolling();
1195
+ }
1196
+
1197
+ console.log('Continuous polling:', isChecked ? 'enabled' : 'disabled');
1198
+ });
1199
+
1200
+ // Retry button click handler
1201
+ retryConnectionBtn.addEventListener('click', () => {
1202
+ showToast('Retrying connection...', 2000, 'info');
1203
+ checkServerOnce();
1204
+ });
1205
+ } else {
1206
+ // Fallback if polling controls not found - use default behavior
1207
+ console.warn('Polling controls not found, using default polling behavior');
1208
+ updateButtonStatesForServerStatus();
1209
+ setInterval(updateButtonStatesForServerStatus, 3000);
1210
+ }
1211
+
1313
1212
  // Initialize site management functionality
1314
1213
  initializeSiteManagement();
1315
1214
 
@@ -1414,9 +1313,13 @@ document.addEventListener('DOMContentLoaded', () => {
1414
1313
  }
1415
1314
 
1416
1315
  });
1417
-
1418
- // Load saved recordings
1316
+
1317
+ // Load saved recordings (legacy)
1419
1318
  loadRecordings();
1319
+
1320
+ // Load screen and workflow recordings with saved preferences
1321
+ loadScreenRecordings();
1322
+ loadWorkflowRecordings();
1420
1323
 
1421
1324
  // Setup recording settings event listeners
1422
1325
  const frameRateSelect = document.getElementById('frameRate');
@@ -1488,19 +1391,26 @@ document.addEventListener('DOMContentLoaded', () => {
1488
1391
  try {
1489
1392
  // Check if server is running before starting recording
1490
1393
  const serverStatus = await checkServerStatus();
1491
- if (!serverStatus.connected) {
1492
- console.error('Server not running');
1394
+ const isBrowserOnlyMode = !serverStatus.connected;
1395
+
1396
+ if (isBrowserOnlyMode) {
1397
+ console.log('Starting recording in browser-only mode');
1398
+ // Browser-only mode - proceed with local storage
1399
+ } else {
1400
+ console.log('Starting recording with server');
1401
+ }
1402
+
1403
+ // Check FREE tier usage limit before starting
1404
+ const usageCheck = await chrome.runtime.sendMessage({ action: 'checkUsageLimit' });
1405
+ if (usageCheck && !usageCheck.allowed) {
1406
+ console.log('[Usage] Daily limit reached:', usageCheck);
1493
1407
  if (recordingStatus) {
1494
- recordingStatus.innerHTML = '<strong style="color: #f44336;">Error: Server not running</strong><br>' +
1495
- '<small>Please start the Chrome Debug server first</small>';
1408
+ recordingStatus.innerHTML = '<strong style="color: #f44336;">Daily limit reached</strong><br>' +
1409
+ `<small>${usageCheck.count}/${usageCheck.limit} recordings used today.<br>Upgrade to Pro for unlimited.</small>`;
1496
1410
  }
1497
- // Clear error message after 5 seconds
1498
- setTimeout(() => {
1499
- if (recordingStatus) recordingStatus.textContent = '';
1500
- }, 5000);
1501
1411
  return;
1502
1412
  }
1503
-
1413
+
1504
1414
  // Get the current active tab
1505
1415
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
1506
1416
  console.log('Current tab:', tab);
@@ -1531,10 +1441,11 @@ document.addEventListener('DOMContentLoaded', () => {
1531
1441
  };
1532
1442
 
1533
1443
  // Send message to background to start recording
1534
- chrome.runtime.sendMessage({
1535
- action: 'startRecording',
1444
+ chrome.runtime.sendMessage({
1445
+ action: 'startRecording',
1536
1446
  tabId: tab.id,
1537
- settings: settings
1447
+ settings: settings,
1448
+ mode: isBrowserOnlyMode ? 'browser-only' : 'server'
1538
1449
  }, (response) => {
1539
1450
  recordBtn.disabled = false;
1540
1451
 
@@ -1574,23 +1485,47 @@ document.addEventListener('DOMContentLoaded', () => {
1574
1485
  recordBtn.textContent = 'Stopping...';
1575
1486
  recordBtn.disabled = true;
1576
1487
 
1577
- chrome.runtime.sendMessage({
1488
+ chrome.runtime.sendMessage({
1578
1489
  action: 'stopRecording'
1579
- }, (response) => {
1490
+ }, async (response) => {
1580
1491
  isStoppingRecording = false;
1581
1492
  recordBtn.disabled = false;
1582
-
1493
+
1583
1494
  if (chrome.runtime.lastError) {
1584
1495
  console.error('Error stopping recording:', chrome.runtime.lastError);
1585
1496
  recordingStatus.textContent = 'Error: ' + chrome.runtime.lastError.message;
1586
1497
  recordBtn.textContent = 'Stop Recording';
1587
1498
  return;
1588
1499
  }
1589
-
1500
+
1590
1501
  if (response && response.success) {
1591
- console.log('Recording stopped successfully');
1502
+ console.log('Recording stopped successfully', response);
1592
1503
  isRecording = false;
1593
1504
  updateRecordingUI();
1505
+
1506
+ console.log('About to show loading overlay for screen-recording-section');
1507
+ // Show loading overlay over screen recording section
1508
+ showSectionLoading('screen-recording-section', 'Saving screen recording...');
1509
+ console.log('Loading overlay should be visible now');
1510
+
1511
+ // Use then/catch instead of async/await since we're in a callback
1512
+ loadScreenRecordings(true)
1513
+ .then(() => {
1514
+ console.log('Screen recordings loaded successfully');
1515
+ // Show success toast
1516
+ const frameCount = response.frameCount || 0;
1517
+ const duration = response.duration || '0s';
1518
+ showToast(`Screen recording saved! ${frameCount} frames (${duration})`, 3000, 'success');
1519
+ })
1520
+ .catch(error => {
1521
+ console.error('Error loading screen recordings:', error);
1522
+ showToast('Error loading recordings. Please refresh.', 3000, 'error');
1523
+ })
1524
+ .finally(() => {
1525
+ console.log('Hiding loading overlay');
1526
+ // Always hide loading overlay
1527
+ hideSectionLoading('screen-recording-section');
1528
+ });
1594
1529
  } else {
1595
1530
  recordingStatus.textContent = 'Error: ' + (response?.error || 'Failed to stop recording');
1596
1531
  recordBtn.textContent = 'Stop Recording';
@@ -1654,26 +1589,16 @@ document.addEventListener('DOMContentLoaded', () => {
1654
1589
  return;
1655
1590
  }
1656
1591
 
1657
- // Check license before allowing workflow recording (same pattern as strategic plan)
1658
- console.log('[License] Checking license for workflow recording...');
1659
- const licenseCheck = await chrome.runtime.sendMessage({
1660
- action: 'checkLicenseForWorkflow'
1661
- });
1662
- console.log('[License] License check result:', licenseCheck);
1663
-
1664
- // If not Pro or limit reached, show upgrade modal
1665
- if (!licenseCheck || !licenseCheck.allowed || (licenseCheck.tier && licenseCheck.tier !== 'pro')) {
1666
- const message = licenseCheck?.message || 'Workflow Recording is a Pro feature. Upgrade to Pro for unlimited workflow recordings with function tracing.';
1667
-
1668
- // Show modal
1669
- if (confirm(`${message}\n\nWould you like to upgrade now?`)) {
1670
- // Open upgrade page
1671
- chrome.tabs.create({
1672
- url: LEMONSQUEEZY_CHECKOUT_URL
1673
- });
1592
+ // Check FREE tier usage limit before starting
1593
+ const usageCheck = await chrome.runtime.sendMessage({ action: 'checkUsageLimit' });
1594
+ if (usageCheck && !usageCheck.allowed) {
1595
+ console.log('[Usage] Daily limit reached:', usageCheck);
1596
+ const workflowStatus = document.getElementById('workflowRecordingStatus');
1597
+ if (workflowStatus) {
1598
+ workflowStatus.innerHTML = '<strong style="color: #f44336;">Daily limit reached</strong><br>' +
1599
+ `<small>${usageCheck.count}/${usageCheck.limit} recordings used today.<br>Upgrade to Pro for unlimited recordings.</small>`;
1674
1600
  }
1675
-
1676
- return; // Stop execution
1601
+ return;
1677
1602
  }
1678
1603
 
1679
1604
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
@@ -1822,86 +1747,29 @@ document.addEventListener('DOMContentLoaded', () => {
1822
1747
  if (response && response.success && response.workflow) {
1823
1748
  isWorkflowRecording = false;
1824
1749
  // Clear state from storage
1825
- chrome.storage.local.set({
1750
+ chrome.storage.local.set({
1826
1751
  workflowRecording: false,
1827
1752
  workflowStartTime: null
1828
1753
  });
1829
1754
  updateWorkflowRecordingUI();
1830
-
1831
- // Display the workflow results
1832
- if (workflowRecordingsContainer) {
1833
- let workflowItem;
1834
-
1835
- // Get current server port for prompt generation
1836
- const serverConnection = await checkServerStatus();
1837
- const currentServerPort = serverConnection.connectedPort;
1838
-
1839
- // The response.workflow contains the full response from stopWorkflowRecording
1755
+
1756
+ // Show loading overlay over workflow recording section
1757
+ showSectionLoading('workflow-recording-section', 'Saving workflow recording...');
1758
+
1759
+ try {
1760
+ // Single source of truth - load from server
1761
+ await loadWorkflowRecordings(true);
1762
+
1763
+ // Show success toast - response.workflow.actions is the array
1840
1764
  const workflowData = response.workflow;
1841
-
1842
- if (workflowData.savedToServer) {
1843
- // Create a saved recording entry like screen recordings
1844
- workflowItem = document.createElement('div');
1845
- workflowItem.className = 'recording-item';
1846
- workflowItem.innerHTML = `
1847
- <div class="recording-info">
1848
- <div style="font-weight: bold; margin-bottom: 4px;">
1849
- ${workflowData.workflow.sessionId} (workflow)
1850
- </div>
1851
- <div style="color: #666; font-size: 11px;">
1852
- ${workflowData.workflow.actions.length} actions • ${workflowData.workflow.logs.length} logs
1853
- </div>
1854
- </div>
1855
- <div style="display: flex; gap: 4px; margin-top: 8px;">
1856
- <button class="copy-id-btn" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
1857
- Copy ID
1858
- </button>
1859
- <button class="copy-prompt-btn" data-id="${workflowData.workflow.sessionId}" data-port="${currentServerPort || ''}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
1860
- Copy Prompt
1861
- </button>
1862
- </div>
1863
- `;
1864
-
1865
- // Add event listeners for the buttons
1866
- const copyIdBtn = workflowItem.querySelector('.copy-id-btn');
1867
- const copyPromptBtn = workflowItem.querySelector('.copy-prompt-btn');
1868
-
1869
- copyIdBtn.addEventListener('click', () => {
1870
- navigator.clipboard.writeText(workflowData.workflow.sessionId).then(() => {
1871
- copyIdBtn.textContent = 'Copied!';
1872
- copyIdBtn.style.background = '#4CAF50';
1873
- setTimeout(() => {
1874
- copyIdBtn.textContent = 'Copy ID';
1875
- copyIdBtn.style.background = '#2196F3';
1876
- }, 2000);
1877
- });
1878
- });
1879
-
1880
- // copyPromptBtn is now handled by the shared event listener system with data-id and data-port attributes
1881
- } else {
1882
- // Fallback when server not available
1883
- workflowItem = document.createElement('div');
1884
- workflowItem.className = 'recording-item';
1885
- workflowItem.innerHTML = `
1886
- <div class="recording-info">
1887
- <div style="font-weight: bold; margin-bottom: 4px; color: #ff6b6b;">
1888
- Server not available - JSON downloaded
1889
- </div>
1890
- <div style="color: #666; font-size: 11px;">
1891
- ${workflowData.workflow.length} actions recorded
1892
- </div>
1893
- </div>
1894
- `;
1895
- }
1896
-
1897
- // Add the item to the container
1898
- workflowRecordingsContainer.appendChild(workflowItem);
1899
- workflowRecordingsList.style.display = 'block';
1900
-
1901
- // Reload the list to show all recordings
1902
- setTimeout(() => {
1903
- loadWorkflowRecordings();
1904
- }, 500);
1765
+ const actionCount = workflowData?.actions?.length || 0;
1766
+ showToast(`Workflow recording saved! ${actionCount} actions recorded`, 3000, 'success');
1767
+ } catch (error) {
1768
+ console.error('Error loading workflow recordings:', error);
1769
+ showToast('Error loading recordings. Please refresh.', 3000, 'error');
1770
+ } finally {
1771
+ // Always hide loading overlay
1772
+ hideSectionLoading('workflow-recording-section');
1905
1773
  }
1906
1774
  }
1907
1775
  });
@@ -2193,34 +2061,46 @@ document.addEventListener('DOMContentLoaded', () => {
2193
2061
  const viewScreenRecordingsLink = document.getElementById('viewScreenRecordings');
2194
2062
 
2195
2063
  if (viewWorkflowRecordingsLink) {
2196
- viewWorkflowRecordingsLink.addEventListener('click', (e) => {
2064
+ viewWorkflowRecordingsLink.addEventListener('click', async (e) => {
2197
2065
  e.preventDefault();
2198
2066
  const workflowRecordingsList = document.getElementById('workflowRecordingsList');
2199
2067
  if (workflowRecordingsList) {
2200
2068
  if (workflowRecordingsList.style.display === 'none' || workflowRecordingsList.style.display === '') {
2069
+ // Show recordings
2201
2070
  workflowRecordingsList.style.display = 'block';
2202
- loadWorkflowRecordings();
2071
+ await loadWorkflowRecordings();
2203
2072
  viewWorkflowRecordingsLink.textContent = 'Hide Recordings';
2073
+ // Save preference
2074
+ await chrome.storage.local.set({ workflowRecordingsVisible: true });
2204
2075
  } else {
2076
+ // Hide recordings
2205
2077
  workflowRecordingsList.style.display = 'none';
2206
2078
  viewWorkflowRecordingsLink.textContent = 'View Past Recordings';
2079
+ // Save preference
2080
+ await chrome.storage.local.set({ workflowRecordingsVisible: false });
2207
2081
  }
2208
2082
  }
2209
2083
  });
2210
2084
  }
2211
2085
 
2212
2086
  if (viewScreenRecordingsLink) {
2213
- viewScreenRecordingsLink.addEventListener('click', (e) => {
2087
+ viewScreenRecordingsLink.addEventListener('click', async (e) => {
2214
2088
  e.preventDefault();
2215
2089
  const recordingsList = document.getElementById('recordingsList');
2216
2090
  if (recordingsList) {
2217
2091
  if (recordingsList.style.display === 'none') {
2218
- loadScreenRecordings();
2092
+ // Show recordings
2093
+ await loadScreenRecordings();
2219
2094
  recordingsList.style.display = 'block';
2220
2095
  viewScreenRecordingsLink.textContent = 'Hide Recordings';
2096
+ // Save preference
2097
+ await chrome.storage.local.set({ screenRecordingsVisible: true });
2221
2098
  } else {
2099
+ // Hide recordings
2222
2100
  recordingsList.style.display = 'none';
2223
2101
  viewScreenRecordingsLink.textContent = 'View Past Recordings';
2102
+ // Save preference
2103
+ await chrome.storage.local.set({ screenRecordingsVisible: false });
2224
2104
  }
2225
2105
  }
2226
2106
  });
@@ -2261,16 +2141,19 @@ document.addEventListener('DOMContentLoaded', () => {
2261
2141
  });
2262
2142
 
2263
2143
  // Function to load and display workflow recordings
2264
- async function loadWorkflowRecordings() {
2144
+ async function loadWorkflowRecordings(autoShow = false) {
2265
2145
  const workflowRecordingsList = document.getElementById('workflowRecordingsList');
2266
2146
  const workflowRecordingsContainer = document.getElementById('workflowRecordingsContainer');
2267
-
2147
+ const viewWorkflowRecordingsLink = document.getElementById('viewWorkflowRecordings');
2148
+
2268
2149
  if (!workflowRecordingsList || !workflowRecordingsContainer) {
2269
2150
  return;
2270
2151
  }
2271
-
2152
+
2272
2153
  // Clear existing items
2273
2154
  workflowRecordingsContainer.innerHTML = '';
2155
+
2156
+ let hasRecordings = false;
2274
2157
 
2275
2158
  // Check server for workflow recordings
2276
2159
  const ports = CONFIG_PORTS;
@@ -2283,8 +2166,9 @@ async function loadWorkflowRecordings() {
2283
2166
 
2284
2167
  // Handle both data.recordings and direct array response
2285
2168
  const recordings = data.recordings || data;
2286
-
2169
+
2287
2170
  if (Array.isArray(recordings) && recordings.length > 0) {
2171
+ hasRecordings = true;
2288
2172
  // Display each workflow recording
2289
2173
  recordings.forEach(recording => {
2290
2174
  const workflowItem = document.createElement('div');
@@ -2303,12 +2187,18 @@ async function loadWorkflowRecordings() {
2303
2187
  ${recording.screenshot_settings ? '<div style="color: #2196F3; font-size: 10px;">&#128248; Screenshots included</div>' : ''}
2304
2188
  </div>
2305
2189
  <div class="recording-buttons">
2190
+ <button class="copy-id-btn" data-session-id="${recording.session_id}" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2191
+ Copy ID
2192
+ </button>
2193
+ <button class="copy-prompt-btn" data-session-id="${recording.session_id}" data-name="${recording.name || ''}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2194
+ Copy Prompt
2195
+ </button>
2196
+ <button class="view-btn" data-session-id="${recording.session_id}" style="padding: 4px 8px; background: #9C27B0; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2197
+ View
2198
+ </button>
2306
2199
  <button class="play-btn" data-session-id="${recording.session_id}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2307
2200
  &#9654; Play
2308
2201
  </button>
2309
- <button class="get-prompt-btn" data-session-id="${recording.session_id}" data-name="${recording.name || ''}" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2310
- Get Prompt
2311
- </button>
2312
2202
  <button class="delete-btn" data-recording-id="${recording.id || recording.session_id}" style="padding: 4px 8px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2313
2203
  Delete
2314
2204
  </button>
@@ -2316,17 +2206,29 @@ async function loadWorkflowRecordings() {
2316
2206
  `;
2317
2207
 
2318
2208
  // Add event listeners
2209
+ const copyIdBtn = workflowItem.querySelector('.copy-id-btn');
2210
+ const copyPromptBtn = workflowItem.querySelector('.copy-prompt-btn');
2211
+ const viewBtn = workflowItem.querySelector('.view-btn');
2319
2212
  const playBtn = workflowItem.querySelector('.play-btn');
2320
- const getPromptBtn = workflowItem.querySelector('.get-prompt-btn');
2321
2213
  const deleteBtn = workflowItem.querySelector('.delete-btn');
2322
-
2323
- playBtn.addEventListener('click', () => playWorkflow(recording.session_id));
2324
- getPromptBtn.addEventListener('click', () => {
2214
+
2215
+ copyIdBtn.addEventListener('click', () => {
2216
+ navigator.clipboard.writeText(recording.session_id).then(() => {
2217
+ copyIdBtn.textContent = 'Copied!';
2218
+ copyIdBtn.style.background = '#4CAF50';
2219
+ setTimeout(() => {
2220
+ copyIdBtn.textContent = 'Copy ID';
2221
+ copyIdBtn.style.background = '#2196F3';
2222
+ }, 2000);
2223
+ });
2224
+ });
2225
+
2226
+ copyPromptBtn.addEventListener('click', () => {
2325
2227
  const sessionId = recording.session_id;
2326
2228
  const name = recording.name;
2327
2229
  const port = window.CHROMEDEBUG_CONFIG?.ports?.[0] || '3001';
2328
2230
  let prompt;
2329
-
2231
+
2330
2232
  if (name) {
2331
2233
  // Use name-based prompt similar to screen recording format
2332
2234
  prompt = `Please use the get_workflow_recording function in Chrome Debug to load the workflow recording "${name}" (session: ${sessionId}) that was recorded on port ${port}.`;
@@ -2334,16 +2236,26 @@ async function loadWorkflowRecordings() {
2334
2236
  // Use session ID based prompt
2335
2237
  prompt = `Please use the get_workflow_recording function in Chrome Debug to load the workflow recording "${sessionId}" that was recorded on port ${port}.`;
2336
2238
  }
2337
-
2239
+
2338
2240
  navigator.clipboard.writeText(prompt).then(() => {
2339
- getPromptBtn.textContent = 'Copied!';
2340
- getPromptBtn.style.background = '#4CAF50';
2241
+ copyPromptBtn.textContent = 'Copied!';
2242
+ const originalBg = copyPromptBtn.style.background;
2243
+ copyPromptBtn.style.background = '#4CAF50';
2341
2244
  setTimeout(() => {
2342
- getPromptBtn.textContent = 'Get Prompt';
2343
- getPromptBtn.style.background = '#2196F3';
2245
+ copyPromptBtn.textContent = 'Copy Prompt';
2246
+ copyPromptBtn.style.background = originalBg;
2344
2247
  }, 2000);
2345
2248
  });
2346
2249
  });
2250
+
2251
+ viewBtn.addEventListener('click', async () => {
2252
+ // Open frame editor in workflow mode
2253
+ const frameEditorUrl = chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recording.session_id}&type=workflow`);
2254
+ chrome.tabs.create({ url: frameEditorUrl });
2255
+ });
2256
+
2257
+ playBtn.addEventListener('click', () => playWorkflow(recording.session_id));
2258
+
2347
2259
  deleteBtn.addEventListener('click', () => {
2348
2260
  console.log('Delete clicked for recording:', recording);
2349
2261
  const recordingId = recording.id || recording.session_id;
@@ -2378,6 +2290,27 @@ async function loadWorkflowRecordings() {
2378
2290
  errorMsg.textContent = 'Could not connect to Chrome Debug server';
2379
2291
  workflowRecordingsContainer.appendChild(errorMsg);
2380
2292
  }
2293
+
2294
+ // Handle visibility based on recordings and user preference
2295
+ if (!hasRecordings) {
2296
+ workflowRecordingsList.style.display = 'none';
2297
+ } else {
2298
+ // Get user's saved preference
2299
+ const prefs = await chrome.storage.local.get(['workflowRecordingsVisible']);
2300
+ const shouldShow = autoShow || (prefs.workflowRecordingsVisible !== false); // Default to true
2301
+
2302
+ if (shouldShow) {
2303
+ workflowRecordingsList.style.display = 'block';
2304
+ if (viewWorkflowRecordingsLink) {
2305
+ viewWorkflowRecordingsLink.textContent = 'Hide Recordings';
2306
+ }
2307
+ } else {
2308
+ workflowRecordingsList.style.display = 'none';
2309
+ if (viewWorkflowRecordingsLink) {
2310
+ viewWorkflowRecordingsLink.textContent = 'View Past Recordings';
2311
+ }
2312
+ }
2313
+ }
2381
2314
  }
2382
2315
 
2383
2316
  // Function to play a workflow
@@ -2526,18 +2459,147 @@ async function deleteWorkflowRecording(recordingId) {
2526
2459
  }
2527
2460
 
2528
2461
  // Function to load and display screen recordings
2529
- async function loadScreenRecordings() {
2462
+ async function loadScreenRecordings(autoShow = false) {
2530
2463
  const recordingsList = document.getElementById('recordingsList');
2531
2464
  const recordingsContainer = document.getElementById('recordingsContainer');
2532
-
2465
+ const viewScreenRecordingsLink = document.getElementById('viewScreenRecordings');
2466
+
2533
2467
  if (!recordingsList || !recordingsContainer) {
2534
2468
  return;
2535
2469
  }
2536
-
2470
+
2537
2471
  // Clear existing items
2538
2472
  recordingsContainer.innerHTML = '';
2539
-
2540
- // Check server for screen recordings
2473
+
2474
+ let hasRecordings = false;
2475
+
2476
+ // First, load browser-only recordings from IndexedDB
2477
+ try {
2478
+ console.log('[Popup] Requesting browser-only recordings...');
2479
+ const browserRecordings = await chrome.runtime.sendMessage({
2480
+ action: 'getBrowserRecordings'
2481
+ });
2482
+ console.log('[Popup] Browser recordings received:', browserRecordings);
2483
+
2484
+ if (browserRecordings && browserRecordings.length > 0) {
2485
+ hasRecordings = true;
2486
+ recordingsList.style.display = 'block';
2487
+
2488
+ // Display each browser-only recording
2489
+ browserRecordings.forEach(recording => {
2490
+ const recordingItem = document.createElement('div');
2491
+ recordingItem.className = 'recording-item';
2492
+
2493
+ const date = new Date(recording.startTime);
2494
+ const formattedDate = date.toLocaleString();
2495
+ const displayName = recording.title || `Recording ${recording.sessionId}`;
2496
+
2497
+ recordingItem.innerHTML = `
2498
+ <div class="recording-info">
2499
+ <div class="recording-id" title="${recording.sessionId}">
2500
+ <span class="recording-id-text">🌐 ${displayName}</span>
2501
+ <span style="font-size: 10px; background: #ff9800; color: white; padding: 2px 6px; border-radius: 3px; margin-left: 4px;">BROWSER-ONLY</span>
2502
+ <span style="color: #666; font-size: 11px; margin-left: 8px;">${recording.frameCount || 0} frames • ${formattedDate}</span>
2503
+ </div>
2504
+ </div>
2505
+ <div class="recording-buttons">
2506
+ <button class="copy-id-btn" data-session-id="${recording.sessionId}" title="Copy ID">Copy ID</button>
2507
+ <button class="copy-prompt-btn" data-session-id="${recording.sessionId}" data-name="${recording.title || ''}" title="Copy Claude Prompt">Copy Prompt</button>
2508
+ <button class="view-browser-btn" data-session-id="${recording.sessionId}" title="View Screenshots">View</button>
2509
+ <button class="export-btn" data-session-id="${recording.sessionId}" title="Export Recording">Export</button>
2510
+ <button class="delete-browser-btn" data-session-id="${recording.sessionId}" title="Delete">Delete</button>
2511
+ </div>
2512
+ `;
2513
+
2514
+ // Add event listeners
2515
+ const copyIdBtn = recordingItem.querySelector('.copy-id-btn');
2516
+ const copyPromptBtn = recordingItem.querySelector('.copy-prompt-btn');
2517
+ const viewBtn = recordingItem.querySelector('.view-browser-btn');
2518
+ const exportBtn = recordingItem.querySelector('.export-btn');
2519
+ const deleteBtn = recordingItem.querySelector('.delete-browser-btn');
2520
+
2521
+ copyIdBtn.addEventListener('click', () => {
2522
+ navigator.clipboard.writeText(recording.sessionId).then(() => {
2523
+ copyIdBtn.textContent = 'Copied!';
2524
+ copyIdBtn.style.background = '#4CAF50';
2525
+ setTimeout(() => {
2526
+ copyIdBtn.textContent = 'Copy ID';
2527
+ copyIdBtn.style.background = '#2196F3';
2528
+ }, 2000);
2529
+ });
2530
+ });
2531
+
2532
+ copyPromptBtn.addEventListener('click', () => {
2533
+ const sessionId = recording.sessionId;
2534
+ const title = recording.title;
2535
+ let prompt;
2536
+
2537
+ if (title) {
2538
+ prompt = `Please analyze the browser-only screen recording "${title}" (session: ${sessionId}). This recording is stored locally in the browser.`;
2539
+ } else {
2540
+ prompt = `Please analyze the browser-only screen recording "${sessionId}". This recording is stored locally in the browser.`;
2541
+ }
2542
+
2543
+ navigator.clipboard.writeText(prompt).then(() => {
2544
+ copyPromptBtn.textContent = 'Copied!';
2545
+ const originalBg = copyPromptBtn.style.background;
2546
+ copyPromptBtn.style.background = '#4CAF50';
2547
+ setTimeout(() => {
2548
+ copyPromptBtn.textContent = 'Copy Prompt';
2549
+ copyPromptBtn.style.background = originalBg;
2550
+ }, 2000);
2551
+ });
2552
+ });
2553
+
2554
+ viewBtn.addEventListener('click', () => {
2555
+ // Open frame editor in browser-only mode
2556
+ const frameEditorUrl = chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recording.sessionId}&browserOnly=true`);
2557
+ chrome.tabs.create({ url: frameEditorUrl });
2558
+ });
2559
+
2560
+ exportBtn.addEventListener('click', async () => {
2561
+ try {
2562
+ const result = await chrome.runtime.sendMessage({
2563
+ action: 'exportBrowserRecording',
2564
+ sessionId: recording.sessionId
2565
+ });
2566
+
2567
+ if (result && result.success) {
2568
+ exportBtn.textContent = 'Exported!';
2569
+ exportBtn.style.background = '#4CAF50';
2570
+ setTimeout(() => {
2571
+ exportBtn.textContent = 'Export';
2572
+ exportBtn.style.background = '#FF9800';
2573
+ }, 2000);
2574
+ }
2575
+ } catch (error) {
2576
+ console.error('Failed to export recording:', error);
2577
+ alert('Failed to export recording');
2578
+ }
2579
+ });
2580
+
2581
+ deleteBtn.addEventListener('click', async () => {
2582
+ if (confirm('Are you sure you want to delete this browser recording?')) {
2583
+ try {
2584
+ await chrome.runtime.sendMessage({
2585
+ action: 'deleteBrowserRecording',
2586
+ sessionId: recording.sessionId
2587
+ });
2588
+ loadScreenRecordings(); // Reload list
2589
+ } catch (error) {
2590
+ console.error('Failed to delete recording:', error);
2591
+ }
2592
+ }
2593
+ });
2594
+
2595
+ recordingsContainer.appendChild(recordingItem);
2596
+ });
2597
+ }
2598
+ } catch (error) {
2599
+ console.error('Failed to load browser-only recordings:', error);
2600
+ }
2601
+
2602
+ // Then, check server for screen recordings
2541
2603
  const ports = CONFIG_PORTS;
2542
2604
  for (const port of ports) {
2543
2605
  try {
@@ -2545,6 +2607,7 @@ async function loadScreenRecordings() {
2545
2607
  if (response.ok) {
2546
2608
  const data = await response.json();
2547
2609
  if (data && data.length > 0) {
2610
+ hasRecordings = true;
2548
2611
  recordingsList.style.display = 'block';
2549
2612
 
2550
2613
  // Display each screen recording
@@ -2563,25 +2626,25 @@ async function loadScreenRecordings() {
2563
2626
 
2564
2627
  recordingItem.innerHTML = `
2565
2628
  <div class="recording-info">
2566
- <div style="font-weight: bold; margin-bottom: 4px;">${displayName}</div>
2567
- <div style="color: #666; font-size: 11px;">
2568
- ${recording.totalFrames} frames • ${formattedDate}
2629
+ <div class="recording-id" title="${recording.sessionId}">
2630
+ <span class="recording-id-text">🎥 ${displayName}</span>
2631
+ <span style="color: #666; font-size: 11px; margin-left: 8px;">${recording.totalFrames} frames • ${formattedDate}</span>
2569
2632
  </div>
2570
2633
  </div>
2571
2634
  <div class="recording-buttons">
2572
- <button class="copy-id-btn" data-session-id="${recording.sessionId}" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2573
- Copy ID
2574
- </button>
2575
- <button class="delete-btn" data-session-id="${recording.sessionId}" style="padding: 4px 8px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2576
- Delete
2577
- </button>
2635
+ <button class="copy-id-btn" data-session-id="${recording.sessionId}" title="Copy ID">Copy ID</button>
2636
+ <button class="copy-prompt-btn" data-session-id="${recording.sessionId}" data-name="${recording.name || ''}" data-port="${port}" title="Copy Claude Prompt">Copy Prompt</button>
2637
+ <button class="view-btn" data-session-id="${recording.sessionId}" title="View Screenshots">View</button>
2638
+ <button class="delete-btn" data-session-id="${recording.sessionId}" title="Delete">Delete</button>
2578
2639
  </div>
2579
2640
  `;
2580
2641
 
2581
2642
  // Add event listeners
2582
2643
  const copyIdBtn = recordingItem.querySelector('.copy-id-btn');
2644
+ const copyPromptBtn = recordingItem.querySelector('.copy-prompt-btn');
2645
+ const viewBtn = recordingItem.querySelector('.view-btn');
2583
2646
  const deleteBtn = recordingItem.querySelector('.delete-btn');
2584
-
2647
+
2585
2648
  copyIdBtn.addEventListener('click', () => {
2586
2649
  navigator.clipboard.writeText(recording.sessionId).then(() => {
2587
2650
  copyIdBtn.textContent = 'Copied!';
@@ -2592,14 +2655,60 @@ async function loadScreenRecordings() {
2592
2655
  }, 2000);
2593
2656
  });
2594
2657
  });
2595
-
2658
+
2659
+ copyPromptBtn.addEventListener('click', () => {
2660
+ const sessionId = recording.sessionId;
2661
+ const name = recording.name;
2662
+ let prompt;
2663
+
2664
+ if (name) {
2665
+ prompt = `Please use the chrome_debug_show_frames function in Chrome Debug to display the screen recording "${name}" (session: ${sessionId}) that was recorded on port ${port}.`;
2666
+ } else {
2667
+ prompt = `Please use the chrome_debug_show_frames function in Chrome Debug to display the screen recording "${sessionId}" that was recorded on port ${port}.`;
2668
+ }
2669
+
2670
+ navigator.clipboard.writeText(prompt).then(() => {
2671
+ copyPromptBtn.textContent = 'Copied!';
2672
+ const originalBg = copyPromptBtn.style.background;
2673
+ copyPromptBtn.style.background = '#4CAF50';
2674
+ setTimeout(() => {
2675
+ copyPromptBtn.textContent = 'Copy Prompt';
2676
+ copyPromptBtn.style.background = originalBg;
2677
+ }, 2000);
2678
+ });
2679
+ });
2680
+
2681
+ viewBtn.addEventListener('click', async () => {
2682
+ try {
2683
+ // Check if frame editor is available (Pro version)
2684
+ const frameEditorUrl = chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recording.sessionId}`);
2685
+ const response = await fetch(frameEditorUrl, { method: 'HEAD' });
2686
+
2687
+ if (response.ok) {
2688
+ // Pro version - open frame editor
2689
+ chrome.tabs.create({ url: frameEditorUrl });
2690
+ } else {
2691
+ // Free version - show summary
2692
+ const framesCount = recording.totalFrames || 0;
2693
+ const noteText = recording.name ? `\nName: ${recording.name}` : '';
2694
+ alert(`Screen Recording${noteText}\nSession ID: ${recording.sessionId}\n\nFrames: ${framesCount}\n\nUse "Copy ID" to share with Claude Code for detailed analysis.\n\nUpgrade to Pro for the Frame Editor with advanced viewing and editing features.`);
2695
+ }
2696
+ } catch (error) {
2697
+ console.error('Failed to view recording:', error);
2698
+ // Fallback to alert
2699
+ const framesCount = recording.totalFrames || 0;
2700
+ const noteText = recording.name ? `\nName: ${recording.name}` : '';
2701
+ alert(`Screen Recording${noteText}\nSession ID: ${recording.sessionId}\n\nFrames: ${framesCount}\n\nUse "Copy ID" to share with Claude Code for detailed analysis.`);
2702
+ }
2703
+ });
2704
+
2596
2705
  deleteBtn.addEventListener('click', async () => {
2597
2706
  if (confirm('Are you sure you want to delete this screen recording?')) {
2598
2707
  try {
2599
2708
  const deleteResponse = await fetch(`http://localhost:${port}/chromedebug/recording/${recording.sessionId}`, {
2600
2709
  method: 'DELETE'
2601
2710
  });
2602
-
2711
+
2603
2712
  if (deleteResponse.ok) {
2604
2713
  // Reload the list
2605
2714
  loadScreenRecordings();
@@ -2612,8 +2721,6 @@ async function loadScreenRecordings() {
2612
2721
 
2613
2722
  recordingsContainer.appendChild(recordingItem);
2614
2723
  });
2615
- } else {
2616
- recordingsList.style.display = 'none';
2617
2724
  }
2618
2725
  break; // Found working server
2619
2726
  }
@@ -2621,6 +2728,27 @@ async function loadScreenRecordings() {
2621
2728
  console.error(`Failed to load screen recordings from port ${port}:`, error);
2622
2729
  }
2623
2730
  }
2731
+
2732
+ // Handle visibility based on recordings and user preference
2733
+ if (!hasRecordings) {
2734
+ recordingsList.style.display = 'none';
2735
+ } else {
2736
+ // Get user's saved preference
2737
+ const prefs = await chrome.storage.local.get(['screenRecordingsVisible']);
2738
+ const shouldShow = autoShow || (prefs.screenRecordingsVisible !== false); // Default to true
2739
+
2740
+ if (shouldShow) {
2741
+ recordingsList.style.display = 'block';
2742
+ if (viewScreenRecordingsLink) {
2743
+ viewScreenRecordingsLink.textContent = 'Hide Recordings';
2744
+ }
2745
+ } else {
2746
+ recordingsList.style.display = 'none';
2747
+ if (viewScreenRecordingsLink) {
2748
+ viewScreenRecordingsLink.textContent = 'View Past Recordings';
2749
+ }
2750
+ }
2751
+ }
2624
2752
  }
2625
2753
 
2626
2754
  /*
@@ -2790,6 +2918,17 @@ chrome.storage.onChanged.addListener((changes, namespace) => {
2790
2918
  isWorkflowRecording = changes.workflowRecording.newValue === true;
2791
2919
  updateWorkflowRecordingUI();
2792
2920
  }
2921
+ // Update usage display when usage count changes
2922
+ if (changes.chromedebug_daily_usage) {
2923
+ const usage = changes.chromedebug_daily_usage.newValue;
2924
+ if (usage) {
2925
+ const usageCountEl = document.getElementById('usage-count');
2926
+ const usageLimitEl = document.getElementById('usage-limit');
2927
+ if (usageCountEl) usageCountEl.textContent = usage.count || 0;
2928
+ if (usageLimitEl) usageLimitEl.textContent = '5'; // FREE tier limit
2929
+ console.log(`[Usage] Storage updated: ${usage.count}/5`);
2930
+ }
2931
+ }
2793
2932
  }
2794
2933
  });
2795
2934
 
@@ -2828,12 +2967,20 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2828
2967
  recordingStatus.innerHTML = `<strong style="color: #4CAF50;">Recording saved!</strong><br>` +
2829
2968
  `<small>${request.totalFrames} frames (${Math.round(request.duration / 1000)}s)</small>`;
2830
2969
  }
2831
- // Add frame session to recordings list
2832
- addRecording(request.sessionId, false, true, request.serverPort, null, request.sessionName);
2970
+ // Reload screen recordings list to show the new recording
2971
+ loadScreenRecordings(true);
2833
2972
  // Clear status after 5 seconds
2834
2973
  setTimeout(() => {
2835
2974
  if (recordingStatus) recordingStatus.textContent = '';
2836
2975
  }, 5000);
2976
+ } else if (request.action === 'workflowRecordingSaved') {
2977
+ console.log('Workflow recording saved:', request.workflowId);
2978
+ // Reload workflow recordings list to show the new recording
2979
+ loadWorkflowRecordings(true);
2980
+ } else if (request.action === 'recordingStopped') {
2981
+ console.log('Recording stopped:', request.sessionId);
2982
+ // Reload screen recordings list immediately
2983
+ loadScreenRecordings(true);
2837
2984
  } else if (request.action === 'uploadError') {
2838
2985
  const recordingStatus = document.getElementById('recordingStatus');
2839
2986
  if (recordingStatus) {