@dynamicu/chromedebug-mcp 2.7.1 → 2.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,6 +36,7 @@ class BrowserRecordingManager {
36
36
  title: metadata.title || 'Browser Recording',
37
37
  frameCount: 0,
38
38
  status: 'recording',
39
+ isVideoMode: metadata.isVideoMode || false,
39
40
  metadata
40
41
  };
41
42
 
@@ -248,6 +249,39 @@ class BrowserRecordingManager {
248
249
  }
249
250
  }
250
251
 
252
+ /**
253
+ * Delete all recordings and their frames
254
+ * @returns {Promise<{success: boolean, deletedCount: number}>}
255
+ */
256
+ async deleteAllRecordings() {
257
+ try {
258
+ const recordings = await this.listRecordings();
259
+ let deletedCount = 0;
260
+
261
+ for (const recording of recordings) {
262
+ const result = await this.deleteRecording(recording.sessionId);
263
+ if (result.success) {
264
+ deletedCount++;
265
+ }
266
+ }
267
+
268
+ // Clear any active recordings
269
+ this.activeRecordings.clear();
270
+
271
+ return {
272
+ success: true,
273
+ deletedCount
274
+ };
275
+ } catch (error) {
276
+ console.error('Failed to delete all recordings:', error);
277
+ return {
278
+ success: false,
279
+ deletedCount: 0,
280
+ error: error.message
281
+ };
282
+ }
283
+ }
284
+
251
285
  }
252
286
 
253
287
  // Export for use in other extension files
@@ -476,9 +476,15 @@ if (isExtensionValid()) {
476
476
  sendResponse({ success: true });
477
477
  } else if (request.action === 'recordingStarted') {
478
478
  // Recording indicator now handled by ScreenCaptureVisualFeedback
479
- // console.log('[ChromeDebug MCP] Received recordingStarted message with scheduled start time:', request.scheduledStartTime);
479
+ console.log('[ChromeDebug MCP] Received recordingStarted, videoMode:', request.videoMode, 'sessionId:', request.sessionId);
480
480
  // Start tracking screen interactions with scheduled timing
481
- startScreenInteractionTracking(request.scheduledStartTime, request.sessionId);
481
+ startScreenInteractionTracking(request.scheduledStartTime, request.sessionId, request.videoMode || false);
482
+ sendResponse({ success: true });
483
+ } else if (request.action === 'setMouseTrackingSettings') {
484
+ // Update mouse tracking settings (PRO-only feature)
485
+ mouseTrackingEnabled = request.enabled || false;
486
+ mouseSampleInterval = request.sampleInterval || 100;
487
+ // console.log('[ChromeDebug MCP] Mouse tracking settings updated:', { mouseTrackingEnabled, mouseSampleInterval });
482
488
  sendResponse({ success: true });
483
489
  } else if (request.action === 'recordingStopped') {
484
490
  // Recording indicator cleanup now handled by ScreenCaptureVisualFeedback
@@ -522,6 +528,18 @@ if (isExtensionValid()) {
522
528
  sendResponse({ success: false, error: error.message });
523
529
  });
524
530
  return true; // Keep channel open for async response
531
+ } else if (request.action === 'startScreenshotRecording') {
532
+ // Start screenshot recording mode (v2.7.3)
533
+ // console.log('[ChromeDebug MCP] Starting screenshot recording with settings:', request.settings);
534
+ screenshotModeTabId = request.tabId;
535
+ screenshotModeSessionId = request.sessionId;
536
+ startScreenshotRecordingMode(request.settings);
537
+ sendResponse({ success: true });
538
+ } else if (request.action === 'getScreenshotData') {
539
+ // Called by background script to retrieve screenshot data
540
+ // console.log('[ChromeDebug MCP] Getting screenshot data');
541
+ const screenshots = stopScreenshotRecordingMode();
542
+ sendResponse({ success: true, screenshots: screenshots });
525
543
  } else if (request.action === 'createRestorePoint') {
526
544
  // Create restore point for current workflow
527
545
  createRestorePoint(request.actionIndex || workflowActions.length).then(restorePointId => {
@@ -878,23 +896,21 @@ let workflowTabId = null; // Store tabId for use when stopping recording
878
896
  let lastScreenshotTime = 0;
879
897
  let pendingScreenshots = []; // Track pending screenshot captures
880
898
 
899
+ // Screenshot recording mode variables (v2.7.3)
900
+ let isScreenshotMode = false;
901
+ let screenshotModeSettings = { countdown: 3, hideCursor: true };
902
+ let screenshotModeActions = [];
903
+ let screenshotModeIndicator = null;
904
+ let screenshotModeTabId = null;
905
+ let screenshotModeSessionId = null;
906
+ let isCountdownActive = false; // Prevent multiple countdowns
907
+
881
908
  // Async screenshot capture without blocking click recording
882
909
  function captureScreenshotAsync(action, actionIndex) {
883
910
  // Create the async capture promise
884
911
  const capturePromise = (async () => {
885
912
  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
-
913
+ // Throttling is handled centrally in background.js
898
914
  const screenshotData = await captureWorkflowScreenshot();
899
915
 
900
916
  if (screenshotData) {
@@ -1636,6 +1652,305 @@ async function stopWorkflowRecording() {
1636
1652
  };
1637
1653
  }
1638
1654
 
1655
+ // ============================================================
1656
+ // SCREENSHOT RECORDING MODE FUNCTIONS (v2.7.3)
1657
+ // Click-to-capture mode with configurable countdown
1658
+ // ============================================================
1659
+
1660
+ /**
1661
+ * Start screenshot recording mode
1662
+ * @param {object} settings - {countdown: number, hideCursor: boolean}
1663
+ */
1664
+ function startScreenshotRecordingMode(settings = {}) {
1665
+ isScreenshotMode = true;
1666
+ screenshotModeSettings = {
1667
+ countdown: settings.countdown ?? 3,
1668
+ hideCursor: settings.hideCursor ?? true
1669
+ };
1670
+ screenshotModeActions = [];
1671
+ isCountdownActive = false;
1672
+
1673
+ // console.log('[Screenshot Mode] Starting with settings:', screenshotModeSettings);
1674
+
1675
+ // Create mode indicator
1676
+ createScreenshotModeIndicator();
1677
+
1678
+ // Add click listener (capture phase to intercept before page handlers)
1679
+ document.addEventListener('click', handleScreenshotModeClick, true);
1680
+
1681
+ // Note: Cursor hiding is now done per-capture in handleScreenshotModeClick()
1682
+ // This ensures the cursor is only hidden during the countdown/capture, not the entire session
1683
+ }
1684
+
1685
+ /**
1686
+ * Stop screenshot recording mode and return captured screenshots
1687
+ */
1688
+ function stopScreenshotRecordingMode() {
1689
+ // console.log('[Screenshot Mode] Stopping, captured:', screenshotModeActions.length, 'screenshots');
1690
+
1691
+ isScreenshotMode = false;
1692
+ isCountdownActive = false;
1693
+
1694
+ // Remove click listener
1695
+ document.removeEventListener('click', handleScreenshotModeClick, true);
1696
+
1697
+ // Remove mode indicator
1698
+ removeScreenshotModeIndicator();
1699
+
1700
+ // Restore cursor
1701
+ document.body.classList.remove('chromedebug-hide-cursor');
1702
+
1703
+ // Reset state variables
1704
+ screenshotModeTabId = null;
1705
+ screenshotModeSessionId = null;
1706
+
1707
+ // Return captured screenshots
1708
+ const screenshots = screenshotModeActions.map(action => ({
1709
+ data: action.screenshot_data,
1710
+ timestamp: action.timestamp,
1711
+ x: action.x,
1712
+ y: action.y
1713
+ }));
1714
+
1715
+ screenshotModeActions = [];
1716
+ return screenshots;
1717
+ }
1718
+
1719
+ /**
1720
+ * Wait for browser repaint to complete
1721
+ * Uses double-requestAnimationFrame to ensure DOM changes are painted
1722
+ */
1723
+ function waitForRepaint() {
1724
+ return new Promise(resolve => {
1725
+ requestAnimationFrame(() => {
1726
+ requestAnimationFrame(resolve);
1727
+ });
1728
+ });
1729
+ }
1730
+
1731
+ /**
1732
+ * Handle click event in screenshot mode
1733
+ */
1734
+ async function handleScreenshotModeClick(event) {
1735
+ if (!isScreenshotMode) return;
1736
+
1737
+ // Prevent multiple countdowns at once
1738
+ if (isCountdownActive) {
1739
+ // console.log('[Screenshot Mode] Countdown already active, ignoring click');
1740
+ return;
1741
+ }
1742
+
1743
+ // CRITICAL: Capture tabId at click time (per Second Opinion review)
1744
+ // This ensures countdown captures the correct tab even if user switches
1745
+ const currentTabId = screenshotModeTabId;
1746
+
1747
+ // Get click position
1748
+ const clickX = event.clientX;
1749
+ const clickY = event.clientY;
1750
+
1751
+ // console.log('[Screenshot Mode] Click detected at:', clickX, clickY);
1752
+
1753
+ // Don't prevent default - let the click happen
1754
+ // The countdown gives time for page to respond
1755
+
1756
+ const countdown = screenshotModeSettings.countdown;
1757
+
1758
+ isCountdownActive = true;
1759
+
1760
+ try {
1761
+ // CRITICAL: Hide indicator BEFORE countdown/capture to prevent it appearing in screenshot
1762
+ // Using visibility:hidden (not display:none) to avoid layout reflow
1763
+ if (screenshotModeIndicator) {
1764
+ screenshotModeIndicator.style.visibility = 'hidden';
1765
+ }
1766
+
1767
+ // Hide cursor just before capture if setting enabled (per Second Opinion review)
1768
+ // Cursor is only hidden during countdown/capture, not the entire session
1769
+ if (screenshotModeSettings.hideCursor) {
1770
+ // Ensure style tag exists (lazy injection)
1771
+ if (!document.getElementById('chromedebug-hide-cursor-style')) {
1772
+ const style = document.createElement('style');
1773
+ style.id = 'chromedebug-hide-cursor-style';
1774
+ style.textContent = '.chromedebug-hide-cursor { cursor: none !important; }';
1775
+ document.head.appendChild(style);
1776
+ }
1777
+ document.body.classList.add('chromedebug-hide-cursor');
1778
+ await waitForRepaint(); // Ensure cursor is actually hidden before proceeding
1779
+ }
1780
+
1781
+ if (countdown > 0) {
1782
+ await showCountdownOverlay(countdown);
1783
+ // showCountdownOverlay now includes waitForRepaint after overlay removal
1784
+ } else {
1785
+ // No countdown - still need repaint delay for indicator to be hidden
1786
+ await waitForRepaint();
1787
+ }
1788
+
1789
+ // Capture screenshot via background script using captured tabId
1790
+ const screenshot = await captureScreenshotForMode(currentTabId);
1791
+
1792
+ if (screenshot) {
1793
+ screenshotModeActions.push({
1794
+ type: 'screenshot',
1795
+ timestamp: Date.now(),
1796
+ x: clickX,
1797
+ y: clickY,
1798
+ screenshot_data: screenshot
1799
+ });
1800
+
1801
+ // console.log('[Screenshot Mode] Screenshot captured, total:', screenshotModeActions.length);
1802
+
1803
+ // Update indicator count
1804
+ updateScreenshotModeIndicatorCount();
1805
+ }
1806
+ } catch (error) {
1807
+ console.error('[Screenshot Mode] Error capturing screenshot:', error);
1808
+ } finally {
1809
+ isCountdownActive = false;
1810
+
1811
+ // Restore cursor visibility after capture
1812
+ if (screenshotModeSettings.hideCursor) {
1813
+ document.body.classList.remove('chromedebug-hide-cursor');
1814
+ }
1815
+
1816
+ // Restore indicator visibility - check element still exists in DOM
1817
+ if (screenshotModeIndicator && document.body.contains(screenshotModeIndicator)) {
1818
+ screenshotModeIndicator.style.visibility = 'visible';
1819
+ }
1820
+ }
1821
+ }
1822
+
1823
+ /**
1824
+ * Capture screenshot through background script
1825
+ */
1826
+ async function captureScreenshotForMode(tabId) {
1827
+ return new Promise((resolve, reject) => {
1828
+ if (!isExtensionValid()) {
1829
+ reject(new Error('Extension context invalidated'));
1830
+ return;
1831
+ }
1832
+
1833
+ chrome.runtime.sendMessage({
1834
+ action: 'captureScreenshotForMode',
1835
+ tabId: tabId
1836
+ }, (response) => {
1837
+ if (chrome.runtime.lastError) {
1838
+ reject(chrome.runtime.lastError);
1839
+ return;
1840
+ }
1841
+ if (response && response.success) {
1842
+ resolve(response.screenshot);
1843
+ } else {
1844
+ reject(new Error(response?.error || 'Failed to capture screenshot'));
1845
+ }
1846
+ });
1847
+ });
1848
+ }
1849
+
1850
+ /**
1851
+ * Show countdown overlay before capturing screenshot
1852
+ */
1853
+ async function showCountdownOverlay(seconds) {
1854
+ const overlay = document.createElement('div');
1855
+ overlay.id = 'chromedebug-countdown-overlay';
1856
+ overlay.style.cssText = `
1857
+ position: fixed;
1858
+ top: 0; left: 0; right: 0; bottom: 0;
1859
+ display: flex;
1860
+ align-items: center;
1861
+ justify-content: center;
1862
+ z-index: 2147483647;
1863
+ pointer-events: none;
1864
+ background: rgba(0,0,0,0.3);
1865
+ `;
1866
+
1867
+ const circle = document.createElement('div');
1868
+ circle.style.cssText = `
1869
+ width: 200px; height: 200px;
1870
+ border-radius: 50%;
1871
+ background: rgba(0,0,0,0.8);
1872
+ display: flex;
1873
+ align-items: center;
1874
+ justify-content: center;
1875
+ color: white;
1876
+ font-size: 120px;
1877
+ font-family: Arial, sans-serif;
1878
+ font-weight: bold;
1879
+ `;
1880
+
1881
+ overlay.appendChild(circle);
1882
+ document.body.appendChild(overlay);
1883
+
1884
+ for (let i = seconds; i > 0; i--) {
1885
+ circle.textContent = i;
1886
+ await new Promise(r => setTimeout(r, 1000));
1887
+ }
1888
+
1889
+ overlay.remove();
1890
+
1891
+ // Wait for browser to complete repaint after overlay removal
1892
+ // This ensures the overlay doesn't appear in the captured screenshot
1893
+ await waitForRepaint();
1894
+ }
1895
+
1896
+ /**
1897
+ * Create screenshot mode indicator
1898
+ */
1899
+ function createScreenshotModeIndicator() {
1900
+ // Remove any existing indicator
1901
+ removeScreenshotModeIndicator();
1902
+
1903
+ const indicator = document.createElement('div');
1904
+ indicator.id = 'chromedebug-screenshot-indicator';
1905
+ indicator.style.cssText = `
1906
+ position: fixed;
1907
+ top: 10px; right: 10px;
1908
+ padding: 8px 16px;
1909
+ background: #FF9800;
1910
+ color: white;
1911
+ border-radius: 20px;
1912
+ font-family: Arial, sans-serif;
1913
+ font-size: 14px;
1914
+ z-index: 2147483647;
1915
+ box-shadow: 0 2px 10px rgba(0,0,0,0.3);
1916
+ display: flex;
1917
+ align-items: center;
1918
+ gap: 8px;
1919
+ `;
1920
+ indicator.innerHTML = `
1921
+ <span style="font-size: 16px;">📸</span>
1922
+ <span>Click to capture</span>
1923
+ <span id="chromedebug-screenshot-count" style="background: rgba(255,255,255,0.3); padding: 2px 8px; border-radius: 10px; font-weight: bold;">0</span>
1924
+ `;
1925
+ document.body.appendChild(indicator);
1926
+ screenshotModeIndicator = indicator;
1927
+ }
1928
+
1929
+ /**
1930
+ * Remove screenshot mode indicator
1931
+ */
1932
+ function removeScreenshotModeIndicator() {
1933
+ if (screenshotModeIndicator) {
1934
+ screenshotModeIndicator.remove();
1935
+ screenshotModeIndicator = null;
1936
+ }
1937
+ // Also try to remove by ID in case reference was lost
1938
+ const existing = document.getElementById('chromedebug-screenshot-indicator');
1939
+ if (existing) {
1940
+ existing.remove();
1941
+ }
1942
+ }
1943
+
1944
+ /**
1945
+ * Update screenshot mode indicator count
1946
+ */
1947
+ function updateScreenshotModeIndicatorCount() {
1948
+ const countElement = document.getElementById('chromedebug-screenshot-count');
1949
+ if (countElement) {
1950
+ countElement.textContent = screenshotModeActions.length;
1951
+ }
1952
+ }
1953
+
1639
1954
  // Capture DOM snapshot with form values and states
1640
1955
  function captureDOMSnapshot() {
1641
1956
  const snapshot = {
@@ -1962,6 +2277,9 @@ async function recordScreenClick(event) {
1962
2277
  xpath: getXPath(element),
1963
2278
  x: event.clientX,
1964
2279
  y: event.clientY,
2280
+ viewportWidth: window.innerWidth, // For scaling during playback
2281
+ viewportHeight: window.innerHeight,
2282
+ devicePixelRatio: window.devicePixelRatio, // Critical for Retina/HiDPI display scaling
1965
2283
  timestamp: Date.now() - recordingScheduledStartTime // Relative timestamp from scheduled start
1966
2284
  };
1967
2285
 
@@ -2159,21 +2477,51 @@ function recordScreenMouseUp(event) {
2159
2477
  screenRecordingDragStartElement = null;
2160
2478
  }
2161
2479
 
2480
+ // Mouse movement tracking (PRO-only feature)
2481
+ function handleMouseMove(e) {
2482
+ const now = Date.now();
2483
+ if (!mouseTrackingEnabled || !isScreenRecording) return;
2484
+ if (now - lastMouseTime < mouseSampleInterval) return;
2485
+
2486
+ lastMouseTime = now;
2487
+ sendScreenInteraction({
2488
+ type: 'mousemove',
2489
+ x: e.clientX,
2490
+ y: e.clientY,
2491
+ pageX: e.pageX,
2492
+ pageY: e.pageY,
2493
+ viewportWidth: window.innerWidth,
2494
+ viewportHeight: window.innerHeight,
2495
+ devicePixelRatio: window.devicePixelRatio, // Critical for Retina/HiDPI display scaling
2496
+ timestamp: now - recordingScheduledStartTime
2497
+ });
2498
+ }
2499
+
2500
+ function startMouseTracking() {
2501
+ if (!mouseTrackingEnabled) return;
2502
+ document.addEventListener('mousemove', handleMouseMove, { passive: true });
2503
+ }
2504
+
2505
+ function stopMouseTracking() {
2506
+ document.removeEventListener('mousemove', handleMouseMove);
2507
+ lastMouseTime = 0;
2508
+ }
2509
+
2162
2510
  // Global variables for synchronized timing
2163
2511
  let recordingScheduledStartTime = null;
2164
2512
  let recordingSessionId = null;
2165
2513
 
2166
2514
  // Start/stop screen interaction tracking
2167
- function startScreenInteractionTracking(scheduledStartTime, sessionId) {
2515
+ function startScreenInteractionTracking(scheduledStartTime, sessionId, videoMode = false) {
2168
2516
  // Store the scheduled start time and session ID for timestamp calculations
2169
2517
  recordingScheduledStartTime = scheduledStartTime;
2170
2518
  recordingSessionId = sessionId;
2171
2519
 
2172
2520
  // console.log('[ChromePilot] Screen interaction tracking scheduled to start at:', new Date(scheduledStartTime));
2173
2521
 
2174
- // Trigger visual feedback start
2522
+ // Trigger visual feedback start (pass videoMode for hidden indicator)
2175
2523
  if (window.screenCaptureVisualFeedback) {
2176
- window.screenCaptureVisualFeedback.handleStartScreenCapture({ sessionId, scheduledStartTime });
2524
+ window.screenCaptureVisualFeedback.handleStartScreenCapture({ sessionId, scheduledStartTime, videoMode });
2177
2525
  }
2178
2526
 
2179
2527
  // Wait until the scheduled start time before activating tracking
@@ -2190,6 +2538,9 @@ function startScreenInteractionTracking(scheduledStartTime, sessionId) {
2190
2538
  document.addEventListener('mousedown', recordScreenMouseDown, true);
2191
2539
  document.addEventListener('mouseup', recordScreenMouseUp, true);
2192
2540
 
2541
+ // Start mouse tracking if enabled (PRO-only feature)
2542
+ startMouseTracking();
2543
+
2193
2544
  // console.log('[ChromePilot] Screen interaction tracking started at scheduled time');
2194
2545
  }, waitTime);
2195
2546
  }
@@ -2197,6 +2548,9 @@ function startScreenInteractionTracking(scheduledStartTime, sessionId) {
2197
2548
  function stopScreenInteractionTracking() {
2198
2549
  isScreenRecording = false;
2199
2550
 
2551
+ // Stop mouse tracking (PRO-only feature)
2552
+ stopMouseTracking();
2553
+
2200
2554
  // Array of cleanup operations for systematic execution
2201
2555
  const cleanupOperations = [
2202
2556
  () => document.removeEventListener('click', recordScreenClick, true),
@@ -2302,6 +2656,11 @@ let screenRecordingDragStartElement = null;
2302
2656
  let screenRecordingDragStartX = 0;
2303
2657
  let screenRecordingDragStartY = 0;
2304
2658
 
2659
+ // Mouse movement tracking (PRO-only feature)
2660
+ let mouseTrackingEnabled = false;
2661
+ let mouseSampleInterval = 100; // User-selectable: 50, 100, or 200ms
2662
+ let lastMouseTime = 0;
2663
+
2305
2664
  // Full data recording system components
2306
2665
  let fullDataRecording = null;
2307
2666
  let dataBuffer = null;
@@ -2937,11 +3296,17 @@ class ScreenCaptureVisualFeedback {
2937
3296
  }
2938
3297
 
2939
3298
  handleStartScreenCapture(message) {
2940
- // console.log('[ScreenCapture] Starting new recording, current indicator exists:', !!this.recordingIndicatorElement);
3299
+ // If we're already in video mode recording and got a redundant start message,
3300
+ // ignore it to prevent re-showing the indicator
3301
+ if (this.isRecording && this.isVideoMode) {
3302
+ console.log('[Video Mode Debug] Already in video mode recording, ignoring redundant start message');
3303
+ return;
3304
+ }
3305
+
2941
3306
  this.isRecording = true;
2942
3307
  this.actionCount = 0;
2943
- this.showRecordingIndicator();
2944
- // Action counter is now part of the unified indicator, initialized to 0 in HTML
3308
+ this.isVideoMode = message.videoMode || false;
3309
+ this.showRecordingIndicator(this.isVideoMode);
2945
3310
  }
2946
3311
 
2947
3312
  handleScreenCaptureInteraction(interaction) {
@@ -3023,7 +3388,15 @@ class ScreenCaptureVisualFeedback {
3023
3388
  // Action counter is now part of unified indicator, handled by hideRecordingIndicator
3024
3389
  }
3025
3390
 
3026
- showRecordingIndicator() {
3391
+ showRecordingIndicator(videoMode = false) {
3392
+ // Store video mode for use in countdown sequence
3393
+ this.isVideoMode = videoMode;
3394
+ console.log('[Video Mode Debug] showRecordingIndicator called with videoMode:', videoMode);
3395
+
3396
+ // VIDEO MODE: Show countdown but hide the control menu after
3397
+ // The countdown (3, 2, 1) will show so user knows when recording starts
3398
+ // After countdown, the indicator is removed (handled in startCountdownSequence)
3399
+
3027
3400
  // CRITICAL FIX: Clear any existing countdown interval FIRST
3028
3401
  if (this.countdownInterval) {
3029
3402
  // console.log('[ScreenCapture] Clearing existing countdown interval');
@@ -3119,22 +3492,39 @@ class ScreenCaptureVisualFeedback {
3119
3492
  startCountdownSequence() {
3120
3493
  let count = 3;
3121
3494
 
3495
+ // Video mode: simpler countdown without action badge
3496
+ const getCountdownHTML = (num) => {
3497
+ if (this.isVideoMode) {
3498
+ return `
3499
+ <span style="color: #FF9800; font-size: 20px; animation: pulse 1.5s infinite;">🎥</span>
3500
+ <span>Video recording in ${num}...</span>
3501
+ `;
3502
+ }
3503
+ return `
3504
+ <span style="color: #FF9800; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
3505
+ <span>Starting in ${num}...</span>
3506
+ <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>
3507
+ `;
3508
+ };
3509
+
3122
3510
  // Initial countdown display
3123
- this.recordingIndicatorElement.innerHTML = `
3124
- <span style="color: #FF9800; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
3125
- <span>Starting in ${count}...</span>
3126
- <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>
3127
- `;
3511
+ this.recordingIndicatorElement.innerHTML = getCountdownHTML(count);
3128
3512
 
3129
3513
  this.countdownInterval = setInterval(() => {
3130
3514
  count--;
3515
+
3516
+ // VIDEO MODE: Hide indicator at count=1 (before final frame) to ensure it's not captured
3517
+ // Use visibility+opacity for immediate hiding without layout reflow
3518
+ if (count === 1 && this.isVideoMode && this.recordingIndicatorElement) {
3519
+ this.recordingIndicatorElement.style.visibility = 'hidden';
3520
+ this.recordingIndicatorElement.style.opacity = '0';
3521
+ }
3522
+
3131
3523
  if (count > 0) {
3132
- // Update countdown
3133
- this.recordingIndicatorElement.innerHTML = `
3134
- <span style="color: #FF9800; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
3135
- <span>Starting in ${count}...</span>
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>
3137
- `;
3524
+ // Update countdown (but already hidden for video mode at count=1)
3525
+ if (!this.isVideoMode || count > 1) {
3526
+ this.recordingIndicatorElement.innerHTML = getCountdownHTML(count);
3527
+ }
3138
3528
  } else {
3139
3529
  // Countdown complete! Notify background to start inactivity monitoring NOW
3140
3530
  if (isExtensionValid()) {
@@ -3143,6 +3533,22 @@ class ScreenCaptureVisualFeedback {
3143
3533
  });
3144
3534
  }
3145
3535
 
3536
+ // VIDEO MODE: Hide indicator completely after countdown
3537
+ // User must stop recording via popup (opens from browser extension icon)
3538
+ console.log('[Video Mode Debug] Countdown complete. isVideoMode:', this.isVideoMode);
3539
+ if (this.isVideoMode) {
3540
+ console.log('[Video Mode Debug] HIDING mini controller for video mode');
3541
+ clearInterval(this.countdownInterval);
3542
+ this.countdownInterval = null;
3543
+ // Remove indicator element completely
3544
+ if (this.recordingIndicatorElement && this.recordingIndicatorElement.parentNode) {
3545
+ this.recordingIndicatorElement.parentNode.removeChild(this.recordingIndicatorElement);
3546
+ this.recordingIndicatorElement = null;
3547
+ console.log('[Video Mode Debug] Mini controller REMOVED from DOM');
3548
+ }
3549
+ return; // Exit early, no need to show recording UI
3550
+ }
3551
+
3146
3552
  // Switch to recording display with two-row layout
3147
3553
  this.recordingIndicatorElement.style.pointerEvents = 'auto'; // Enable clicking
3148
3554
  this.recordingIndicatorElement.innerHTML = `
@@ -19,7 +19,7 @@ const FIREBASE_CONFIG = {
19
19
  const FUNCTIONS_URL = `https://us-central1-${FIREBASE_CONFIG.projectId}.cloudfunctions.net`;
20
20
 
21
21
  // LemonSqueezy checkout URL (Production)
22
- const LEMONSQUEEZY_CHECKOUT_URL = 'https://chromedebug.com/buy/996773cb-682b-430f-b9e3-9ce2130bd967';
22
+ const LEMONSQUEEZY_CHECKOUT_URL = 'https://chromedebug.com/checkout/buy/996773cb-682b-430f-b9e3-9ce2130bd967';
23
23
 
24
24
  // Make available globally for service worker context
25
25
  if (typeof self !== 'undefined') {
@@ -24,7 +24,7 @@ const FIREBASE_CONFIG = {
24
24
  const FUNCTIONS_URL = `https://us-central1-${FIREBASE_CONFIG.projectId}.cloudfunctions.net`;
25
25
 
26
26
  // LemonSqueezy checkout URL (Production)
27
- const LEMONSQUEEZY_CHECKOUT_URL = 'https://chromedebug.com/buy/996773cb-682b-430f-b9e3-9ce2130bd967';
27
+ const LEMONSQUEEZY_CHECKOUT_URL = 'https://chromedebug.com/checkout/buy/996773cb-682b-430f-b9e3-9ce2130bd967';
28
28
 
29
29
  // Export for ES6 modules (popup.js, firebase-client.js)
30
30
  export { FIREBASE_CONFIG, FUNCTIONS_URL, LEMONSQUEEZY_CHECKOUT_URL };