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
diff --git a/content.js b/content.js
index e19f7c0..adf948b 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;
+ 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
+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 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`);
+
+ elements.forEach(element => {
+ element.removeAttribute('data-tts-debug-tooltip');
+ element.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 @@