extension ui improvements

This commit is contained in:
Francesco Renzi
2026-01-21 21:29:35 +00:00
committed by GitHub
parent 11ca211a3a
commit 38514d5278
3 changed files with 908 additions and 198 deletions

View File

@@ -1,25 +1,142 @@
/**
* Content Script Styles
*
*
* Matches GitHub's Primer design system for seamless integration.
* Uses CSS custom properties for light/dark mode support.
*
* Supports two layout modes:
* - Bottom panel: Fixed at bottom of viewport (like browser DevTools)
* - Sidebar: Fixed on right side of viewport
*/
/* Debugger Pane Container */
/* ==========================================================================
Base Debugger Pane Styles
========================================================================== */
.dap-debugger-pane {
background-color: var(--bgColor-default, #0d1117);
border-color: var(--borderColor-default, #30363d) !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
font-size: 14px;
z-index: 999;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
}
/* Header */
.dap-header {
/* ==========================================================================
Bottom Panel Layout
========================================================================== */
/* Add padding to body when bottom panel is open so all content is accessible */
body.dap-bottom-panel-active {
padding-bottom: 360px !important; /* Slightly more than panel height for breathing room */
}
.dap-debugger-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 350px;
border-top: 1px solid var(--borderColor-default, #30363d);
display: flex;
flex-direction: column;
}
.dap-debugger-bottom .dap-header {
background-color: var(--bgColor-muted, #161b22);
flex-shrink: 0;
padding: 8px 12px;
}
.dap-header .octicon {
color: var(--fgColor-muted, #8b949e);
.dap-debugger-bottom .dap-content {
flex: 1;
min-height: 0;
display: flex;
}
.dap-debugger-bottom .dap-scopes {
width: 33%;
min-width: 200px;
max-width: 400px;
display: flex;
flex-direction: column;
border-right: 1px solid var(--borderColor-default, #30363d);
}
.dap-debugger-bottom .dap-repl {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.dap-debugger-bottom .dap-controls {
gap: 4px;
}
/* ==========================================================================
Sidebar Layout
========================================================================== */
.dap-debugger-sidebar {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 350px;
border-left: 1px solid var(--borderColor-default, #30363d);
display: flex;
flex-direction: column;
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3);
}
.dap-debugger-sidebar .dap-header {
background-color: var(--bgColor-muted, #161b22);
flex-shrink: 0;
padding: 8px 12px;
}
.dap-debugger-sidebar .dap-scopes {
flex: 0 0 auto;
max-height: 40%;
min-height: 150px;
display: flex;
flex-direction: column;
border-bottom: 1px solid var(--borderColor-default, #30363d);
}
.dap-debugger-sidebar .dap-repl {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.dap-debugger-sidebar .dap-controls {
background-color: var(--bgColor-muted, #161b22);
flex-shrink: 0;
gap: 8px;
justify-content: center;
}
/* ==========================================================================
Header Styles
========================================================================== */
.dap-header {
display: flex;
align-items: center;
border-bottom: 1px solid var(--borderColor-default, #30363d);
}
.dap-header .text-bold {
font-weight: 600;
color: var(--fgColor-default, #e6edf3);
}
.dap-header-right {
display: flex;
align-items: center;
}
.dap-step-info {
@@ -27,57 +144,135 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--fgColor-muted, #8b949e);
font-size: 12px;
}
/* Status Labels */
.dap-status-label {
flex-shrink: 0;
/* ==========================================================================
Layout Toggle Buttons
========================================================================== */
.dap-layout-toggles {
display: flex;
gap: 2px;
background-color: var(--bgColor-inset, #010409);
border-radius: 6px;
padding: 2px;
}
.Label--attention {
background-color: #9e6a03 !important;
color: #ffffff !important;
.dap-layout-btn {
padding: 4px 6px !important;
background-color: transparent !important;
border: none !important;
color: var(--fgColor-muted, #8b949e) !important;
border-radius: 4px !important;
display: flex;
align-items: center;
justify-content: center;
}
.Label--success {
background-color: #238636 !important;
color: #ffffff !important;
.dap-layout-btn:hover {
background-color: var(--bgColor-neutral-muted, #6e768166) !important;
color: var(--fgColor-default, #e6edf3) !important;
}
.dap-layout-btn.active {
background-color: var(--bgColor-accent-muted, #388bfd26) !important;
color: var(--fgColor-accent, #58a6ff) !important;
}
.dap-layout-btn svg {
width: 14px;
height: 14px;
}
/* ==========================================================================
Close Button
========================================================================== */
.dap-close-btn {
padding: 4px 6px !important;
background-color: transparent !important;
border: none !important;
color: var(--fgColor-muted, #8b949e) !important;
display: flex;
align-items: center;
justify-content: center;
}
.Label--danger {
background-color: #da3633 !important;
color: #ffffff !important;
border: none !important;
.dap-close-btn:hover {
background-color: var(--bgColor-danger-muted, #da363326) !important;
color: var(--fgColor-danger, #f85149) !important;
}
.Label--secondary {
background-color: #30363d !important;
color: #8b949e !important;
border: none !important;
.dap-close-btn svg {
width: 16px;
height: 16px;
}
/* Content Area */
.dap-content {
min-height: 200px;
max-height: 400px;
/* ==========================================================================
Control Buttons
========================================================================== */
.dap-controls {
display: flex;
align-items: center;
padding: 8px;
}
/* Scopes Panel */
.dap-control-btn {
width: 32px;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 !important;
margin-right: 4px;
}
.dap-control-btn:last-child {
margin-right: 0;
}
.dap-control-btn svg {
width: 16px;
height: 16px;
display: block;
margin: auto !important;
}
.dap-control-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.dap-control-btn:not(:disabled):hover {
background-color: var(--bgColor-accent-muted, #388bfd26);
}
/* ==========================================================================
Scopes Panel
========================================================================== */
.dap-scopes {
border-color: var(--borderColor-default, #30363d) !important;
min-width: 150px;
overflow: hidden;
}
.dap-scope-header {
background-color: var(--bgColor-muted, #161b22);
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
padding: 8px;
border-bottom: 1px solid var(--borderColor-default, #30363d);
}
.dap-scope-tree {
font-size: 12px;
line-height: 1.6;
padding: 8px;
overflow-y: auto;
flex: 1;
}
/* Tree Nodes */
@@ -123,16 +318,23 @@
word-break: break-word;
}
/* REPL Console */
/* ==========================================================================
REPL Console
========================================================================== */
.dap-repl {
display: flex;
flex-direction: column;
overflow: hidden;
}
.dap-repl-header {
background-color: var(--bgColor-muted, #161b22);
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
padding: 8px;
border-bottom: 1px solid var(--borderColor-default, #30363d);
}
.dap-repl-output {
@@ -143,7 +345,7 @@
padding: 8px;
flex: 1;
overflow-y: auto;
min-height: 100px;
min-height: 60px;
}
.dap-output-input {
@@ -165,6 +367,8 @@
/* REPL Input */
.dap-repl-input {
flex-shrink: 0;
padding: 8px;
border-top: 1px solid var(--borderColor-default, #30363d);
}
.dap-repl-input input {
@@ -174,6 +378,9 @@
border-color: var(--borderColor-default, #30363d) !important;
color: var(--fgColor-default, #e6edf3) !important;
width: 100%;
padding: 6px 8px;
border-radius: 6px;
border: 1px solid var(--borderColor-default, #30363d);
}
.dap-repl-input input:focus {
@@ -191,51 +398,112 @@
color: var(--fgColor-muted, #8b949e);
}
/* Control Buttons */
.dap-controls {
background-color: var(--bgColor-muted, #161b22);
/* ==========================================================================
Breakpoint Indicator
========================================================================== */
/* Current step indicator - shown on the step where debugger is paused
Breakpoints are BEFORE steps, so we indicate the boundary above the step */
check-step.dap-current-step {
position: relative;
}
.dap-controls button {
min-width: 32px;
height: 28px;
/* Highlight the step row with a subtle background and red accent line at TOP */
check-step.dap-current-step > details,
check-step.dap-current-step > div {
position: relative;
}
check-step.dap-current-step > details > summary,
check-step.dap-current-step > div:first-child {
background: linear-gradient(90deg, rgba(248, 81, 73, 0.08) 0%, rgba(248, 81, 73, 0.02) 100%) !important;
border-top: 2px solid #f85149 !important;
}
/* Red accent bar at top of step - horizontal line showing "breakpoint here" */
check-step.dap-current-step::after {
content: '';
position: absolute;
left: 0;
right: 0;
top: 0;
height: 2px;
background: linear-gradient(90deg, #f85149 0%, #da3633 100%);
}
/* Triangle pointer at top of step - points to the boundary between steps */
check-step.dap-current-step::before {
content: '';
position: absolute;
right: -8px;
top: 0;
transform: translateY(-50%);
width: 0;
height: 0;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-left: 10px solid #f85149;
z-index: 100;
filter: drop-shadow(2px 0 2px rgba(0, 0, 0, 0.2));
}
/* ==========================================================================
Debug Button in Header
========================================================================== */
.dap-debug-btn-container {
display: flex;
align-items: center;
margin-right: 8px;
}
.dap-debug-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 8px;
gap: 4px;
font-weight: 500;
padding: 5px 12px;
height: 32px;
}
.dap-controls button svg {
width: 14px;
height: 14px;
.dap-debug-btn-text {
font-size: var(--text-body-size-medium, .875rem);
}
.dap-controls button:disabled {
opacity: 0.4;
cursor: not-allowed;
.dap-debug-btn .octicon {
width: 16px;
height: 16px;
}
.dap-controls button:not(:disabled):hover {
.dap-debug-btn.selected {
background-color: var(--bgColor-accent-muted, #388bfd26);
border-color: var(--borderColor-accent-emphasis, #388bfd);
color: var(--fgColor-accent, #58a6ff);
}
.dap-step-counter {
flex-shrink: 0;
.dap-debug-btn:hover:not(:disabled) {
background-color: var(--bgColor-neutral-muted, #6e768166);
}
/* Utility Classes (in case GitHub's aren't loaded) */
/* ==========================================================================
Utility Classes
========================================================================== */
.d-flex { display: flex; }
.flex-column { flex-direction: column; }
.flex-items-center { align-items: center; }
.flex-auto { flex: 1 1 auto; }
.justify-content-center { justify-content: center; }
.p-2 { padding: 8px; }
.px-2 { padding-left: 8px; padding-right: 8px; }
.mx-2 { margin-left: 8px; margin-right: 8px; }
.mb-2 { margin-bottom: 8px; }
.ml-1 { margin-left: 4px; }
.ml-2 { margin-left: 8px; }
.ml-3 { margin-left: 16px; }
.mr-2 { margin-right: 8px; }
.mr-3 { margin-right: 16px; }
.ml-auto { margin-left: auto; }
.border { border: 1px solid var(--borderColor-default, #30363d); }
@@ -253,11 +521,19 @@
.color-fg-danger { color: var(--fgColor-danger, #f85149); }
.color-fg-default { color: var(--fgColor-default, #e6edf3); }
/* Light mode overrides */
/* ==========================================================================
Light Mode Overrides
========================================================================== */
@media (prefers-color-scheme: light) {
.dap-debugger-pane {
background-color: var(--bgColor-default, #ffffff);
border-color: var(--borderColor-default, #d0d7de) !important;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
}
.dap-debugger-sidebar {
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
}
.dap-header,
@@ -279,6 +555,12 @@
.color-fg-muted {
color: var(--fgColor-muted, #656d76);
}
check-step.dap-current-step > details > summary,
check-step.dap-current-step > div:first-child {
background: linear-gradient(90deg, rgba(248, 81, 73, 0.1) 0%, rgba(248, 81, 73, 0.02) 100%) !important;
border-top: 2px solid #f85149 !important;
}
}
/* Respect GitHub's color mode data attribute */
@@ -286,6 +568,12 @@
html[data-color-mode="light"] .dap-debugger-pane {
background-color: #ffffff;
border-color: #d0d7de !important;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
}
[data-color-mode="light"] .dap-debugger-sidebar,
html[data-color-mode="light"] .dap-debugger-sidebar {
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
}
[data-color-mode="light"] .dap-header,
@@ -306,27 +594,17 @@ 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;
[data-color-mode="light"] .dap-layout-toggles,
html[data-color-mode="light"] .dap-layout-toggles {
background-color: #eaeef2;
}
.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);
[data-color-mode="light"] check-step.dap-current-step > details > summary,
[data-color-mode="light"] check-step.dap-current-step > div:first-child,
html[data-color-mode="light"] check-step.dap-current-step > details > summary,
html[data-color-mode="light"] check-step.dap-current-step > div:first-child {
background: linear-gradient(90deg, rgba(248, 81, 73, 0.1) 0%, rgba(248, 81, 73, 0.02) 100%) !important;
border-top: 2px solid #f85149 !important;
}
/* Light mode for debug button */

View File

@@ -2,7 +2,7 @@
* Content Script - Debugger UI
*
* Injects the debugger pane into GitHub Actions job pages and handles
* all UI interactions.
* all UI interactions. Supports two layout modes: sidebar and bottom panel.
*/
// State
@@ -11,6 +11,12 @@ let currentFrameId = 0;
let isConnected = false;
let replHistory = [];
let replHistoryIndex = -1;
let currentLayout = 'bottom'; // 'bottom' | 'sidebar'
let currentStepElement = null; // Track current step for breakpoint indicator
// Layout constants
const BOTTOM_PANEL_HEIGHT = 350;
const SIDEBAR_WIDTH = 350;
// HTML escape helper
function escapeHtml(text) {
@@ -27,6 +33,26 @@ function stripResultIndicator(name) {
return name.replace(/\s*\[(running|success|failure|skipped|cancelled)\]$/i, '');
}
/**
* Load layout preference from storage
*/
function loadLayoutPreference() {
return new Promise((resolve) => {
chrome.storage.local.get(['debuggerLayout'], (data) => {
currentLayout = data.debuggerLayout || 'bottom';
resolve(currentLayout);
});
});
}
/**
* Save layout preference to storage
*/
function saveLayoutPreference(layout) {
currentLayout = layout;
chrome.storage.local.set({ debuggerLayout: layout });
}
/**
* Send DAP request to background script
*/
@@ -84,33 +110,93 @@ function getAllSteps() {
}
/**
* Create the debugger pane HTML
* SVG Icons
*/
function createDebuggerPaneHTML() {
const Icons = {
bug: `<svg class="octicon" viewBox="0 0 16 16" width="16" height="16">
<path fill="currentColor" d="M4.72.22a.75.75 0 0 1 1.06 0l1 1a.75.75 0 0 1-1.06 1.06l-.22-.22-.22.22a.75.75 0 0 1-1.06-1.06l1-1Z"/>
<path fill="currentColor" d="M11.28.22a.75.75 0 0 0-1.06 0l-1 1a.75.75 0 0 0 1.06 1.06l.22-.22.22.22a.75.75 0 0 0 1.06-1.06l-1-1Z"/>
<path fill="currentColor" d="M8 4a4 4 0 0 0-4 4v1h1v2.5a2.5 2.5 0 0 0 2.5 2.5h1a2.5 2.5 0 0 0 2.5-2.5V9h1V8a4 4 0 0 0-4-4Z"/>
<path fill="currentColor" d="M5 9H3.5a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5H5V9ZM11 9h1.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H11V9Z"/>
</svg>`,
close: `<svg viewBox="0 0 16 16" width="16" height="16">
<path fill="currentColor" d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"/>
</svg>`,
layoutBottom: `<svg viewBox="0 0 16 16" width="16" height="16">
<path fill="currentColor" d="M1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25V2.75C0 1.784.784 1 1.75 1Zm0 1.5a.25.25 0 0 0-.25.25v6h13v-6a.25.25 0 0 0-.25-.25H1.75Zm-.25 10.75c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-3h-13v3Z"/>
</svg>`,
layoutSidebar: `<svg viewBox="0 0 16 16" width="16" height="16">
<path fill="currentColor" d="M1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25V2.75C0 1.784.784 1 1.75 1ZM1.5 2.75v10.5c0 .138.112.25.25.25h8.5V2.5h-8.5a.25.25 0 0 0-.25.25Zm10.25 10.75h2.5a.25.25 0 0 0 .25-.25V2.75a.25.25 0 0 0-.25-.25h-2.5v11Z"/>
</svg>`,
reverseContinue: `<svg viewBox="0 0 16 16" width="16" height="16"><path fill="currentColor" d="M2 2v12h2V8.5l5 4V8.5l5 4V2.5l-5 4V2.5l-5 4V2z"/></svg>`,
stepBack: `<svg viewBox="0 0 16 16" width="16" height="16"><path fill="currentColor" d="M2 2v12h2V2H2zm3 6 7 5V3L5 8z"/></svg>`,
continue: `<svg viewBox="0 0 16 16" width="16" height="16"><path fill="currentColor" d="M4 2l10 6-10 6z"/></svg>`,
stepForward: `<svg viewBox="0 0 16 16" width="16" height="16"><path fill="currentColor" d="M12 2v12h2V2h-2zM2 3l7 5-7 5V3z"/></svg>`,
};
/**
* Create control buttons HTML
*/
function createControlButtonsHTML(compact = false) {
const btnClass = compact ? 'btn-sm' : 'btn-sm';
return `
<button class="btn ${btnClass} dap-control-btn" data-action="reverseContinue" title="Reverse Continue (go to first checkpoint)" disabled>
${Icons.reverseContinue}
</button>
<button class="btn ${btnClass} dap-control-btn" data-action="stepBack" title="Step Back" disabled>
${Icons.stepBack}
</button>
<button class="btn ${btnClass} btn-primary dap-control-btn" data-action="continue" title="Continue" disabled>
${Icons.continue}
</button>
<button class="btn ${btnClass} dap-control-btn" data-action="next" title="Step to Next" disabled>
${Icons.stepForward}
</button>
`;
}
/**
* Create the bottom panel HTML structure
*/
function createBottomPaneHTML() {
return `
<div class="dap-header d-flex flex-items-center p-2 border-bottom">
<svg class="octicon mr-2" viewBox="0 0 16 16" width="16" height="16">
<path fill="currentColor" d="M4.72.22a.75.75 0 0 1 1.06 0l1 1a.75.75 0 0 1-1.06 1.06l-.22-.22-.22.22a.75.75 0 0 1-1.06-1.06l1-1Z"/>
<path fill="currentColor" d="M11.28.22a.75.75 0 0 0-1.06 0l-1 1a.75.75 0 0 0 1.06 1.06l.22-.22.22.22a.75.75 0 0 0 1.06-1.06l-1-1Z"/>
<path fill="currentColor" d="M8 4a4 4 0 0 0-4 4v1h1v2.5a2.5 2.5 0 0 0 2.5 2.5h1a2.5 2.5 0 0 0 2.5-2.5V9h1V8a4 4 0 0 0-4-4Z"/>
<path fill="currentColor" d="M5 9H3.5a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5H5V9ZM11 9h1.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H11V9Z"/>
</svg>
<span class="text-bold">Debugger</span>
<span class="dap-step-info color-fg-muted ml-2">Connecting...</span>
<span class="Label dap-status-label ml-auto">CONNECTING</span>
<div class="dap-header-right ml-auto d-flex flex-items-center">
<div class="dap-controls d-flex flex-items-center mr-3">
${createControlButtonsHTML(true)}
</div>
<div class="dap-layout-toggles d-flex flex-items-center mr-2">
<button class="btn btn-sm dap-layout-btn" data-layout="sidebar" title="Sidebar layout">
${Icons.layoutSidebar}
</button>
<button class="btn btn-sm dap-layout-btn active" data-layout="bottom" title="Bottom panel layout">
${Icons.layoutBottom}
</button>
</div>
<button class="btn btn-sm dap-close-btn" title="Close debugger">
${Icons.close}
</button>
</div>
</div>
<div class="dap-content d-flex" style="height: 300px;">
<div class="dap-content d-flex">
<!-- Scopes Panel -->
<div class="dap-scopes border-right overflow-auto" style="width: 33%;">
<div class="dap-scope-header p-2 text-bold border-bottom">Variables</div>
<div class="dap-scopes border-right overflow-auto">
<div class="dap-scope-header p-2 text-bold border-bottom d-flex flex-items-center">
<span>Variables</span>
</div>
<div class="dap-scope-tree p-2">
<div class="color-fg-muted">Connect to view variables</div>
</div>
</div>
<!-- REPL Console -->
<div class="dap-repl d-flex flex-column" style="width: 67%;">
<div class="dap-repl d-flex flex-column">
<div class="dap-repl-header p-2 text-bold border-bottom">Console</div>
<div class="dap-repl-output overflow-auto flex-auto p-2 text-mono text-small">
<div class="color-fg-muted">Welcome to Actions DAP Debugger</div>
@@ -123,24 +209,60 @@ function createDebuggerPaneHTML() {
</div>
</div>
</div>
`;
}
/**
* Create the sidebar panel HTML structure
*/
function createSidebarPaneHTML() {
return `
<div class="dap-header d-flex flex-items-center p-2 border-bottom">
<span class="text-bold">Debugger</span>
<div class="dap-header-right ml-auto d-flex flex-items-center">
<div class="dap-layout-toggles d-flex flex-items-center mr-2">
<button class="btn btn-sm dap-layout-btn active" data-layout="sidebar" title="Sidebar layout">
${Icons.layoutSidebar}
</button>
<button class="btn btn-sm dap-layout-btn" data-layout="bottom" title="Bottom panel layout">
${Icons.layoutBottom}
</button>
</div>
<button class="btn btn-sm dap-close-btn" title="Close debugger">
${Icons.close}
</button>
</div>
</div>
<!-- Scopes Panel -->
<div class="dap-scopes overflow-auto border-bottom">
<div class="dap-scope-header p-2 text-bold border-bottom d-flex flex-items-center">
<span>Variables</span>
</div>
<div class="dap-scope-tree p-2">
<div class="color-fg-muted">Connect to view variables</div>
</div>
</div>
<!-- REPL Console -->
<div class="dap-repl d-flex flex-column">
<div class="dap-repl-header p-2 text-bold border-bottom">Console</div>
<div class="dap-repl-output overflow-auto flex-auto p-2 text-mono text-small">
<div class="color-fg-muted">Welcome to Actions DAP Debugger</div>
<div class="color-fg-muted">Enter expressions like: \${{ github.ref }}</div>
<div class="color-fg-muted">Or shell commands: !ls -la</div>
</div>
<div class="dap-repl-input border-top p-2">
<input type="text" class="form-control input-sm text-mono"
placeholder="Enter expression or !command" disabled>
</div>
</div>
<!-- Control buttons -->
<div class="dap-controls d-flex flex-items-center p-2 border-top">
<button class="btn btn-sm mr-2" data-action="reverseContinue" title="Reverse Continue (go to first checkpoint)" disabled>
<svg viewBox="0 0 16 16" width="16" height="16"><path fill="currentColor" d="M2 2v12h2V8.5l5 4V8.5l5 4V2.5l-5 4V2.5l-5 4V2z"/></svg>
</button>
<button class="btn btn-sm mr-2" data-action="stepBack" title="Step Back" disabled>
<svg viewBox="0 0 16 16" width="16" height="16"><path fill="currentColor" d="M2 2v12h2V2H2zm3 6 7 5V3L5 8z"/></svg>
</button>
<button class="btn btn-sm btn-primary mr-2" data-action="continue" title="Continue" disabled>
<svg viewBox="0 0 16 16" width="16" height="16"><path fill="currentColor" d="M4 2l10 6-10 6z"/></svg>
</button>
<button class="btn btn-sm mr-2" data-action="next" title="Step to Next" disabled>
<svg viewBox="0 0 16 16" width="16" height="16"><path fill="currentColor" d="M2 3l7 5-7 5V3zm7 5l5 0V2h2v12h-2V8.5l-5 0z"/></svg>
</button>
<span class="dap-step-counter color-fg-muted ml-auto text-small">
Not connected
</span>
<div class="dap-controls d-flex flex-items-center justify-content-center p-2 border-top">
${createControlButtonsHTML()}
</div>
`;
}
@@ -148,49 +270,126 @@ function createDebuggerPaneHTML() {
/**
* Inject debugger pane into the page
*/
function injectDebuggerPane() {
function injectDebuggerPane(layout = null) {
// Remove existing pane if any
const existing = document.querySelector('.dap-debugger-pane');
if (existing) existing.remove();
// Find where to inject
const stepsContainer = document.querySelector('check-steps');
if (!stepsContainer) {
console.warn('[Content] No check-steps container found');
return null;
}
const targetLayout = layout || currentLayout;
// Create pane
const pane = document.createElement('div');
pane.className = 'dap-debugger-pane mx-2 mb-2 border rounded-2';
pane.innerHTML = createDebuggerPaneHTML();
pane.className = `dap-debugger-pane dap-debugger-${targetLayout}`;
// Insert before the first real workflow step (skip "Set up job" at index 0)
const steps = stepsContainer.querySelectorAll('check-step');
const targetStep = steps.length > 1 ? steps[1] : stepsContainer.firstChild;
stepsContainer.insertBefore(pane, targetStep);
if (targetLayout === 'bottom') {
pane.innerHTML = createBottomPaneHTML();
} else {
pane.innerHTML = createSidebarPaneHTML();
}
// Append to body for fixed positioning
document.body.appendChild(pane);
// Add body padding class for bottom layout to ensure content is scrollable
if (targetLayout === 'bottom') {
document.body.classList.add('dap-bottom-panel-active');
} else {
document.body.classList.remove('dap-bottom-panel-active');
}
// Setup event handlers
setupPaneEventHandlers(pane);
debuggerPane = pane;
currentLayout = targetLayout;
// Update layout toggle button states
updateLayoutToggleButtons();
return pane;
}
/**
* Move debugger pane to before a specific step
* Switch between layouts
*/
function moveDebuggerPane(stepElement, stepName) {
if (!debuggerPane || !stepElement) return;
function switchLayout(newLayout) {
if (newLayout === currentLayout) return;
// Move the pane
stepElement.parentNode.insertBefore(debuggerPane, stepElement);
// Store current state before switching
const wasConnected = isConnected;
const replOutputContent = debuggerPane?.querySelector('.dap-repl-output')?.innerHTML;
const scopeTreeContent = debuggerPane?.querySelector('.dap-scope-tree')?.innerHTML;
// Update step info
const stepInfo = debuggerPane.querySelector('.dap-step-info');
if (stepInfo) {
stepInfo.textContent = `Paused before: ${stepName}`;
// Remove old pane
if (debuggerPane) {
debuggerPane.remove();
debuggerPane = null;
}
// Save preference and create new pane
saveLayoutPreference(newLayout);
const pane = injectDebuggerPane(newLayout);
// Update body padding class based on new layout
if (newLayout === 'bottom') {
document.body.classList.add('dap-bottom-panel-active');
} else {
document.body.classList.remove('dap-bottom-panel-active');
}
// Restore state
if (replOutputContent) {
const output = pane.querySelector('.dap-repl-output');
if (output) output.innerHTML = replOutputContent;
}
if (scopeTreeContent) {
const scopeTree = pane.querySelector('.dap-scope-tree');
if (scopeTree) scopeTree.innerHTML = scopeTreeContent;
}
// Re-enable controls if connected
if (wasConnected) {
enableControls(true);
}
// Update debug button state
const btn = document.querySelector('.dap-debug-btn');
if (btn) btn.classList.add('selected');
// Update layout toggle button states
updateLayoutToggleButtons();
}
/**
* Update layout toggle button active states
*/
function updateLayoutToggleButtons() {
if (!debuggerPane) return;
debuggerPane.querySelectorAll('.dap-layout-btn').forEach((btn) => {
const layout = btn.dataset.layout;
btn.classList.toggle('active', layout === currentLayout);
});
}
/**
* Close the debugger pane
*/
function closeDebuggerPane() {
// Clear breakpoint indicator
clearBreakpointIndicator();
// Remove body padding class
document.body.classList.remove('dap-bottom-panel-active');
if (debuggerPane) {
debuggerPane.remove();
debuggerPane = null;
}
// Update debug button state
const btn = document.querySelector('.dap-debug-btn');
if (btn) btn.classList.remove('selected');
}
/**
@@ -215,6 +414,22 @@ function setupPaneEventHandlers(pane) {
});
});
// Layout toggle buttons
pane.querySelectorAll('.dap-layout-btn').forEach((btn) => {
btn.addEventListener('click', () => {
const layout = btn.dataset.layout;
if (layout && layout !== currentLayout) {
switchLayout(layout);
}
});
});
// Close button
const closeBtn = pane.querySelector('.dap-close-btn');
if (closeBtn) {
closeBtn.addEventListener('click', closeDebuggerPane);
}
// REPL input
const input = pane.querySelector('.dap-repl-input input');
if (input) {
@@ -299,7 +514,7 @@ function appendOutput(text, type) {
function enableControls(enabled) {
if (!debuggerPane) return;
debuggerPane.querySelectorAll('.dap-controls button').forEach((btn) => {
debuggerPane.querySelectorAll('.dap-control-btn').forEach((btn) => {
btn.disabled = !enabled;
});
@@ -315,37 +530,42 @@ function enableControls(enabled) {
function updateStatus(status, extra) {
if (!debuggerPane) return;
const label = debuggerPane.querySelector('.dap-status-label');
if (label) {
label.textContent = status;
label.className = 'Label dap-status-label ml-auto ';
switch (status) {
case 'PAUSED':
label.classList.add('Label--attention');
break;
case 'RUNNING':
label.classList.add('Label--success');
break;
case 'TERMINATED':
case 'DISCONNECTED':
label.classList.add('Label--secondary');
break;
case 'ERROR':
label.classList.add('Label--danger');
break;
default:
label.classList.add('Label--secondary');
}
// Update step info text
const stepInfo = debuggerPane.querySelector('.dap-step-info');
if (stepInfo && extra) {
stepInfo.textContent = extra;
}
}
// Update step counter if extra info provided
if (extra) {
const counter = debuggerPane.querySelector('.dap-step-counter');
if (counter) {
counter.textContent = extra;
}
/**
* Update breakpoint indicator - highlights the current step
*/
function updateBreakpointIndicator(stepElement) {
// Clear previous indicator
clearBreakpointIndicator();
if (!stepElement) return;
// Add indicator class to current step
stepElement.classList.add('dap-current-step');
currentStepElement = stepElement;
// Scroll step into view if needed
stepElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
/**
* Clear breakpoint indicator from all steps
*/
function clearBreakpointIndicator() {
if (currentStepElement) {
currentStepElement.classList.remove('dap-current-step');
currentStepElement = null;
}
// Also clear any other steps that might have the class
document.querySelectorAll('.dap-current-step').forEach((el) => {
el.classList.remove('dap-current-step');
});
}
/**
@@ -396,7 +616,7 @@ function createTreeNode(name, variablesReference, isExpandable, value) {
// Expand icon
const expandIcon = document.createElement('span');
expandIcon.className = 'dap-expand-icon';
expandIcon.textContent = isExpandable ? '\u25B6' : ' '; // or space
expandIcon.textContent = isExpandable ? '\u25B6' : ' '; // triangleright or space
content.appendChild(expandIcon);
// Name
@@ -433,7 +653,7 @@ async function toggleTreeNode(node) {
if (children) {
// Toggle visibility
children.hidden = !children.hidden;
expandIcon.textContent = children.hidden ? '\u25B6' : '\u25BC'; // ▶ or ▼
expandIcon.textContent = children.hidden ? '\u25B6' : '\u25BC'; // triangleright or triangledown
return;
}
@@ -461,10 +681,10 @@ async function toggleTreeNode(node) {
}
node.appendChild(childContainer);
expandIcon.textContent = '\u25BC'; //
expandIcon.textContent = '\u25BC'; // triangledown
} catch (error) {
console.error('[Content] Failed to load variables:', error);
expandIcon.textContent = '\u25B6'; //
expandIcon.textContent = '\u25B6'; // triangleright
}
}
@@ -487,7 +707,6 @@ async function handleStoppedEvent(body) {
currentFrameId = currentFrame.id;
// 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);
@@ -497,14 +716,15 @@ async function handleStoppedEvent(body) {
stepElement = findStepByNumber(currentFrame.line + 1);
}
// Update breakpoint indicator
if (stepElement) {
moveDebuggerPane(stepElement, rawStepName);
updateBreakpointIndicator(stepElement);
}
// Update step counter
const counter = debuggerPane?.querySelector('.dap-step-counter');
if (counter) {
counter.textContent = `Step ${currentFrame.line || currentFrame.id} of ${stackTrace.stackFrames.length}`;
// Update step info in header
const stepInfo = debuggerPane?.querySelector('.dap-step-info');
if (stepInfo) {
stepInfo.textContent = `Paused before: ${rawStepName}`;
}
// Load scopes
@@ -531,13 +751,9 @@ function handleOutputEvent(body) {
*/
function handleTerminatedEvent() {
isConnected = false;
updateStatus('TERMINATED');
updateStatus('TERMINATED', 'Session ended');
enableControls(false);
const stepInfo = debuggerPane?.querySelector('.dap-step-info');
if (stepInfo) {
stepInfo.textContent = 'Session ended';
}
clearBreakpointIndicator();
}
/**
@@ -552,25 +768,24 @@ async function loadCurrentDebugState() {
const currentFrame = stackTrace.stackFrames[0];
currentFrameId = currentFrame.id;
// Move pane to current step
// Strip result indicator from step name for DOM lookup
const rawStepName = stripResultIndicator(currentFrame.name);
let stepElement = findStepByName(rawStepName);
if (!stepElement && currentFrame.line > 0) {
// Fallback: use step number from Line property
// Add 1 to account for "Set up job" which is always step 1 in GitHub UI but not in DAP
stepElement = findStepByNumber(currentFrame.line + 1);
}
// Update breakpoint indicator
if (stepElement) {
moveDebuggerPane(stepElement, rawStepName);
updateBreakpointIndicator(stepElement);
}
// Update step counter
const counter = debuggerPane.querySelector('.dap-step-counter');
if (counter) {
counter.textContent = `Step ${currentFrame.line || currentFrame.id} of ${stackTrace.stackFrames.length}`;
// Update step info in header
const stepInfo = debuggerPane.querySelector('.dap-step-info');
if (stepInfo) {
stepInfo.textContent = `Paused before: ${rawStepName}`;
}
// Load scopes
@@ -590,11 +805,7 @@ function handleStatusChange(status) {
switch (status) {
case 'connected':
isConnected = true;
updateStatus('CONNECTED');
const stepInfo = debuggerPane?.querySelector('.dap-step-info');
if (stepInfo) {
stepInfo.textContent = 'Waiting for debug event...';
}
updateStatus('CONNECTED', 'Waiting for debug event...');
break;
case 'paused':
@@ -606,20 +817,22 @@ function handleStatusChange(status) {
case 'running':
isConnected = true;
updateStatus('RUNNING');
updateStatus('RUNNING', 'Running...');
enableControls(false);
break;
case 'disconnected':
isConnected = false;
updateStatus('DISCONNECTED');
updateStatus('DISCONNECTED', 'Disconnected');
enableControls(false);
clearBreakpointIndicator();
break;
case 'error':
isConnected = false;
updateStatus('ERROR');
updateStatus('ERROR', 'Connection error');
enableControls(false);
clearBreakpointIndicator();
break;
}
}
@@ -656,7 +869,14 @@ 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');
// Try multiple possible container selectors
let container = document.querySelector('.js-check-run-search');
// Alternative: look for the header area with search box
if (!container) {
container = document.querySelector('.PageLayout-content .Box-header');
}
if (!container || container.querySelector('.dap-debug-btn-container')) {
return; // Already injected or container not found
}
@@ -665,25 +885,20 @@ function injectDebugButton() {
buttonContainer.className = 'ml-2 dap-debug-btn-container';
buttonContainer.innerHTML = `
<button type="button" class="btn btn-sm dap-debug-btn" title="Toggle DAP Debugger">
<svg viewBox="0 0 16 16" width="16" height="16" class="octicon mr-1" style="vertical-align: text-bottom;">
<path fill="currentColor" d="M4.72.22a.75.75 0 0 1 1.06 0l1 1a.75.75 0 0 1-1.06 1.06l-.22-.22-.22.22a.75.75 0 0 1-1.06-1.06l1-1Z"/>
<path fill="currentColor" d="M11.28.22a.75.75 0 0 0-1.06 0l-1 1a.75.75 0 0 0 1.06 1.06l.22-.22.22.22a.75.75 0 0 0 1.06-1.06l-1-1Z"/>
<path fill="currentColor" d="M8 4a4 4 0 0 0-4 4v1h1v2.5a2.5 2.5 0 0 0 2.5 2.5h1a2.5 2.5 0 0 0 2.5-2.5V9h1V8a4 4 0 0 0-4-4Z"/>
<path fill="currentColor" d="M5 9H3.5a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5H5V9ZM11 9h1.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H11V9Z"/>
</svg>
Debug
${Icons.bug}
<span class="ml-1 dap-debug-btn-text">Debug</span>
</button>
`;
const button = buttonContainer.querySelector('button');
button.addEventListener('click', () => {
button.addEventListener('click', async () => {
let pane = document.querySelector('.dap-debugger-pane');
if (pane) {
// Toggle visibility
pane.hidden = !pane.hidden;
button.classList.toggle('selected', !pane.hidden);
// Close pane
closeDebuggerPane();
} else {
// Create and show pane
// Load preference and create pane
await loadLayoutPreference();
pane = injectDebuggerPane();
if (pane) {
button.classList.add('selected');
@@ -705,9 +920,12 @@ function injectDebugButton() {
/**
* Initialize content script
*/
function init() {
async function init() {
console.log('[Content] Actions DAP Debugger content script loaded');
// Load layout preference
await loadLayoutPreference();
// Check if we're on a job page
const steps = getAllSteps();
if (steps.length === 0) {
@@ -731,8 +949,6 @@ function init() {
// Check current connection status
chrome.runtime.sendMessage({ type: 'get-status' }, async (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');
@@ -742,6 +958,8 @@ function init() {
if (btn) btn.classList.add('selected');
}
handleStatusChange(response.status);
// If already paused, load the current debug state
if (response.status === 'paused') {
await loadCurrentDebugState();