From 6536d23ef6608bb1f24ee87e775999b4b305eb3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 10:39:21 +0000 Subject: [PATCH 1/4] Initial plan From 33499bd528938888e95b8b17e2936b6833257ef2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 10:43:15 +0000 Subject: [PATCH 2/4] Add debug mode feature with hover tooltips for element class names Co-authored-by: paulpv <1393897+paulpv@users.noreply.github.com> --- content.js | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++- popup.html | 6 +++ popup.js | 16 +++++++- 3 files changed, 126 insertions(+), 4 deletions(-) diff --git a/content.js b/content.js index e19f7c0..8e2040f 100644 --- a/content.js +++ b/content.js @@ -10,6 +10,7 @@ const DEFAULT_PITCH = 1; const DEFAULT_VERBOSITY = 'highlights'; // Default verbosity: Highlights & Summary const DEFAULT_NEW_ONLY = true; // Default: skip pre-existing content const GRACE_PERIOD_MS = 2000; // Time window to consider content as pre-existing (milliseconds) +const DEFAULT_DEBUG_MODE = false; // Default: debug mode off // State management let spokenItems = []; @@ -26,6 +27,7 @@ let speechPitch = DEFAULT_PITCH; // Current speech pitch let speechVerbosity = DEFAULT_VERBOSITY; // Current speech verbosity: 'all', 'highlights', or 'summary' let newOnlyMode = DEFAULT_NEW_ONLY; // Whether to skip pre-existing content let extensionInitTime = Date.now(); // Track when extension initialized to filter old content +let debugMode = DEFAULT_DEBUG_MODE; // Whether debug mode is enabled // Initialize voices function initVoices() { @@ -42,8 +44,8 @@ function initVoices() { selectedVoice = voices.find(v => v.name === DESIRED_VOICE_NAME) || voices[0]; console.log(`${TAG}: initVoices: Using voice: ${selectedVoice.name}`); - // Load saved rate, pitch, verbosity, and newOnly from storage - chrome.storage.sync.get(['speechRate', 'speechPitch', 'speechVerbosity', 'newOnly'], function(result) { + // Load saved rate, pitch, verbosity, newOnly, and debugMode from storage + chrome.storage.sync.get(['speechRate', 'speechPitch', 'speechVerbosity', 'newOnly', 'debugMode'], function(result) { if (result.speechRate !== undefined) { speechRate = result.speechRate; console.log(`${TAG}: Loaded speech rate: ${speechRate}`); @@ -60,6 +62,14 @@ function initVoices() { newOnlyMode = result.newOnly; console.log(`${TAG}: Loaded new only mode: ${newOnlyMode}`); } + if (result.debugMode !== undefined) { + debugMode = result.debugMode; + console.log(`${TAG}: Loaded debug mode: ${debugMode}`); + // Apply debug mode if it was saved as enabled + if (debugMode) { + applyDebugMode(); + } + } }); } @@ -266,6 +276,88 @@ function shouldSpeakElement(element, container) { return !hasParentWithClass(element, container, 'Tool-module__detailsContainer'); } +// Apply debug mode tooltips to all divs under TaskChat container +function applyDebugMode() { + console.log(`${TAG}: Applying debug mode tooltips`); + + const taskChatContainer = document.querySelector('[class*="TaskChat-module__stickableContainer--"]'); + if (!taskChatContainer) { + console.log(`${TAG}: TaskChat container not found for debug mode`); + return; + } + + // Find all div elements under the TaskChat container + const divs = taskChatContainer.querySelectorAll('div'); + console.log(`${TAG}: Found ${divs.length} div elements to apply tooltips`); + + divs.forEach(div => { + applyDebugTooltipToElement(div); + }); +} + +// Apply debug tooltip to a single element +function applyDebugTooltipToElement(element) { + if (element.nodeType !== Node.ELEMENT_NODE || element.tagName !== 'DIV') { + return; + } + + // Get all class names + const classNames = element.className; + if (classNames) { + // Set the title attribute with the class names + element.setAttribute('data-tts-debug-tooltip', 'true'); + element.setAttribute('title', classNames); + } +} + +// Apply debug mode tooltips to an element and all its div children +function applyDebugModeToElement(element) { + if (!debugMode) { + return; + } + + // Apply to the element itself if it's a div + applyDebugTooltipToElement(element); + + // Apply to all div children + const divs = element.querySelectorAll ? element.querySelectorAll('div') : []; + divs.forEach(div => { + applyDebugTooltipToElement(div); + }); +} + +// Remove debug mode tooltips +function removeDebugMode() { + console.log(`${TAG}: Removing debug mode tooltips`); + + const taskChatContainer = document.querySelector('[class*="TaskChat-module__stickableContainer--"]'); + if (!taskChatContainer) { + console.log(`${TAG}: TaskChat container not found for debug mode removal`); + return; + } + + // Find all divs with debug tooltips + const divs = taskChatContainer.querySelectorAll('div[data-tts-debug-tooltip="true"]'); + console.log(`${TAG}: Removing tooltips from ${divs.length} div elements`); + + divs.forEach(div => { + div.removeAttribute('data-tts-debug-tooltip'); + div.removeAttribute('title'); + }); +} + +// Toggle debug mode +function toggleDebugMode(enabled) { + debugMode = enabled; + console.log(`${TAG}: Debug mode ${enabled ? 'enabled' : 'disabled'}`); + + if (enabled) { + applyDebugMode(); + } else { + removeDebugMode(); + } +} + // Helper function to add a spoken item if not already tracked function addSpokenItem(text, element) { if (text && !spokenItems.some(item => item.text === text)) { @@ -368,6 +460,9 @@ function processSessionContainer(sessionContainer) { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { + // Apply debug mode to new node if enabled + applyDebugModeToElement(node); + // Check if this node or its children contain markdown containers let newMarkdownContainers = []; if (node.matches && node.matches('[class*="MarkdownRenderer-module__container--"]')) { @@ -483,6 +578,9 @@ function monitorTaskChat() { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { + // Apply debug mode to new node if enabled + applyDebugModeToElement(node); + // Check if this is a session container if (node.classList && Array.from(node.classList).some(c => c.includes('Session-module__detailsContainer--'))) { //console.log(`${TAG}: Found new session container element`); @@ -646,6 +744,12 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { sendResponse({ success: true }); break; + case 'setDebugMode': + // Update debug mode + toggleDebugMode(message.debugMode ?? DEFAULT_DEBUG_MODE); + sendResponse({ success: true }); + break; + case 'jumpTo': // Jump to a specific item in the spokenItems array const targetIndex = message.index; diff --git a/popup.html b/popup.html index 146ff44..c0151e1 100644 --- a/popup.html +++ b/popup.html @@ -175,6 +175,12 @@

Copilot TTS

New Only (skip existing content) +
+ +
diff --git a/popup.js b/popup.js index ead8a26..5c408d0 100644 --- a/popup.js +++ b/popup.js @@ -17,6 +17,7 @@ document.addEventListener('DOMContentLoaded', function() { const progressLabel = document.getElementById('progressLabel'); const verbositySelect = document.getElementById('verbositySelect'); const newOnlyCheckbox = document.getElementById('newOnlyCheckbox'); + const debugModeCheckbox = document.getElementById('debugModeCheckbox'); // Helper function to send message to content script async function sendMessageToActiveTab(message) { @@ -182,8 +183,8 @@ document.addEventListener('DOMContentLoaded', function() { setTimeout(refreshStatus, 2000); }); - // Load saved rate, pitch, verbosity, and newOnly values - chrome.storage.sync.get(['speechRate', 'speechPitch', 'speechVerbosity', 'newOnly'], function(result) { + // Load saved rate, pitch, verbosity, newOnly, and debugMode values + chrome.storage.sync.get(['speechRate', 'speechPitch', 'speechVerbosity', 'newOnly', 'debugMode'], function(result) { if (result.speechRate !== undefined) { rateSlider.value = result.speechRate; rateValue.textContent = result.speechRate + 'x'; @@ -198,6 +199,9 @@ document.addEventListener('DOMContentLoaded', function() { if (result.newOnly !== undefined) { newOnlyCheckbox.checked = result.newOnly; } + if (result.debugMode !== undefined) { + debugModeCheckbox.checked = result.debugMode; + } }); // Rate slider handler @@ -250,6 +254,14 @@ document.addEventListener('DOMContentLoaded', function() { console.log(`${TAG}: New Only set to: ${newOnly}`); }); + // Debug Mode checkbox handler + debugModeCheckbox.addEventListener('change', function() { + const debugMode = debugModeCheckbox.checked; + chrome.storage.sync.set({ debugMode: debugMode }); + sendMessageToActiveTab({ action: 'setDebugMode', debugMode: debugMode }); + console.log(`${TAG}: Debug Mode set to: ${debugMode}`); + }); + // Initial status check refreshStatus(); From d3dc7daf5693e2678e96a14179098d6875af9ba5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 10:46:37 +0000 Subject: [PATCH 3/4] Improve debug mode: show tooltips for divs without classes and optimize selector Co-authored-by: paulpv <1393897+paulpv@users.noreply.github.com> --- content.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/content.js b/content.js index 8e2040f..adf948b 100644 --- a/content.js +++ b/content.js @@ -303,11 +303,11 @@ function applyDebugTooltipToElement(element) { // Get all class names const classNames = element.className; - if (classNames) { - // Set the title attribute with the class names - element.setAttribute('data-tts-debug-tooltip', 'true'); - element.setAttribute('title', classNames); - } + const tooltipText = classNames || `
(no classes)`; + + // Set the title attribute with the class names or indication of no classes + element.setAttribute('data-tts-debug-tooltip', 'true'); + element.setAttribute('title', tooltipText); } // Apply debug mode tooltips to an element and all its div children @@ -336,13 +336,13 @@ function removeDebugMode() { return; } - // Find all divs with debug tooltips - const divs = taskChatContainer.querySelectorAll('div[data-tts-debug-tooltip="true"]'); - console.log(`${TAG}: Removing tooltips from ${divs.length} div elements`); + // Find all elements with debug tooltips (more efficient selector) + const elements = taskChatContainer.querySelectorAll('[data-tts-debug-tooltip="true"]'); + console.log(`${TAG}: Removing tooltips from ${elements.length} elements`); - divs.forEach(div => { - div.removeAttribute('data-tts-debug-tooltip'); - div.removeAttribute('title'); + elements.forEach(element => { + element.removeAttribute('data-tts-debug-tooltip'); + element.removeAttribute('title'); }); } From 21758ff4fdcadb511165ef7e9ac37702b64ef297 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 10:48:39 +0000 Subject: [PATCH 4/4] Add comprehensive documentation for debug mode feature Co-authored-by: paulpv <1393897+paulpv@users.noreply.github.com> --- README.md | 28 ++++++++++++++++++++++++++++ TESTING.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/README.md b/README.md index 7809583..0295249 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ A Chrome browser extension that monitors GitHub Copilot Tasks pages and speaks t - **Highlights & Summary** (default): Speaks Copilot responses and summaries, excludes tool logs - **Summary Only**: Speaks only final summary messages from Copilot - **New Only mode** (checkbox, enabled by default): Skip pre-existing content and only speak new content that appears after extension loads +- **Debug Mode** (checkbox, disabled by default): Shows element class names on hover for troubleshooting and understanding content structure - Adjustable speech rate (0.5x to 2x, default 1.2x) - Adjustable speech pitch (0.5x to 2x, default 1.0x) - Speech queue with 2-second delays between items for better pacing @@ -70,12 +71,39 @@ When new text content is detected, it is queued for speaking. After the first us - **Highlights & Summary**: Speaks Copilot responses and summaries (default) - **Summary Only**: Speaks only final summary messages - **New Only**: Checkbox to skip pre-existing content (enabled by default) + - **Debug Mode**: Checkbox to show element class names on hover (disabled by default, useful for troubleshooting) - **Speed Slider**: Adjust speech rate (0.5x to 2x, default 1.2x) - **Pitch Slider**: Adjust speech pitch (0.5x to 2x, default 1.0x) 6. The status shows your current position (e.g., "Item 3 of 10") **Note:** The extension requires a user interaction (click or keypress) before it can speak. This is a browser security requirement. Once you interact with the page, all queued content will be spoken automatically with a 2-second delay between items. Elements being spoken are highlighted with a yellow background. +## Debug Mode + +The **Debug Mode** feature helps understand what content is being spoken and troubleshoot issues by showing element class names on hover. + +**To enable Debug Mode:** +1. Click the extension icon to open the popup +2. Check the "Debug Mode (show class names on hover)" checkbox +3. Close the popup and hover over any div element on the page + +**Features:** +- Shows native browser tooltips with element class names when hovering over divs +- Displays `
(no classes)` for elements without classes +- Helps identify key containers like: + - `TaskChat-module__stickableContainer--*` + - `Session-module__detailsContainer--*` + - `MarkdownRenderer-module__container--*` + - `Tool-module__detailsContainer--*` +- Automatically applies to dynamically added content +- Setting persists across page reloads and browser sessions + +**Use Cases:** +- Understanding GitHub Copilot's DOM structure +- Troubleshooting speech issues (e.g., why certain content is/isn't spoken) +- Identifying which elements are being targeted for speech +- Debugging the extension's behavior with new GitHub UI updates + ## File Structure ``` diff --git a/TESTING.md b/TESTING.md index f5b1b41..fb6e694 100644 --- a/TESTING.md +++ b/TESTING.md @@ -297,3 +297,51 @@ 3. **"Not on Copilot Tasks page" in popup**: Verify URL matches `https://github.com/copilot/tasks/*` 4. **"Waiting for interaction" status**: Click anywhere on the page or press any key to enable speech 5. **Speech queue not progressing**: Check if playback is paused (button shows "▶ Play"); click to resume + +## Test 22: Debug Mode Feature +**Steps:** +1. Navigate to a Copilot task page +2. Open the extension popup +3. Check the "Debug Mode (show class names on hover)" checkbox +4. Close the popup +5. Hover over various div elements on the page + +**Expected Results:** +- Console shows: "CopilotTTS-Content: Debug mode enabled" +- Console shows: "CopilotTTS-Content: Applying debug mode tooltips" +- Console shows: "CopilotTTS-Content: Found X div elements to apply tooltips" +- Hovering over div elements shows native browser tooltips with class names +- Elements without classes show "
(no classes)" in tooltip +- Key containers show their full class names (e.g., "Session-module__detailsContainer--abc123") + +**Steps to Disable:** +1. Open the popup again +2. Uncheck the "Debug Mode" checkbox + +**Expected Results:** +- Console shows: "CopilotTTS-Content: Debug mode disabled" +- Console shows: "CopilotTTS-Content: Removing debug mode tooltips" +- Tooltips are removed from all elements +- Hovering no longer shows class names + +**Steps to Test Persistence:** +1. Enable debug mode via checkbox +2. Close and reopen the popup +3. Reload the page + +**Expected Results:** +- Debug mode checkbox remains checked after reopening popup +- Debug mode is automatically applied after page reload +- Console shows: "CopilotTTS-Content: Loaded debug mode: true" +- Tooltips appear automatically without needing to toggle again + +**Steps to Test Dynamic Content:** +1. Enable debug mode +2. Type a message and submit to Copilot +3. Wait for new content to appear +4. Hover over the new div elements + +**Expected Results:** +- New content also has tooltips applied automatically +- No need to re-enable debug mode for new elements +- All dynamically added divs get tooltips