diff --git a/browser-ext/content/content.css b/browser-ext/content/content.css
index da70556ee..4e3c432dc 100644
--- a/browser-ext/content/content.css
+++ b/browser-ext/content/content.css
@@ -305,3 +305,33 @@ html[data-color-mode="light"] .dap-repl-output,
html[data-color-mode="light"] .dap-repl-input input {
background-color: #f6f8fa !important;
}
+
+/* Debug Button in Header */
+.dap-debug-btn-container {
+ display: flex;
+ align-items: center;
+}
+
+.dap-debug-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.dap-debug-btn.selected {
+ background-color: var(--bgColor-accent-muted, #388bfd26);
+ border-color: var(--borderColor-accent-emphasis, #388bfd);
+}
+
+.dap-debug-btn:hover:not(:disabled) {
+ background-color: var(--bgColor-neutral-muted, #6e768166);
+}
+
+/* Light mode for debug button */
+[data-color-mode="light"] .dap-debug-btn.selected,
+html[data-color-mode="light"] .dap-debug-btn.selected {
+ background-color: #ddf4ff;
+ border-color: #54aeff;
+}
diff --git a/browser-ext/content/content.js b/browser-ext/content/content.js
index 1aa06d6df..2be7acdc1 100644
--- a/browser-ext/content/content.js
+++ b/browser-ext/content/content.js
@@ -19,6 +19,14 @@ function escapeHtml(text) {
return div.innerHTML;
}
+/**
+ * Strip result indicator suffix from step name
+ * e.g., "Run tests [running]" -> "Run tests"
+ */
+function stripResultIndicator(name) {
+ return name.replace(/\s*\[(running|success|failure|skipped|cancelled)\]$/i, '');
+}
+
/**
* Send DAP request to background script
*/
@@ -236,7 +244,9 @@ async function handleReplKeydown(e) {
frameId: currentFrameId,
context: command.startsWith('!') ? 'repl' : 'watch',
});
- if (response.result) {
+ // Only show result if it's NOT an exit code summary
+ // (shell command output is already streamed via output events)
+ if (response.result && !/^\(exit code: -?\d+\)$/.test(response.result)) {
appendOutput(response.result, 'result');
}
} catch (error) {
@@ -359,15 +369,26 @@ async function loadScopes(frameId) {
scopesContainer.innerHTML = '
Loading...
';
try {
+ console.log('[Content] Loading scopes for frame:', frameId);
const response = await sendDapRequest('scopes', { frameId });
+ console.log('[Content] Scopes response:', response);
scopesContainer.innerHTML = '';
+ if (!response.scopes || response.scopes.length === 0) {
+ scopesContainer.innerHTML = 'No scopes available
';
+ return;
+ }
+
for (const scope of response.scopes) {
- const node = createTreeNode(scope.name, scope.variablesReference, true);
+ console.log('[Content] Creating tree node for scope:', scope.name, 'variablesRef:', scope.variablesReference);
+ // Only mark as expandable if variablesReference > 0
+ const isExpandable = scope.variablesReference > 0;
+ const node = createTreeNode(scope.name, scope.variablesReference, isExpandable);
scopesContainer.appendChild(node);
}
} catch (error) {
+ console.error('[Content] Failed to load scopes:', error);
scopesContainer.innerHTML = `Error: ${escapeHtml(error.message)}
`;
}
}
@@ -476,24 +497,30 @@ async function handleStoppedEvent(body) {
const currentFrame = stackTrace.stackFrames[0];
currentFrameId = currentFrame.id;
- // Find the step element and move pane
- const stepName = currentFrame.name;
- const stepElement = findStepByName(stepName);
+ // Strip result indicator from step name for DOM lookup
+ // e.g., "Run tests [running]" -> "Run tests"
+ const rawStepName = stripResultIndicator(currentFrame.name);
+ let stepElement = findStepByName(rawStepName);
+
+ if (!stepElement) {
+ // Fallback: use step index
+ // Note: GitHub Actions UI shows "Set up job" at index 0, which is not a real workflow step
+ // DAP uses 1-based frame IDs, so frame ID 1 maps to UI step index 1 (skipping "Set up job")
+ const steps = getAllSteps();
+ const adjustedIndex = currentFrame.id; // 1-based, happens to match after skipping "Set up job"
+ if (adjustedIndex > 0 && adjustedIndex < steps.length) {
+ stepElement = steps[adjustedIndex];
+ }
+ }
if (stepElement) {
- moveDebuggerPane(stepElement, stepName);
- } else {
- // Try to find by frame id as step index
- const steps = getAllSteps();
- if (steps[currentFrame.id]) {
- moveDebuggerPane(steps[currentFrame.id], stepName);
- }
+ moveDebuggerPane(stepElement, rawStepName);
}
// Update step counter
const counter = debuggerPane?.querySelector('.dap-step-counter');
if (counter) {
- counter.textContent = `Step ${currentFrame.id + 1} of ${stackTrace.stackFrames.length}`;
+ counter.textContent = `Step ${currentFrame.id} of ${stackTrace.stackFrames.length}`;
}
// Load scopes
@@ -599,6 +626,56 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
}
});
+/**
+ * Inject debug button into GitHub Actions UI header
+ */
+function injectDebugButton() {
+ const container = document.querySelector('.js-check-run-search');
+ if (!container || container.querySelector('.dap-debug-btn-container')) {
+ return; // Already injected or container not found
+ }
+
+ const buttonContainer = document.createElement('div');
+ buttonContainer.className = 'ml-2 dap-debug-btn-container';
+ buttonContainer.innerHTML = `
+
+ `;
+
+ const button = buttonContainer.querySelector('button');
+ button.addEventListener('click', () => {
+ let pane = document.querySelector('.dap-debugger-pane');
+ if (pane) {
+ // Toggle visibility
+ pane.hidden = !pane.hidden;
+ button.classList.toggle('selected', !pane.hidden);
+ } else {
+ // Create and show pane
+ pane = injectDebuggerPane();
+ if (pane) {
+ button.classList.add('selected');
+ // Check connection status after creating pane
+ chrome.runtime.sendMessage({ type: 'get-status' }, (response) => {
+ if (response && response.status) {
+ handleStatusChange(response.status);
+ }
+ });
+ }
+ }
+ });
+
+ // Insert at the beginning of the container
+ container.insertBefore(buttonContainer, container.firstChild);
+ console.log('[Content] Debug button injected');
+}
+
/**
* Initialize content script
*/
@@ -614,21 +691,30 @@ function init() {
const steps = getAllSteps();
if (steps.length > 0) {
observer.disconnect();
- console.log('[Content] Steps found, injecting debugger pane');
- injectDebuggerPane();
+ console.log('[Content] Steps found, injecting debug button');
+ injectDebugButton();
}
});
observer.observe(document.body, { childList: true, subtree: true });
return;
}
- // Inject debugger pane
- injectDebuggerPane();
+ // Inject debug button in header (user can click to show debugger pane)
+ injectDebugButton();
// Check current connection status
chrome.runtime.sendMessage({ type: 'get-status' }, (response) => {
if (response && response.status) {
handleStatusChange(response.status);
+ // If already connected/paused, auto-show the debugger pane
+ if (response.status === 'paused' || response.status === 'connected') {
+ const pane = document.querySelector('.dap-debugger-pane');
+ if (!pane) {
+ injectDebuggerPane();
+ const btn = document.querySelector('.dap-debug-btn');
+ if (btn) btn.classList.add('selected');
+ }
+ }
}
});
}