From f1a0d1a9f8e8a0b4c6420399c84b4fb96a20afe0 Mon Sep 17 00:00:00 2001 From: Francesco Renzi Date: Thu, 15 Jan 2026 21:26:43 +0000 Subject: [PATCH] wip --- browser-ext/content/content.css | 30 ++++++++ browser-ext/content/content.js | 120 +++++++++++++++++++++++++++----- 2 files changed, 133 insertions(+), 17 deletions(-) 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'); + } + } } }); }