mirror of
https://github.com/actions/runner.git
synced 2026-01-23 04:51:23 +08:00
extension ui improvements
This commit is contained in:
214
.opencode/plans/dap-browser-extension-ui-improvements.md
Normal file
214
.opencode/plans/dap-browser-extension-ui-improvements.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# DAP Browser Extension UI Improvements
|
||||
|
||||
**Status:** Implemented
|
||||
**Date:** January 2026
|
||||
**Related:** [dap-browser-extension.md](./dap-browser-extension.md), [dap-debugging.md](./dap-debugging.md)
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes UI improvements made to the DAP browser extension debugger based on designer feedback. The original implementation inserted the debugger pane inline between workflow steps, causing it to "bounce around" as the user stepped through the job. The new implementation uses fixed positioning with two layout options.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The original debugger UI had these issues:
|
||||
1. **Bouncing pane**: The debugger pane was inserted between steps in the DOM, so it moved position each time the user stepped to a new step
|
||||
2. **No layout flexibility**: Users couldn't choose where they wanted the debugger positioned
|
||||
3. **No breakpoint indicator**: There was no visual indication of which step the debugger was currently paused at
|
||||
|
||||
## Solution
|
||||
|
||||
Implemented two fixed-position layout options inspired by browser DevTools:
|
||||
|
||||
### 1. Bottom Panel Layout (Default)
|
||||
- Fixed at the bottom of the viewport
|
||||
- Height: 280px
|
||||
- Variables panel on left (33%), Console on right (67%)
|
||||
- Control buttons in the header row (right side)
|
||||
- Similar to Chrome/Firefox DevTools
|
||||
|
||||
```
|
||||
+------------------------------------------------------------------+
|
||||
| Debugger [step info] [controls] [layout] [X] |
|
||||
+------------------------------------------------------------------+
|
||||
| Variables (1/3) | Console (2/3) |
|
||||
| > github | Welcome message... |
|
||||
| > env | > command output |
|
||||
| > runner | |
|
||||
| | [input field ] |
|
||||
+------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
### 2. Sidebar Layout
|
||||
- Fixed on the right side of the viewport
|
||||
- Width: 350px
|
||||
- Full height of viewport
|
||||
- Variables on top, Console in middle, Controls at bottom
|
||||
|
||||
```
|
||||
+------------------+
|
||||
| Debugger [X] |
|
||||
| [layout btns] |
|
||||
+------------------+
|
||||
| Variables |
|
||||
| > github |
|
||||
| > env |
|
||||
+------------------+
|
||||
| Console |
|
||||
| output... |
|
||||
| |
|
||||
| [input ] |
|
||||
+------------------+
|
||||
| [ controls ] |
|
||||
+------------------+
|
||||
```
|
||||
|
||||
### 3. Breakpoint Indicator
|
||||
Visual marker showing the current step where the debugger is paused:
|
||||
- Red accent bar on the right edge of the step row
|
||||
- Red bottom border on the step header
|
||||
- Triangle pointer pointing toward the debugger panel
|
||||
- Subtle gradient background highlight
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Files Modified
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `browser-ext/content/content.js` | Complete refactor: new layout system, breakpoint indicator, layout toggle |
|
||||
| `browser-ext/content/content.css` | New styles for layouts, breakpoint indicator, toggle buttons |
|
||||
|
||||
### Key Functions Added (`content.js`)
|
||||
|
||||
#### Layout Management
|
||||
```javascript
|
||||
// Load layout preference from chrome.storage.local
|
||||
async function loadLayoutPreference()
|
||||
|
||||
// Save layout preference to chrome.storage.local
|
||||
function saveLayoutPreference(layout)
|
||||
|
||||
// Switch between 'bottom' and 'sidebar' layouts
|
||||
// Preserves console output and variable tree state during switch
|
||||
function switchLayout(newLayout)
|
||||
|
||||
// Create the bottom panel HTML structure
|
||||
function createBottomPaneHTML()
|
||||
|
||||
// Create the sidebar panel HTML structure
|
||||
function createSidebarPaneHTML()
|
||||
```
|
||||
|
||||
#### Breakpoint Indicator
|
||||
```javascript
|
||||
// Highlights the current step with CSS class 'dap-current-step'
|
||||
function updateBreakpointIndicator(stepElement)
|
||||
|
||||
// Clears the indicator from all steps
|
||||
function clearBreakpointIndicator()
|
||||
```
|
||||
|
||||
#### Panel Controls
|
||||
```javascript
|
||||
// Close the debugger panel and clear indicators
|
||||
function closeDebuggerPane()
|
||||
|
||||
// Update layout toggle button active states
|
||||
function updateLayoutToggleButtons()
|
||||
```
|
||||
|
||||
### CSS Classes Added (`content.css`)
|
||||
|
||||
| Class | Purpose |
|
||||
|-------|---------|
|
||||
| `.dap-debugger-bottom` | Bottom panel layout (fixed position) |
|
||||
| `.dap-debugger-sidebar` | Sidebar layout (fixed position) |
|
||||
| `.dap-layout-toggles` | Container for layout toggle buttons |
|
||||
| `.dap-layout-btn` | Individual layout toggle button |
|
||||
| `.dap-layout-btn.active` | Active state for selected layout |
|
||||
| `.dap-close-btn` | Close button styling |
|
||||
| `check-step.dap-current-step` | Breakpoint indicator on step element |
|
||||
|
||||
### State Variables
|
||||
|
||||
```javascript
|
||||
let currentLayout = 'bottom'; // 'bottom' | 'sidebar'
|
||||
let currentStepElement = null; // Track current step for breakpoint indicator
|
||||
```
|
||||
|
||||
### Storage
|
||||
|
||||
Layout preference is persisted to `chrome.storage.local` under the key `debuggerLayout`.
|
||||
|
||||
## Removed Functionality
|
||||
|
||||
- `moveDebuggerPane()` - No longer needed since debugger uses fixed positioning
|
||||
- Inline pane injection between steps - Replaced with fixed position panels
|
||||
|
||||
## Design Mockups Reference
|
||||
|
||||
The implementation was based on these mockup frames:
|
||||
- **Frame 4/5/6**: Sidebar layout on right side
|
||||
- **Frame 7**: Bottom panel layout with controls in header
|
||||
- All frames showed the breakpoint indicator as a red/orange accent on the current step
|
||||
|
||||
## Future Improvements
|
||||
|
||||
Potential enhancements for future iterations:
|
||||
|
||||
1. **Resizable panels**: Allow users to drag to resize the panel width/height
|
||||
2. **Minimize/maximize**: Add ability to minimize the panel to just a header bar
|
||||
3. **Detached window**: Option to pop out debugger into separate browser window
|
||||
4. **Keyboard shortcuts**: Add shortcuts for layout switching and panel toggle
|
||||
5. **Remember panel size**: Persist user's preferred panel dimensions
|
||||
6. **Breakpoint list**: Show list of all breakpoints with ability to navigate
|
||||
7. **Step indicator in panel**: Show step name/number in the panel header with prev/next navigation
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Bottom panel displays correctly at viewport bottom
|
||||
- [ ] Sidebar panel displays correctly on right side
|
||||
- [ ] Layout toggle buttons switch between layouts
|
||||
- [ ] Layout preference persists across page reloads
|
||||
- [ ] Close button removes panel and updates Debug button state
|
||||
- [ ] Breakpoint indicator appears on current step when paused
|
||||
- [ ] Breakpoint indicator moves when stepping to next step
|
||||
- [ ] Breakpoint indicator clears when disconnected/terminated
|
||||
- [ ] Console output preserves when switching layouts
|
||||
- [ ] Variables tree preserves when switching layouts
|
||||
- [ ] Works correctly in both light and dark mode
|
||||
- [ ] Panel doesn't interfere with page scrolling
|
||||
- [ ] Step scrolls into view when breakpoint changes
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
### Why Fixed Positioning?
|
||||
|
||||
The original inline injection approach had issues:
|
||||
1. Required complex DOM manipulation to move the pane between steps
|
||||
2. Caused layout shifts in the GitHub page
|
||||
3. Made it difficult to maintain console scroll position
|
||||
4. Required finding the correct insertion point for each step
|
||||
|
||||
Fixed positioning solves these:
|
||||
1. Panel stays in place - no DOM movement needed
|
||||
2. No layout shifts in the main page content
|
||||
3. Panel state (console, variables) naturally preserved
|
||||
4. Simpler CSS and JavaScript
|
||||
|
||||
### Layout Toggle UX
|
||||
|
||||
The toggle is a button group in the panel header showing both layout options:
|
||||
- Sidebar icon (vertical split)
|
||||
- Bottom icon (horizontal split)
|
||||
|
||||
The active layout is highlighted. Clicking the other option triggers `switchLayout()`.
|
||||
|
||||
### Breakpoint Indicator Implementation
|
||||
|
||||
Uses CSS class `.dap-current-step` added to the `<check-step>` element:
|
||||
- `::after` pseudo-element creates the red accent bar
|
||||
- `::before` pseudo-element creates the triangle pointer
|
||||
- Direct child selectors style the step header background and border
|
||||
|
||||
The indicator is updated in `handleStoppedEvent()` when the debugger pauses at a new step.
|
||||
@@ -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 */
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user