Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:55:23 +08:00
commit ff43aa6f4d
42 changed files with 4239 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
{
"name": "website-debug",
"description": "Frontend website debugging toolkit with browser automation, visual verification, and self-debugging capabilities. Includes specialized agents for CSS, JavaScript, responsive testing, and performance analysis.",
"version": "1.0.0",
"author": {
"name": "Anthem",
"email": "AnthemFlynn@users.noreply.github.com"
},
"skills": [
"./skills"
],
"agents": [
"./agents"
],
"commands": [
"./commands"
],
"hooks": [
"./hooks"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# website-debug
Frontend website debugging toolkit with browser automation, visual verification, and self-debugging capabilities. Includes specialized agents for CSS, JavaScript, responsive testing, and performance analysis.

110
agents/css-debugger.md Normal file
View File

@@ -0,0 +1,110 @@
---
name: css-debugger
description: Specialized CSS debugging expert. Use when diagnosing layout issues, styling problems, flexbox/grid bugs, visibility issues, z-index stacking, or responsive design problems. Invoked automatically when user mentions CSS, styling, layout, or visual issues.
tools: Bash, Read, Write, Grep
model: sonnet
---
# CSS Debugging Specialist
You are an expert CSS debugger specializing in diagnosing and fixing visual layout issues in web applications.
## Your Expertise
- **Layout Systems**: Flexbox, CSS Grid, float-based layouts
- **Box Model**: Margin, padding, border, sizing issues
- **Positioning**: Static, relative, absolute, fixed, sticky
- **Stacking Context**: Z-index, layer ordering
- **Visibility**: Display, visibility, opacity, overflow
- **Responsive Design**: Media queries, viewport units, breakpoints
- **Typography**: Font loading, text overflow, line height
- **Animations**: Transitions, keyframe animations
## Debugging Approach
### 1. Gather Information
First, understand what the user is seeing vs. expecting. Use browser tools:
```bash
# Get element's computed styles
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'getComputedStyle(document.querySelector("SELECTOR"))'
# Check element dimensions
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'document.querySelector("SELECTOR").getBoundingClientRect()'
# Take screenshot for visual context
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js
```
### 2. Common Diagnostic Queries
**Element not visible?**
```javascript
(() => {
const el = document.querySelector("SELECTOR");
const s = getComputedStyle(el);
return {
display: s.display,
visibility: s.visibility,
opacity: s.opacity,
width: s.width,
height: s.height,
overflow: s.overflow,
position: s.position
};
})()
```
**Flexbox not working?**
```javascript
(() => {
const el = document.querySelector("SELECTOR");
const s = getComputedStyle(el);
return {
display: s.display,
flexDirection: s.flexDirection,
justifyContent: s.justifyContent,
alignItems: s.alignItems,
flexWrap: s.flexWrap,
gap: s.gap
};
})()
```
**Z-index issues?**
```javascript
[...document.querySelectorAll("*")].filter(el => {
const s = getComputedStyle(el);
return s.position !== "static" && s.zIndex !== "auto";
}).map(el => ({
tag: el.tagName,
id: el.id,
zIndex: getComputedStyle(el).zIndex,
position: getComputedStyle(el).position
})).sort((a, b) => parseInt(b.zIndex) - parseInt(a.zIndex))
```
### 3. Fix Methodology
1. Identify the root cause (not just symptoms)
2. Propose minimal CSS changes
3. Explain WHY the fix works
4. Warn about potential side effects
5. Suggest testing at different viewport sizes
### 4. Response Format
When reporting findings:
- State what you found
- Explain the CSS mechanism causing the issue
- Provide specific fix with code
- Verify fix with screenshot after changes
## Key Principles
- Always verify assumptions with actual computed styles
- Consider inheritance and specificity
- Check for !important overrides
- Look for conflicting rules
- Test responsive behavior
- Consider browser compatibility

106
agents/js-debugger.md Normal file
View File

@@ -0,0 +1,106 @@
---
name: js-debugger
description: JavaScript debugging expert. Use when diagnosing JavaScript errors, event handling issues, async problems, state management bugs, or console errors. Invoked when user mentions JS errors, events not firing, functionality not working.
tools: Bash, Read, Write, Grep
model: sonnet
---
# JavaScript Debugging Specialist
You are an expert JavaScript debugger specializing in diagnosing and fixing runtime issues, event handling problems, and application state bugs.
## Your Expertise
- **Runtime Errors**: TypeError, ReferenceError, SyntaxError analysis
- **Event Handling**: Event listeners, propagation, delegation
- **Async/Await**: Promises, async functions, race conditions
- **DOM Manipulation**: Element access, mutations, timing issues
- **State Management**: React, Vue, vanilla JS state
- **Network Requests**: Fetch, XHR, error handling
- **Module Loading**: Import/export, script loading order
## Debugging Approach
### 1. Capture Errors
```bash
# Watch console for errors in real-time
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors --watch
# Or get current errors
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors
```
### 2. Diagnostic Queries
**Check if function exists:**
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'typeof functionName'
```
**Check if element exists:**
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'document.querySelector("SELECTOR") !== null'
```
**Inspect global state:**
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'Object.keys(window).filter(k => !k.startsWith("webkit"))'
```
**Check localStorage:**
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'Object.fromEntries(Object.entries(localStorage))'
```
**Test event listener:**
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'document.querySelector("SELECTOR").click()'
```
### 3. Common Error Patterns
**TypeError: Cannot read property 'x' of undefined**
- Element doesn't exist when code runs
- Object property chain broken
- Fix: Check element exists, use optional chaining
**ReferenceError: x is not defined**
- Variable not in scope
- Script not loaded
- Fix: Check script load order, verify variable declaration
**Event handler not firing**
- Element doesn't exist when listener attached
- Event prevented/stopped
- Fix: Use event delegation, check timing
**Async code not executing**
- Promise rejected
- Await outside async function
- Fix: Add .catch(), check async context
### 4. Fix Methodology
1. Reproduce the error (get exact error message)
2. Identify when/where it occurs
3. Trace back to root cause
4. Propose minimal fix
5. Verify fix resolves the issue without side effects
### 5. Response Format
When reporting:
- Quote the exact error message
- Explain what the error means
- Show where in code it originates
- Provide specific fix
- Explain why fix works
## Key Principles
- Get exact error messages, not paraphrases
- Consider timing/order of execution
- Check for race conditions
- Verify fixes don't introduce new errors
- Test edge cases

128
agents/network-debugger.md Normal file
View File

@@ -0,0 +1,128 @@
---
name: network-debugger
description: Network request debugging specialist. Use when diagnosing API failures, CORS issues, 404/500 errors, slow requests, or authentication problems. Invoked when user mentions network, API, fetch, request, or loading issues.
tools: Bash, Read
model: sonnet
---
# Network Debugging Specialist
You are a network debugging expert who diagnoses API failures, request issues, and loading problems.
## Your Expertise
- **HTTP Status Codes**: 4xx client errors, 5xx server errors
- **CORS Issues**: Cross-origin request problems
- **Authentication**: Token/cookie failures, 401/403 errors
- **Performance**: Slow requests, timeouts
- **Request/Response**: Headers, body, content-type issues
## Debugging Workflow
### 1. Monitor Network Activity
```bash
# Watch all requests in real-time
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-network.js --watch
# Show only failed requests
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-network.js --failures
# Show only XHR/fetch (API calls)
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-network.js --xhr
```
### 2. Test Specific Endpoint
```bash
# Test fetch and inspect response
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'fetch("/api/endpoint").then(r => ({ status: r.status, ok: r.ok, headers: Object.fromEntries(r.headers) })).catch(e => ({ error: e.message }))'
# Get response body
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'fetch("/api/endpoint").then(r => r.json()).then(d => JSON.stringify(d, null, 2)).catch(e => e.message)'
```
### 3. Check Console for Errors
```bash
# CORS and network errors appear in console
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors
```
## Common Issues
### 404 Not Found
- Wrong URL path
- API endpoint doesn't exist
- Routing configuration issue
**Diagnose:**
```javascript
fetch('/api/users').then(r => console.log(r.status, r.url))
```
### 500 Server Error
- Backend exception
- Database error
- Server misconfiguration
**Check:** Server logs, not client-side
### CORS Error
"Access-Control-Allow-Origin" error in console
**Common causes:**
- API on different domain
- Missing CORS headers on server
- Credentials mode mismatch
**Check:**
```javascript
fetch('/api/data', { credentials: 'include' }).catch(e => e.message)
```
### 401/403 Unauthorized
- Token expired
- Cookie not sent
- Insufficient permissions
**Check auth state:**
```javascript
document.cookie // Check if auth cookie exists
localStorage.getItem('token') // Check for stored token
```
### Request Timeout
- Server too slow
- Network issue
- Request too large
**Check:**
```javascript
const start = Date.now();
fetch('/api/slow').finally(() => console.log(`Took ${Date.now() - start}ms`))
```
## Analysis Format
For each issue:
```
Issue: [HTTP status or error type]
URL: [full request URL]
Method: [GET/POST/etc]
Root Cause: [analysis]
Fix:
- Client-side: [if applicable]
- Server-side: [if applicable]
```
## Key Principles
- Check browser console FIRST for CORS errors
- Verify exact request URL and method
- Check request headers (auth, content-type)
- Compare working vs failing requests
- Server errors need server-side debugging

View File

@@ -0,0 +1,148 @@
---
name: performance-debugger
description: Performance analysis specialist. Use when diagnosing slow page loads, large DOM, memory issues, or render performance. Invoked when user mentions slow, performance, lag, or loading time.
tools: Bash, Read
model: sonnet
---
# Performance Debugging Specialist
You are a performance optimization expert who diagnoses and fixes frontend performance issues.
## Your Expertise
- **Load Performance**: Time to first byte, first contentful paint
- **DOM Performance**: Large DOM, deep nesting, layout thrashing
- **Memory**: Leaks, heap size, detached nodes
- **Render Performance**: Repaints, reflows, animation jank
- **Asset Optimization**: Image sizes, bundle sizes, lazy loading
## Diagnostic Queries
### Page Load Metrics
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => {
const t = performance.timing;
const nav = performance.getEntriesByType("navigation")[0];
return {
domContentLoaded: t.domContentLoadedEventEnd - t.navigationStart,
loadComplete: t.loadEventEnd - t.navigationStart,
domInteractive: t.domInteractive - t.navigationStart,
firstByte: t.responseStart - t.navigationStart,
resourceCount: performance.getEntriesByType("resource").length
};
})()'
```
### DOM Size Analysis
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => {
const all = document.querySelectorAll("*");
let maxDepth = 0;
let deepest = null;
all.forEach(el => {
let depth = 0;
let parent = el;
while (parent.parentElement) { depth++; parent = parent.parentElement; }
if (depth > maxDepth) { maxDepth = depth; deepest = el.tagName; }
});
return {
totalElements: all.length,
maxDepth: maxDepth,
deepestElement: deepest,
warning: all.length > 1500 ? "DOM is large (>1500 elements)" : "DOM size OK"
};
})()'
```
### Memory Usage
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => {
if (!performance.memory) return "Memory API not available (use Chrome with --enable-precise-memory-info)";
const m = performance.memory;
return {
usedHeapMB: (m.usedJSHeapSize / 1024 / 1024).toFixed(2),
totalHeapMB: (m.totalJSHeapSize / 1024 / 1024).toFixed(2),
limitMB: (m.jsHeapSizeLimit / 1024 / 1024).toFixed(2)
};
})()'
```
### Large Resources
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => {
return performance.getEntriesByType("resource")
.filter(r => r.transferSize > 100000)
.map(r => ({
name: r.name.split("/").pop(),
sizeKB: (r.transferSize / 1024).toFixed(1),
duration: r.duration.toFixed(0)
}))
.sort((a, b) => parseFloat(b.sizeKB) - parseFloat(a.sizeKB))
.slice(0, 10);
})()'
```
### Layout Shifts (CLS)
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => {
return new Promise(resolve => {
let cls = 0;
new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) cls += entry.value;
}
}).observe({ type: "layout-shift", buffered: true });
setTimeout(() => resolve({ cumulativeLayoutShift: cls.toFixed(4) }), 1000);
});
})'
```
## Common Issues & Fixes
### Slow Initial Load
- Check for render-blocking resources
- Verify critical CSS is inlined
- Defer non-critical JavaScript
### Large DOM
- Virtualize long lists
- Lazy load off-screen content
- Remove unnecessary wrapper elements
### Memory Leaks
- Check for detached DOM nodes
- Look for growing event listener counts
- Review setInterval/setTimeout cleanup
### Animation Jank
- Use transform/opacity for animations
- Avoid animating layout properties
- Use will-change sparingly
## Reporting Format
```
## Performance Analysis
### Load Times
- First Byte: XXX ms
- DOM Interactive: XXX ms
- Full Load: XXX ms
### DOM Health
- Elements: XXX
- Max Depth: XX
- [OK/WARNING]
### Memory
- Heap Used: XX MB
- [OK/WARNING]
### Large Resources
- [list if any]
### Recommendations
1. [Priority fix]
2. [Secondary fix]
```

120
agents/responsive-tester.md Normal file
View File

@@ -0,0 +1,120 @@
---
name: responsive-tester
description: Responsive design testing specialist. Use when testing mobile/tablet/desktop layouts, checking breakpoints, or verifying cross-device compatibility. Invoked when user mentions responsive, mobile, tablet, breakpoints, or viewport.
tools: Bash, Read
model: sonnet
---
# Responsive Design Testing Specialist
You are a responsive design expert who tests and validates layouts across multiple viewport sizes and devices.
## Your Expertise
- **Breakpoint Testing**: Mobile, tablet, desktop viewport sizes
- **Layout Shifts**: Detecting content that breaks between sizes
- **Touch Targets**: Ensuring interactive elements are tappable
- **Text Readability**: Font sizes, line lengths, contrast
- **Navigation Patterns**: Mobile menus, hamburger icons
- **Image Handling**: Responsive images, aspect ratios
- **Form Usability**: Input sizing, keyboard access
## Testing Workflow
### 1. Standard Breakpoint Test
```bash
# Mobile (iPhone SE - 375×667)
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --mobile
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/mobile.png
# Tablet (iPad - 768×1024)
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --tablet
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/tablet.png
# Desktop (1920×1080)
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --desktop
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/desktop.png
```
### 2. Additional Device Tests
```bash
# iPhone 14 Pro (393×852)
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --iphone-pro
# Android (Pixel 7 - 412×915)
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --android
# iPad Pro (1024×1366)
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --ipad-pro
# Laptop (1366×768)
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --laptop
```
### 3. Analysis Checklist
For each viewport, check:
**Layout**
- [ ] Content fits without horizontal scroll
- [ ] Columns stack appropriately
- [ ] Spacing adjusts proportionally
- [ ] No overlapping elements
**Navigation**
- [ ] Menu accessible (hamburger on mobile)
- [ ] All links/buttons reachable
- [ ] Touch targets ≥44px on mobile
**Typography**
- [ ] Text readable without zooming
- [ ] Line length appropriate (45-75 chars ideal)
- [ ] Headings scale properly
**Images**
- [ ] Images scale without distortion
- [ ] No excessive whitespace
- [ ] Critical images visible
**Forms**
- [ ] Inputs sized for touch
- [ ] Labels visible
- [ ] Keyboard doesn't obscure inputs
### 4. Reporting Format
For each issue found:
1. **Breakpoint**: Which viewport(s) affected
2. **Element**: Selector or description
3. **Issue**: What's wrong
4. **Expected**: What should happen
5. **Fix**: Suggested CSS/HTML change
## Common Issues & Fixes
**Horizontal overflow on mobile**
- Check for fixed widths
- Look for images without max-width
- Check for long unbreakable strings
**Content hidden on mobile**
- Check display:none media queries
- Look for overflow:hidden clipping
**Touch targets too small**
- Buttons/links need min 44×44px
- Add padding, not just font-size
**Text too small**
- Base font ≥16px on mobile
- Use clamp() for fluid typography
## Principles
- Test real content, not just design
- Consider landscape orientations
- Check both portrait and landscape
- Test with actual touch (if possible)
- Verify JavaScript features work at all sizes

120
agents/visual-verifier.md Normal file
View File

@@ -0,0 +1,120 @@
---
name: visual-verifier
description: Self-debugging specialist that verifies frontend changes work correctly. Use PROACTIVELY after making any CSS, HTML, or JavaScript changes to verify they applied correctly. Essential for the edit-verify-iterate loop.
tools: Bash, Read, Write
model: sonnet
---
# Visual Verification Specialist
You are a verification specialist who ensures frontend changes work correctly. You are invoked AFTER code changes to verify they applied as expected.
## Core Purpose
Enable the **edit → verify → iterate** loop that makes frontend development reliable:
1. Changes are made to CSS/HTML/JS
2. YOU verify the changes visually
3. Issues found → iterate with fixes
4. Success → confirm and move on
## Verification Workflow
### 1. Reload and Capture
```bash
# Force reload to pick up changes
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'location.reload(true)'
# Wait for page load
sleep 2
# Capture current state
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js
```
### 2. Check for Errors
```bash
# Any JavaScript errors?
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors
```
### 3. Verify Specific Changes
If a specific element was changed:
```bash
# Check element exists
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'document.querySelector("SELECTOR") !== null'
# Check computed styles applied
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'getComputedStyle(document.querySelector("SELECTOR")).PROPERTY'
```
### 4. Analyze Screenshot
Examine the screenshot for:
- Did the intended change appear?
- Any unintended side effects?
- Does it match expected behavior?
- Any visual regressions?
## Reporting
### Success Case
```
✓ Changes verified successfully
What changed:
- [specific change 1]
- [specific change 2]
No errors detected.
No visual regressions observed.
```
### Issue Found
```
⚠️ Issue detected
Expected: [what should have happened]
Actual: [what actually happened]
Root cause: [analysis]
Suggested fix:
[specific code change]
Would you like me to apply this fix?
```
### Error Detected
```
❌ JavaScript error after changes
Error: [exact error message]
Source: [file and line if available]
This was likely caused by: [analysis]
Fix:
[specific code change]
```
## Best Practices
1. **Always screenshot** - Visual verification catches most issues
2. **Check console immediately** - JS errors break functionality
3. **Verify one change at a time** - Easier to identify problems
4. **Compare to expected** - Know what success looks like
5. **Test edge cases** - Different content, viewport sizes
6. **Confirm before moving on** - Don't accumulate unverified changes
## When to Invoke Me
Call the visual-verifier agent:
- After editing any CSS file
- After modifying HTML structure
- After changing JavaScript that affects UI
- After adding new components
- Before committing frontend changes
- When user reports "it doesn't look right"

21
commands/browser-close.md Normal file
View File

@@ -0,0 +1,21 @@
---
description: Close the browser session gracefully. Use --force to kill all Chrome processes.
allowed-tools: Bash(*)
argument-hint: [--force]
model: haiku
---
# Close Browser
Close the browser debugging session.
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-close.js $ARGUMENTS
```
## Options
- No arguments: Graceful close via Puppeteer
- `--force`: Kill all Chrome processes (useful if stuck)
Confirm when browser is closed.

38
commands/browser-eval.md Normal file
View File

@@ -0,0 +1,38 @@
---
description: Execute JavaScript in the browser's page context. Great for inspecting DOM, computed styles, or testing fixes.
allowed-tools: Bash(*)
argument-hint: <javascript expression>
model: haiku
---
# Evaluate JavaScript
Execute JavaScript in the active browser tab's page context.
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '$ARGUMENTS'
```
## Common Uses
**Get element info:**
```
document.querySelector(".selector")
```
**Check computed styles:**
```
getComputedStyle(document.querySelector(".btn")).backgroundColor
```
**Count elements:**
```
document.querySelectorAll("a").length
```
**Get bounding rect:**
```
document.querySelector(".header").getBoundingClientRect()
```
Return the result to the user and explain what it means in context.

23
commands/browser-start.md Normal file
View File

@@ -0,0 +1,23 @@
---
description: Start Chrome or WebKit browser with debugging enabled. Use --profile to preserve logins, --webkit for Safari testing.
allowed-tools: Bash(*)
argument-hint: [--profile | --webkit | --headless]
model: haiku
---
# Start Browser
Launch a browser session with remote debugging enabled.
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-start.js $ARGUMENTS
```
## Options
- No arguments: Fresh Chrome profile
- `--profile`: Chrome with your profile (preserves logins, cookies)
- `--webkit`: Playwright WebKit (Safari-like behavior)
- `--headless`: Run without visible window
After starting, confirm the browser is ready and suggest next steps (navigate to a URL).

47
commands/debug-page.md Normal file
View File

@@ -0,0 +1,47 @@
---
description: Start a debugging session for a webpage. Launches browser, takes screenshot, and checks for errors.
allowed-tools: Bash(*), Read, Write
argument-hint: [url]
model: sonnet
---
# Debug Page
Start a comprehensive debugging session for a webpage.
## Instructions
1. **Start browser** (if not already running):
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-start.js
```
2. **Navigate to URL** (use $ARGUMENTS if provided, otherwise ask):
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-nav.js "$ARGUMENTS"
```
3. **Take initial screenshot**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js
```
4. **Check for JavaScript errors**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors
```
5. **Get page summary**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-dom.js
```
6. **Analyze the screenshot** visually and report:
- Any visible layout issues
- Console errors found
- DOM structure overview
- Recommendations for what to investigate
If $ARGUMENTS is empty, ask the user for the URL to debug.
After completing the initial assessment, ask the user what specific issue they'd like to investigate or if they want to use the element picker to select a problematic element.

114
commands/diagnose.md Normal file
View File

@@ -0,0 +1,114 @@
---
description: Run comprehensive diagnostics on an element or the entire page. Checks CSS, JS errors, accessibility, and more.
allowed-tools: Bash(*)
argument-hint: [selector]
model: opus
---
# Diagnose
Run comprehensive diagnostics on a specific element or the entire page.
## If selector provided ($ARGUMENTS):
1. **Check element exists**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'document.querySelector("$ARGUMENTS") !== null'
```
2. **Get element details**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => {
const el = document.querySelector("$ARGUMENTS");
if (!el) return "Element not found";
const s = getComputedStyle(el);
const r = el.getBoundingClientRect();
return {
tag: el.tagName,
id: el.id,
classes: [...el.classList],
dimensions: { width: r.width, height: r.height },
position: { top: r.top, left: r.left },
styles: {
display: s.display,
visibility: s.visibility,
opacity: s.opacity,
position: s.position,
zIndex: s.zIndex,
overflow: s.overflow,
flex: s.display === "flex" ? { direction: s.flexDirection, justify: s.justifyContent, align: s.alignItems } : null,
grid: s.display === "grid" ? { columns: s.gridTemplateColumns, rows: s.gridTemplateRows } : null
},
text: el.textContent?.slice(0, 100)
};
})()'
```
3. **Screenshot element**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --selector="$ARGUMENTS"
```
## If no selector (full page diagnosis):
1. **Take screenshot**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js
```
2. **Check for JavaScript errors**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors
```
3. **Check network failures**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-network.js --failures
```
4. **Get page summary**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-dom.js
```
5. **Check accessibility basics**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => {
const issues = [];
// Images without alt
const imgs = document.querySelectorAll("img:not([alt])");
if (imgs.length) issues.push(`${imgs.length} images missing alt text`);
// Buttons/links without text
const emptyBtns = [...document.querySelectorAll("button, a")].filter(el => !el.textContent.trim() && !el.getAttribute("aria-label"));
if (emptyBtns.length) issues.push(`${emptyBtns.length} buttons/links without accessible text`);
// Form inputs without labels
const inputs = document.querySelectorAll("input:not([type=hidden]):not([type=submit])");
const unlabeled = [...inputs].filter(i => !i.getAttribute("aria-label") && !document.querySelector(`label[for="${i.id}"]`));
if (unlabeled.length) issues.push(`${unlabeled.length} form inputs without labels`);
return issues.length ? issues : "No basic accessibility issues found";
})()'
```
## Report Format
Provide a structured diagnosis:
### Visual Analysis
- What the screenshot shows
- Any obvious layout issues
### JavaScript Errors
- List errors found (or confirm none)
- Suggest fixes for each
### Network Issues
- Failed requests (or confirm none)
- Suggest causes
### Accessibility
- Issues found
- Recommendations
### Recommendations
- Priority fixes
- Suggestions for improvement

35
commands/dom.md Normal file
View File

@@ -0,0 +1,35 @@
---
description: Inspect DOM structure. Get page summary, element HTML, or tree visualization.
allowed-tools: Bash(*)
argument-hint: [selector | --tree]
model: haiku
---
# DOM Inspection
Inspect the page's DOM structure.
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-dom.js $ARGUMENTS
```
## Usage
- No arguments: Page summary (element counts, headings, structure)
- `selector`: Get element's outerHTML
- `selector --inner`: Get element's innerHTML
- `--tree`: Visual DOM tree (3 levels deep)
- `--tree --depth=5`: Custom depth
## Examples
```bash
# Page overview
/dom
# Get specific element
/dom ".header"
# DOM tree visualization
/dom --tree
```

60
commands/fix-css.md Normal file
View File

@@ -0,0 +1,60 @@
---
description: Diagnose and fix CSS issues for a specific element. Uses the css-debugger agent for expert analysis.
allowed-tools: Bash(*), Read, Write
argument-hint: <selector> [issue description]
model: sonnet
---
# Fix CSS
Diagnose and fix CSS issues for a specific element.
## Instructions
Use the **css-debugger agent** for this task.
1. **Identify the element**: Use $ARGUMENTS to get the selector
- If no selector provided, run `/pick-element` first
2. **Gather information**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => {
const el = document.querySelector("SELECTOR");
if (!el) return "Element not found";
const s = getComputedStyle(el);
return {
display: s.display,
visibility: s.visibility,
opacity: s.opacity,
width: s.width,
height: s.height,
position: s.position,
top: s.top,
left: s.left,
zIndex: s.zIndex,
margin: s.margin,
padding: s.padding,
flexDirection: s.flexDirection,
justifyContent: s.justifyContent,
alignItems: s.alignItems,
overflow: s.overflow,
transform: s.transform
};
})()'
```
3. **Take screenshot** to see current state:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js
```
4. **Analyze the issue** based on:
- Computed styles
- Visual appearance
- User's description (if provided)
5. **Propose fix** with specific CSS changes
6. **After making changes**, use `/verify-changes` to confirm fix worked
Delegate the actual analysis and fix proposal to the css-debugger agent for expert-level diagnosis.

23
commands/nav.md Normal file
View File

@@ -0,0 +1,23 @@
---
description: Navigate the browser to a URL. Use --new to open in new tab.
allowed-tools: Bash(*)
argument-hint: <url> [--new]
model: haiku
---
# Navigate
Navigate the browser to a URL.
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-nav.js $ARGUMENTS
```
## Options
- `<url>`: Navigate current tab to URL
- `<url> --new`: Open URL in new tab
If URL doesn't include protocol, `https://` is assumed.
Confirm successful navigation with page title.

57
commands/perf.md Normal file
View File

@@ -0,0 +1,57 @@
---
description: Analyze page performance - load times, DOM size, memory, and large resources.
allowed-tools: Bash(*)
model: sonnet
---
# Performance Analysis
Run a comprehensive performance analysis on the current page.
Use the **performance-debugger agent** for this task.
## Collect Metrics
1. **Page Load Timing**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => {
const t = performance.timing;
return {
firstByte: t.responseStart - t.navigationStart,
domInteractive: t.domInteractive - t.navigationStart,
domContentLoaded: t.domContentLoadedEventEnd - t.navigationStart,
loadComplete: t.loadEventEnd - t.navigationStart
};
})()'
```
2. **DOM Analysis**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => {
const all = document.querySelectorAll("*");
return {
totalElements: all.length,
scripts: document.scripts.length,
stylesheets: document.styleSheets.length,
images: document.images.length
};
})()'
```
3. **Large Resources**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'performance.getEntriesByType("resource").filter(r => r.transferSize > 50000).map(r => ({ name: r.name.split("/").pop(), kb: (r.transferSize/1024).toFixed(1) })).slice(0,10)'
```
4. **Screenshot** for context:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js
```
## Report
Provide a summary with:
- Load time assessment (good/needs improvement/poor)
- DOM size assessment
- Largest resources
- Top 3 recommendations for improvement

39
commands/pick-element.md Normal file
View File

@@ -0,0 +1,39 @@
---
description: Launch interactive element picker for user to select DOM elements. Returns selectors and computed styles.
allowed-tools: Bash(*)
argument-hint: [message to display]
model: sonnet
---
# Pick Element
Launch the interactive element picker so the user can click on DOM elements.
## Instructions
Run the picker with the provided message (or default):
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-pick.js "$ARGUMENTS"
```
If no message provided, use: "Select the element you want to debug"
## User Instructions
Tell the user:
- **Click** on any element to select it
- **Cmd/Ctrl + Click** to add more elements to selection
- **Enter** to confirm multi-selection
- **Escape** to cancel
## Output
After the user selects elements, you'll receive:
- CSS selector for each element
- Tag, ID, and classes
- Dimensions and position
- Computed display, visibility, opacity
- Text content preview
Use this information to diagnose issues or write CSS/JS targeting these elements.

36
commands/resize.md Normal file
View File

@@ -0,0 +1,36 @@
---
description: Resize browser viewport. Use presets like --mobile or custom dimensions.
allowed-tools: Bash(*)
argument-hint: [--mobile | --tablet | --desktop | width height]
model: haiku
---
# Resize Viewport
Change the browser viewport size for responsive testing.
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js $ARGUMENTS
```
## Presets
| Flag | Size | Device |
|------|------|--------|
| `--mobile` | 375×667 | iPhone SE |
| `--iphone` | 390×844 | iPhone 14 |
| `--iphone-pro` | 393×852 | iPhone 14 Pro |
| `--android` | 412×915 | Pixel 7 |
| `--tablet` | 768×1024 | iPad |
| `--ipad-pro` | 1024×1366 | iPad Pro |
| `--laptop` | 1366×768 | Laptop |
| `--desktop` | 1920×1080 | Desktop |
| `--4k` | 3840×2160 | 4K Display |
## Custom Size
```bash
/resize 1440 900
```
After resizing, take a screenshot to verify layout.

22
commands/screenshot.md Normal file
View File

@@ -0,0 +1,22 @@
---
description: Take a screenshot of the current browser viewport. Use --full for full page, --selector for specific element.
allowed-tools: Bash(*)
argument-hint: [--full | --selector=".class"]
model: haiku
---
# Screenshot
Take a screenshot of the current browser state.
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js $ARGUMENTS
```
## Options
- No arguments: Viewport screenshot
- `--full`: Full page screenshot (scrollable content)
- `--selector=".class"`: Screenshot of specific element
After capturing, analyze the screenshot for any visual issues.

View File

@@ -0,0 +1,45 @@
---
description: Test responsive design across mobile, tablet, and desktop viewports. Takes screenshots at each breakpoint.
allowed-tools: Bash(*)
argument-hint: [url]
model: sonnet
---
# Test Responsive Design
Capture screenshots at multiple viewport sizes to verify responsive design.
## Instructions
1. If URL provided in $ARGUMENTS, navigate first:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-nav.js "$ARGUMENTS"
```
2. Test **Mobile** (375×667 - iPhone SE):
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --mobile
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/responsive-mobile.png
```
3. Test **Tablet** (768×1024 - iPad):
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --tablet
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/responsive-tablet.png
```
4. Test **Desktop** (1920×1080):
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --desktop
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/responsive-desktop.png
```
5. **Analyze all screenshots** and report:
- Layout shifts between breakpoints
- Text overflow or truncation
- Overlapping elements
- Hidden/missing content
- Spacing and alignment issues
- Navigation usability at each size
Provide specific recommendations for any issues found.

View File

@@ -0,0 +1,48 @@
---
description: Verify frontend changes after editing code. Takes screenshot, checks for errors, and reports results.
allowed-tools: Bash(*), Read
model: sonnet
---
# Verify Changes
After making frontend changes, verify they work correctly.
## Instructions
1. **Reload the page** to pick up changes:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'location.reload(true)'
```
2. **Wait for page load**:
```bash
sleep 2
```
3. **Take screenshot**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js
```
4. **Check for JavaScript errors**:
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors
```
5. **Analyze and report**:
- Compare screenshot to expected result
- List any JavaScript errors
- Identify if changes applied correctly
- Note any unexpected side effects
6. **If issues found**:
- Describe the problem
- Suggest fixes
- Offer to make corrections
7. **If successful**:
- Confirm changes work as expected
- Ask if any refinements needed
This command is designed for the **edit → verify → iterate** loop that enables self-debugging of frontend work.

24
commands/watch-console.md Normal file
View File

@@ -0,0 +1,24 @@
---
description: Watch browser console in real-time. Shows errors, warnings, and logs as they happen.
allowed-tools: Bash(*)
argument-hint: [--errors | --warnings]
model: haiku
---
# Watch Console
Monitor browser console output in real-time.
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --watch $ARGUMENTS
```
## Options
- No arguments: All console messages
- `--errors`: Only error messages
- `--warnings`: Errors and warnings
Press Ctrl+C to stop watching.
Report any errors that appear and suggest fixes.

24
commands/watch-network.md Normal file
View File

@@ -0,0 +1,24 @@
---
description: Watch network requests in real-time. Great for debugging API calls.
allowed-tools: Bash(*)
argument-hint: [--failures | --xhr]
model: haiku
---
# Watch Network
Monitor network requests in real-time.
```bash
node ~/.claude/plugins/*/skills/website-debug/scripts/browser-network.js --watch $ARGUMENTS
```
## Options
- No arguments: All network requests
- `--failures`: Only 4xx/5xx errors
- `--xhr`: Only XHR/fetch (API calls)
Press Ctrl+C to stop watching.
Report any failed requests and suggest potential causes.

30
hooks/hooks.json Normal file
View File

@@ -0,0 +1,30 @@
{
"$schema": "https://claude.ai/schemas/hooks.json",
"hooks": [
{
"event": "Stop",
"description": "Remind about verification after frontend file changes",
"matcher": {
"tool": "Write",
"path": "**/*.{css,scss,less,html,jsx,tsx,vue,svelte}"
},
"command": "echo '💡 Tip: Run /verify-changes to confirm your frontend changes work correctly'"
},
{
"event": "SubagentStop",
"description": "Suggest next steps after CSS debugger runs",
"matcher": {
"agentName": "css-debugger"
},
"command": "echo '✓ CSS analysis complete. Consider running /verify-changes after applying fixes.'"
},
{
"event": "SubagentStop",
"description": "Suggest next steps after JS debugger runs",
"matcher": {
"agentName": "js-debugger"
},
"command": "echo '✓ JavaScript analysis complete. Run /watch-console to monitor for new errors.'"
}
]
}

197
plugin.lock.json Normal file
View File

@@ -0,0 +1,197 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:AnthemFlynn/ccmp:plugins/website-debug",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "06e82c21b25228cf826d4958dfdd0e13082a7c1e",
"treeHash": "5c924b4662c56047a32efd7bb735fd19872c361a6b5783c4a50ac44dbb51278a",
"generatedAt": "2025-11-28T10:24:52.889220Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "website-debug",
"description": "Frontend website debugging toolkit with browser automation, visual verification, and self-debugging capabilities. Includes specialized agents for CSS, JavaScript, responsive testing, and performance analysis.",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "79634227edbd692b9571358bc63c2ca9cd01e59a00612c12c40244ec65c10907"
},
{
"path": "agents/css-debugger.md",
"sha256": "c520dc2066342f2d92032b7a7570cd7093aa05bfa4255e6e5e33d7a8561fe6f6"
},
{
"path": "agents/network-debugger.md",
"sha256": "c90e1e6acd60dfe13785e3c760ead18d9d67e647ba31734c0b04f36945cb9874"
},
{
"path": "agents/js-debugger.md",
"sha256": "45a24a7781a31cabd90582bf65e822fb2dcae69c3afb4f2078c449896a6d9e35"
},
{
"path": "agents/responsive-tester.md",
"sha256": "72750df593d7a770c5babe2496914cab558f726839a23548348d7cb472e1c7ae"
},
{
"path": "agents/performance-debugger.md",
"sha256": "4dd3d706235d1e1b5c5334bc6610e7e3ec803ff9afa7204c507c0561d345d72b"
},
{
"path": "agents/visual-verifier.md",
"sha256": "5651193d9508683d5cdc2bded937a836987d3491bfcbc48f53410288499ed8ad"
},
{
"path": "hooks/hooks.json",
"sha256": "2e7f356bd0da6a5b9bdd20cde94b44d04a0cc51b5f5a504a7d22a996765fc297"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "079780c702cd94360e464e92fc18f791d23029f325900d563687aa38702c57bc"
},
{
"path": "commands/debug-page.md",
"sha256": "0985f7c6980cbf109b72b5dad7fb0dd487095a4193b82097a2473794193cbe63"
},
{
"path": "commands/nav.md",
"sha256": "635a690a21382a4cc0e49cae61281ea407318f598c9145f21b3aec076aeecda3"
},
{
"path": "commands/diagnose.md",
"sha256": "7be40f64f565f661d710277ee5c07b2bd12f85f61650f7743579b63194f9263e"
},
{
"path": "commands/fix-css.md",
"sha256": "99d11d222691125b3b55bdb1fef7937e9a89d839cd1afb0237bf0513f9db7950"
},
{
"path": "commands/resize.md",
"sha256": "0630e37295de6439c4ef19aedb7d47a8b179cdfbd23cea1416a38154102fa8c2"
},
{
"path": "commands/dom.md",
"sha256": "952c7b54574e7e725f7c8698d46c8bdaaf340e72ffa7637306de512ff02ea47e"
},
{
"path": "commands/pick-element.md",
"sha256": "899aa4822e33262eb4c7abbb19e5a6d312a11f119dc38ff2030f9655fcb395b4"
},
{
"path": "commands/screenshot.md",
"sha256": "392fbe4faa6621d8a394486f15dbae6a2ba44cc886ca086db40ebaaa6ab3ab02"
},
{
"path": "commands/browser-eval.md",
"sha256": "2fb35a7db242a9486aa890ffbc314e6e7dca2624ef1ad885c69ffcb024b09ebd"
},
{
"path": "commands/watch-network.md",
"sha256": "3b4076d4b782cfd903f8500b0f8aae981e0608b632eed0591a4cf4eebb03d831"
},
{
"path": "commands/verify-changes.md",
"sha256": "9ec29a2f4933610837ae9621734c7055b330a987e811b9271492452feff7eb88"
},
{
"path": "commands/browser-close.md",
"sha256": "48e9656a53b40bc204e30e9592de323bba5e81a0e4e753676385e169c7051075"
},
{
"path": "commands/perf.md",
"sha256": "fd062e5a0ea707026bed55d0d23f15aee49565161574d1fe46b526efbad23fe3"
},
{
"path": "commands/test-responsive.md",
"sha256": "194481c3622924a28957c578f72473527d4a6935cc15b1afcbcdc99516f74668"
},
{
"path": "commands/watch-console.md",
"sha256": "812cf1cf54bcad8beb4855223ade88e47abd0686457c0df4e121c7b1e3cad4e6"
},
{
"path": "commands/browser-start.md",
"sha256": "84b4f774dc9b6efa14ba03bbe3dafea632800b0b8f9d503512533818ad807a8d"
},
{
"path": "skills/website-debug/package.json",
"sha256": "a4ea07139d10f79f32f35f2955f48ac0908f1451bba54de3ba462299952b8df8"
},
{
"path": "skills/website-debug/SKILL.md",
"sha256": "4eb3e5dd3ed65fdcca3ce2779605ec268cb0e31c941b6e553dfa92ab2f46f14a"
},
{
"path": "skills/website-debug/references/css-debug.md",
"sha256": "4d8e80c5c491d74440cbb7b564e41b2a63ed0c6415645403b236085456902d9e"
},
{
"path": "skills/website-debug/references/self-debug.md",
"sha256": "05c901f82736ee874cb6da76dc64467eb104b4e41b901e0619d0aff3efc90678"
},
{
"path": "skills/website-debug/references/js-debug.md",
"sha256": "a8e5f535a4e00a1302f58ad5fa72cfb144be4178331e8e8354d316b093690ea5"
},
{
"path": "skills/website-debug/scripts/browser-start.js",
"sha256": "3bb27f610e9e058a010e92b73c7137955edf95f2aa5636c335250949169c9263"
},
{
"path": "skills/website-debug/scripts/setup.sh",
"sha256": "5eb517d1ba23e55dec71046aafe8eaef00b3511ffe96d297a8ec8a336538b4f8"
},
{
"path": "skills/website-debug/scripts/browser-screenshot.js",
"sha256": "edfbe51c669889ef19e57c04a264826b965b801912470dad0fe56e3fe3d62c58"
},
{
"path": "skills/website-debug/scripts/browser-console.js",
"sha256": "87a452b992b1988905c505b047610b6d1d2df53d2bf5e917a7878fae2747fa12"
},
{
"path": "skills/website-debug/scripts/browser-close.js",
"sha256": "ea09fdff34740cc9745c7ec39cda31f48d578ab963879a40c0b4dea92943e0b1"
},
{
"path": "skills/website-debug/scripts/browser-resize.js",
"sha256": "9546d21241462147fc3d27188f2918f226b8f8ed65e7cb5d0db0000551624652"
},
{
"path": "skills/website-debug/scripts/browser-nav.js",
"sha256": "0fed01ca09f2e2ab2c9b5498ca4bcb6f6a02ca55b0d3e6b3a20fbc9b40966d84"
},
{
"path": "skills/website-debug/scripts/browser-eval.js",
"sha256": "4e75ddda0da5f056f62598b79796b3c26d5f6746d380c710577f33ed9536ac9c"
},
{
"path": "skills/website-debug/scripts/browser-dom.js",
"sha256": "2da8acdc37a8e284c5b0f7ed7a725209c6e752cf9312b09a35c93305a5569d15"
},
{
"path": "skills/website-debug/scripts/browser-pick.js",
"sha256": "47233581ebfc12f81d6bea46e4465b686ef2439b00e46c5a08d917c0b5be41f1"
},
{
"path": "skills/website-debug/scripts/browser-network.js",
"sha256": "077ffa8e75f21d6eca6648b783c79b10a9695ff97991757499d60d8f5a2cd141"
}
],
"dirSha256": "5c924b4662c56047a32efd7bb735fd19872c361a6b5783c4a50ac44dbb51278a"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

View File

@@ -0,0 +1,202 @@
---
name: website-debug
description: >
Frontend website debugging toolkit using Chrome DevTools Protocol with Playwright/WebKit fallbacks.
Use this skill when: (1) Debugging CSS, HTML, or JavaScript issues on a webpage, (2) Taking screenshots
to verify visual changes, (3) Inspecting DOM structure or console errors, (4) Testing responsive layouts,
(5) Extracting selectors for automation, (6) Self-debugging frontend work Claude has created,
(7) User says "debug this page", "check my site", "why doesn't this look right", or "fix the frontend".
Supports Chrome (primary) and Safari/WebKit (via Playwright). Designed for agent-driven debugging loops.
---
# Website Debugging Skill
Lightweight, token-efficient browser debugging toolkit for frontend development. Uses CLI scripts instead of MCP servers to minimize context usage (~300 tokens vs 13k-18k).
## Quick Start
Use the slash commands for easiest access:
- `/debug-page <url>` - Start debugging session
- `/screenshot` - Take screenshot
- `/pick-element` - Interactive element selection
- `/test-responsive` - Test at all breakpoints
- `/verify-changes` - Verify after making changes
### Or use scripts directly:
```bash
# Start browser
node scripts/browser-start.js
node scripts/browser-start.js --profile # Preserve logins
node scripts/browser-start.js --webkit # Safari/WebKit
# Navigate
node scripts/browser-nav.js https://localhost:3000
# Debug
node scripts/browser-screenshot.js
node scripts/browser-eval.js 'document.title'
node scripts/browser-pick.js "Select element"
node scripts/browser-console.js --errors
node scripts/browser-network.js --failures
```
## Core Tools Reference
| Script | Purpose | Output |
|--------|---------|--------|
| `browser-start.sh` | Launch Chrome/WebKit with debug port | Status message |
| `browser-nav.sh <url>` | Navigate to URL | Confirmation |
| `browser-screenshot.sh` | Capture viewport | File path (PNG) |
| `browser-eval.sh '<js>'` | Run JS in page | Result or error |
| `browser-pick.sh "<msg>"` | Interactive selector | CSS selectors |
| `browser-console.sh` | Get console output | Logs/errors |
| `browser-network.sh` | Network activity | Request/response data |
| `browser-dom.sh "<sel>"` | Get DOM snapshot | HTML fragment |
| `browser-close.sh` | Close browser | Confirmation |
## Self-Debugging Workflow
When debugging frontend code Claude has written or modified:
### 1. Visual Verification Loop
```bash
# After making CSS/HTML changes, verify visually
./scripts/browser-screenshot.sh
# Claude reads the screenshot, identifies issues, iterates
```
### 2. Console Error Detection
```bash
# Check for JavaScript errors after changes
./scripts/browser-console.sh --errors
# Fix any errors found, re-verify
```
### 3. Responsive Testing
```bash
# Test at different viewport sizes
./scripts/browser-resize.sh 375 667 # iPhone SE
./scripts/browser-screenshot.sh
./scripts/browser-resize.sh 768 1024 # iPad
./scripts/browser-screenshot.sh
./scripts/browser-resize.sh 1920 1080 # Desktop
./scripts/browser-screenshot.sh
```
### 4. Element Inspection
```bash
# When user reports "X looks wrong", have them select it
./scripts/browser-pick.sh "Click on the element that looks wrong"
# Returns detailed info including computed styles
```
## Browser Engine Selection
### Chrome (Default)
Primary engine. Uses Chrome DevTools Protocol on port 9222.
- Best debugging experience
- Full DevTools compatibility
- Use `--profile` to preserve logins
### WebKit/Safari
Fallback via Playwright's WebKit build. Closest to Safari behavior on macOS.
```bash
./scripts/browser-start.sh --webkit
```
- Use for Safari-specific testing
- Layout verification
- WebKit-specific bugs
### When to Use Each
| Scenario | Engine |
|----------|--------|
| General debugging | Chrome |
| Safari layout issues | WebKit |
| Testing with logins | Chrome `--profile` |
| Cross-browser comparison | Both |
| CI/headless testing | Chrome or WebKit |
## Advanced Usage
### Detailed Documentation
For complex scenarios, load the appropriate reference:
- **CSS Debugging**: See [references/css-debug.md](references/css-debug.md)
- **JavaScript Errors**: See [references/js-debug.md](references/js-debug.md)
- **Network Issues**: See [references/network-debug.md](references/network-debug.md)
- **Responsive Design**: See [references/responsive-debug.md](references/responsive-debug.md)
- **Performance**: See [references/performance-debug.md](references/performance-debug.md)
### Composable Output
All scripts output to files when practical, enabling:
```bash
# Capture multiple screenshots for comparison
./scripts/browser-screenshot.sh > /tmp/before.png
# ... make changes ...
./scripts/browser-screenshot.sh > /tmp/after.png
# Save DOM snapshot for analysis
./scripts/browser-dom.sh "body" > /tmp/page-structure.html
# Export console log for review
./scripts/browser-console.sh > /tmp/console-log.txt
```
### Chaining Commands
```bash
# Navigate and screenshot in one command
./scripts/browser-nav.sh https://example.com && ./scripts/browser-screenshot.sh
# Full page audit
./scripts/browser-nav.sh $URL && \
./scripts/browser-console.sh --errors > /tmp/errors.txt && \
./scripts/browser-screenshot.sh
```
## Setup Requirements
### Chrome
Chrome must be launchable from command line. The start script handles this automatically.
### WebKit (Optional)
For Safari testing, ensure Playwright is installed:
```bash
npm install -g playwright
npx playwright install webkit
```
### Dependencies
Scripts require Node.js and puppeteer-core:
```bash
npm install -g puppeteer-core
```
## Troubleshooting
### "Cannot connect to browser"
Browser may not be running or wrong port:
```bash
./scripts/browser-start.sh # Restart browser
```
### "Permission denied"
Scripts may need execute permission:
```bash
chmod +x ./scripts/*.sh
```
### Chrome already running
Kill existing instances first:
```bash
killall "Google Chrome" 2>/dev/null
./scripts/browser-start.sh
```
### WebKit not found
Install Playwright browsers:
```bash
npx playwright install webkit
```

View File

@@ -0,0 +1,38 @@
{
"name": "website-debug-skill",
"version": "1.0.0",
"description": "Frontend website debugging toolkit for Claude Code",
"type": "module",
"scripts": {
"setup": "bash scripts/setup.sh",
"start": "node scripts/browser-start.js",
"start:profile": "node scripts/browser-start.js --profile",
"start:webkit": "node scripts/browser-start.js --webkit",
"nav": "node scripts/browser-nav.js",
"screenshot": "node scripts/browser-screenshot.js",
"eval": "node scripts/browser-eval.js",
"pick": "node scripts/browser-pick.js",
"console": "node scripts/browser-console.js",
"resize": "node scripts/browser-resize.js",
"dom": "node scripts/browser-dom.js"
},
"dependencies": {
"puppeteer-core": "^23.0.0"
},
"optionalDependencies": {
"playwright": "^1.48.0"
},
"engines": {
"node": ">=18.0.0"
},
"keywords": [
"debugging",
"chrome",
"devtools",
"browser",
"automation",
"claude-code",
"skill"
],
"license": "MIT"
}

View File

@@ -0,0 +1,290 @@
# CSS Debugging Reference
Advanced CSS debugging workflows and patterns for frontend development.
## Quick Diagnostics
### Check Element Visibility
```bash
# Is element visible?
./browser-eval.js '(() => {
const el = document.querySelector(".target");
const s = getComputedStyle(el);
return {
display: s.display,
visibility: s.visibility,
opacity: s.opacity,
hidden: el.hidden,
height: s.height,
width: s.width,
overflow: s.overflow
};
})()'
```
### Element Not Showing?
Common causes and checks:
```bash
# Check display property
./browser-eval.js 'getComputedStyle(document.querySelector(".target")).display'
# If "none" - element is hidden by CSS
# Check visibility
./browser-eval.js 'getComputedStyle(document.querySelector(".target")).visibility'
# If "hidden" - element takes space but invisible
# Check opacity
./browser-eval.js 'getComputedStyle(document.querySelector(".target")).opacity'
# If "0" - element is transparent
# Check dimensions
./browser-eval.js 'document.querySelector(".target").getBoundingClientRect()'
# If width/height is 0 - element has no size
# Check if off-screen
./browser-eval.js '(() => {
const r = document.querySelector(".target").getBoundingClientRect();
return {
inViewport: r.top >= 0 && r.left >= 0 && r.bottom <= window.innerHeight && r.right <= window.innerWidth,
position: { top: r.top, left: r.left }
};
})()'
```
## Layout Issues
### Flexbox Debugging
```bash
# Check flex container
./browser-eval.js '(() => {
const el = document.querySelector(".flex-container");
const s = getComputedStyle(el);
return {
display: s.display,
flexDirection: s.flexDirection,
justifyContent: s.justifyContent,
alignItems: s.alignItems,
flexWrap: s.flexWrap,
gap: s.gap
};
})()'
# Check flex items
./browser-eval.js '[...document.querySelectorAll(".flex-container > *")].map(el => {
const s = getComputedStyle(el);
return {
flexGrow: s.flexGrow,
flexShrink: s.flexShrink,
flexBasis: s.flexBasis,
alignSelf: s.alignSelf
};
})'
```
### Grid Debugging
```bash
# Check grid container
./browser-eval.js '(() => {
const el = document.querySelector(".grid-container");
const s = getComputedStyle(el);
return {
display: s.display,
gridTemplateColumns: s.gridTemplateColumns,
gridTemplateRows: s.gridTemplateRows,
gap: s.gap,
gridAutoFlow: s.gridAutoFlow
};
})()'
```
### Box Model
```bash
# Full box model breakdown
./browser-eval.js '(() => {
const el = document.querySelector(".target");
const s = getComputedStyle(el);
const r = el.getBoundingClientRect();
return {
content: { width: r.width, height: r.height },
padding: { top: s.paddingTop, right: s.paddingRight, bottom: s.paddingBottom, left: s.paddingLeft },
border: { top: s.borderTopWidth, right: s.borderRightWidth, bottom: s.borderBottomWidth, left: s.borderLeftWidth },
margin: { top: s.marginTop, right: s.marginRight, bottom: s.marginBottom, left: s.marginLeft },
boxSizing: s.boxSizing
};
})()'
```
## Positioning Problems
### Z-Index Stacking
```bash
# Find all positioned elements with z-index
./browser-eval.js '[...document.querySelectorAll("*")].filter(el => {
const s = getComputedStyle(el);
return s.position !== "static" && s.zIndex !== "auto";
}).map(el => ({
selector: el.tagName + (el.id ? "#" + el.id : "") + (el.className ? "." + el.className.split(" ")[0] : ""),
position: getComputedStyle(el).position,
zIndex: getComputedStyle(el).zIndex
})).sort((a, b) => parseInt(b.zIndex) - parseInt(a.zIndex))'
```
### Fixed/Sticky Elements
```bash
# Find fixed/sticky elements
./browser-eval.js '[...document.querySelectorAll("*")].filter(el => {
const pos = getComputedStyle(el).position;
return pos === "fixed" || pos === "sticky";
}).map(el => ({
tag: el.tagName.toLowerCase(),
id: el.id,
class: el.className,
position: getComputedStyle(el).position
}))'
```
## Responsive Issues
### Media Query Testing
```bash
# Test different breakpoints
./browser-resize.js --mobile && ./browser-screenshot.js --output=/tmp/mobile.png
./browser-resize.js --tablet && ./browser-screenshot.js --output=/tmp/tablet.png
./browser-resize.js --desktop && ./browser-screenshot.js --output=/tmp/desktop.png
```
### Check Current Viewport
```bash
./browser-eval.js '({
viewport: { width: window.innerWidth, height: window.innerHeight },
screen: { width: screen.width, height: screen.height },
devicePixelRatio: window.devicePixelRatio
})'
```
## Typography Issues
### Font Not Loading
```bash
# Check computed font
./browser-eval.js 'getComputedStyle(document.querySelector(".text")).fontFamily'
# Check if webfont loaded
./browser-eval.js 'document.fonts.check("16px YourFontName")'
# List all loaded fonts
./browser-eval.js '[...document.fonts].map(f => ({ family: f.family, style: f.style, weight: f.weight, status: f.status }))'
```
### Text Overflow
```bash
# Check text overflow settings
./browser-eval.js '(() => {
const el = document.querySelector(".truncated-text");
const s = getComputedStyle(el);
return {
overflow: s.overflow,
textOverflow: s.textOverflow,
whiteSpace: s.whiteSpace,
width: s.width,
maxWidth: s.maxWidth
};
})()'
```
## Color & Visual
### Check Colors
```bash
# Get all colors used on element
./browser-eval.js '(() => {
const el = document.querySelector(".target");
const s = getComputedStyle(el);
return {
color: s.color,
backgroundColor: s.backgroundColor,
borderColor: s.borderColor,
outlineColor: s.outlineColor
};
})()'
```
### Find Elements by Color
```bash
# Find all elements with a specific background
./browser-eval.js '[...document.querySelectorAll("*")].filter(el =>
getComputedStyle(el).backgroundColor === "rgb(255, 0, 0)"
).map(el => el.tagName + (el.id ? "#" + el.id : ""))'
```
## Performance Concerns
### Large DOM Check
```bash
./browser-eval.js '({
totalElements: document.querySelectorAll("*").length,
deepestNesting: (() => {
let max = 0;
document.querySelectorAll("*").forEach(el => {
let depth = 0, node = el;
while (node.parentElement) { depth++; node = node.parentElement; }
if (depth > max) max = depth;
});
return max;
})()
})'
```
### Reflow Triggers
Watch for properties that cause layout recalculation:
- offsetTop/Left/Width/Height
- scrollTop/Left/Width/Height
- clientTop/Left/Width/Height
- getComputedStyle()
- getBoundingClientRect()
## Animation Debugging
### Check Animations
```bash
./browser-eval.js '(() => {
const el = document.querySelector(".animated");
const s = getComputedStyle(el);
return {
animation: s.animation,
animationName: s.animationName,
animationDuration: s.animationDuration,
animationTimingFunction: s.animationTimingFunction,
animationDelay: s.animationDelay,
transition: s.transition
};
})()'
```
### Force Animation State
```bash
# Pause all animations
./browser-eval.js 'document.querySelectorAll("*").forEach(el => el.style.animationPlayState = "paused")'
# Resume animations
./browser-eval.js 'document.querySelectorAll("*").forEach(el => el.style.animationPlayState = "")'
```
## Common CSS Fixes
### Overflow Scroll Not Working
Check these in order:
1. Parent has defined height
2. `overflow` is set to `scroll` or `auto`
3. Content is actually taller than container
### Element Behind Another
1. Check z-index values
2. Ensure positioned (relative/absolute/fixed)
3. Check stacking context (transform, opacity < 1, etc. create new contexts)
### Flexbox Not Centering
1. Container has height
2. `align-items` for vertical, `justify-content` for horizontal
3. Check `flex-direction` - swaps main/cross axis

View File

@@ -0,0 +1,259 @@
# JavaScript Debugging Reference
Workflows for diagnosing and fixing JavaScript issues in web applications.
## Error Detection
### Capture Runtime Errors
```bash
# Watch for errors in real-time
./browser-console.js --errors --watch
# Inject error capture (run once per page load)
./browser-eval.js 'window.onerror = (msg, src, line, col, err) => {
window.__jsErrors = window.__jsErrors || [];
window.__jsErrors.push({ message: msg, source: src, line, col, stack: err?.stack });
return false;
}'
# Check captured errors
./browser-eval.js 'window.__jsErrors || []'
```
### Unhandled Promise Rejections
```bash
# Capture unhandled rejections
./browser-eval.js 'window.addEventListener("unhandledrejection", e => {
window.__promiseErrors = window.__promiseErrors || [];
window.__promiseErrors.push({ reason: e.reason?.message || String(e.reason) });
})'
# Check promise errors
./browser-eval.js 'window.__promiseErrors || []'
```
## Common Error Types
### TypeError: Cannot read property 'x' of undefined
**Cause**: Accessing property on null/undefined
**Debug**:
```bash
# Check if element exists
./browser-eval.js 'document.querySelector(".my-element")'
# Check if variable/property exists
./browser-eval.js 'typeof window.myApp?.myProperty'
```
### ReferenceError: x is not defined
**Cause**: Variable not in scope or not declared
**Debug**:
```bash
# Check if global exists
./browser-eval.js '"myVariable" in window'
# List all globals matching pattern
./browser-eval.js 'Object.keys(window).filter(k => k.includes("my"))'
```
### SyntaxError
**Cause**: Invalid JavaScript syntax
**Debug**: Check browser console for file and line number
```bash
./browser-console.js --errors
```
## State Inspection
### Check Global Variables
```bash
# List all custom globals (excluding built-ins)
./browser-eval.js '(() => {
const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
const builtins = new Set(Object.keys(iframe.contentWindow));
iframe.remove();
return Object.keys(window).filter(k => !builtins.has(k) && !k.startsWith("__"));
})()'
```
### Inspect Application State
```bash
# React state (if using React DevTools globals)
./browser-eval.js 'window.__REACT_DEVTOOLS_GLOBAL_HOOK__?.renderers'
# Redux store (if exposed)
./browser-eval.js 'window.__store?.getState()'
# Vue instance (if exposed)
./browser-eval.js 'document.querySelector("#app")?.__vue__?.$data'
```
### Local Storage / Session Storage
```bash
# View localStorage
./browser-eval.js 'Object.fromEntries(Object.entries(localStorage))'
# View sessionStorage
./browser-eval.js 'Object.fromEntries(Object.entries(sessionStorage))'
```
## Event Debugging
### Check Event Listeners
```bash
# Get listeners on element (Chrome only)
./browser-eval.js 'getEventListeners(document.querySelector(".button"))'
# Alternative: Check if onclick exists
./browser-eval.js 'typeof document.querySelector(".button").onclick'
```
### Debug Event Firing
```bash
# Log all click events
./browser-eval.js 'document.addEventListener("click", e => console.log("Click:", e.target.tagName, e.target.className), true)'
# Log all form submissions
./browser-eval.js 'document.addEventListener("submit", e => console.log("Form submit:", e.target.action), true)'
```
### Simulate Events
```bash
# Click element
./browser-eval.js 'document.querySelector(".button").click()'
# Dispatch custom event
./browser-eval.js 'document.querySelector(".target").dispatchEvent(new Event("input", { bubbles: true }))'
# Submit form
./browser-eval.js 'document.querySelector("form").submit()'
```
## Async Debugging
### Track Pending Promises
```bash
# Wrap fetch to log all requests
./browser-eval.js '(() => {
const orig = window.fetch;
window.fetch = (...args) => {
console.log("Fetch:", args[0]);
return orig.apply(window, args);
};
})()'
```
### Debug setTimeout/setInterval
```bash
# List all active timers (approximate)
./browser-eval.js '(() => {
const ids = [];
const id = setTimeout(() => {}, 0);
clearTimeout(id);
for (let i = 1; i < id; i++) {
clearTimeout(i);
clearInterval(i);
}
return `Cleared timers up to ID ${id}`;
})()'
```
## Network-Related JS Issues
### Check if Fetch Failed
```bash
./browser-eval.js 'fetch("/api/data").then(r => ({ status: r.status, ok: r.ok })).catch(e => ({ error: e.message }))'
```
### CORS Errors
Check console for CORS errors:
```bash
./browser-console.js --errors | grep -i cors
```
### API Response Issues
```bash
# Test API and inspect response
./browser-eval.js 'fetch("/api/endpoint").then(r => r.json()).then(d => JSON.stringify(d, null, 2)).catch(e => e.message)'
```
## Module/Import Issues
### Check if Module Loaded
```bash
# Check for module in global scope
./browser-eval.js '"ModuleName" in window'
# Check ES module
./browser-eval.js 'import("./module.js").then(m => Object.keys(m)).catch(e => e.message)'
```
### Script Load Order
```bash
# List all scripts in order
./browser-eval.js '[...document.querySelectorAll("script")].map(s => ({ src: s.src || "(inline)", async: s.async, defer: s.defer }))'
```
## Performance Issues
### Identify Long Tasks
```bash
# Monitor long tasks
./browser-eval.js '(() => {
const observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => console.log("Long task:", entry.duration, "ms"));
});
observer.observe({ entryTypes: ["longtask"] });
return "Long task observer started";
})()'
```
### Memory Leaks
```bash
# Get heap size (Chrome)
./browser-eval.js 'performance.memory ? { usedHeap: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) + "MB", totalHeap: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024) + "MB" } : "Not available"'
```
## Framework-Specific
### React Debugging
```bash
# Find React components in DOM
./browser-eval.js '(() => {
const reactRoot = document.querySelector("[data-reactroot], #root, #app");
return reactRoot ? "React app found" : "No React root detected";
})()'
# Check for React errors
./browser-console.js --errors | grep -i react
```
### Vue Debugging
```bash
# Check Vue version
./browser-eval.js 'window.Vue?.version || document.querySelector("[data-v-]") ? "Vue detected" : "No Vue"'
```
### Next.js/Nuxt Hydration
```bash
# Check for hydration errors
./browser-console.js --errors | grep -i hydrat
```
## Quick Fixes
### Script Not Running
1. Check if script loaded: `./browser-dom.js "script[src*='filename']"`
2. Check for syntax errors: `./browser-console.js --errors`
3. Check load order: Ensure dependencies load first
### Event Handler Not Working
1. Check element exists when handler attached
2. Verify selector is correct
3. Check for event.preventDefault() or stopPropagation()
### Async Code Not Executing
1. Check if Promise rejects: Add .catch() logging
2. Verify await is in async function
3. Check network requests complete successfully

View File

@@ -0,0 +1,288 @@
# Self-Debugging Workflow
Patterns for Claude and AI agents to verify and fix their own frontend work.
## Core Principle
After making any frontend change, verify it worked:
1. **Screenshot** - Visual verification
2. **Console** - Check for errors
3. **DOM** - Confirm structure
4. **Iterate** - Fix issues and repeat
## Basic Verification Loop
```bash
# After modifying HTML/CSS/JS files:
# 1. Reload the page (if not using hot reload)
./browser-nav.js http://localhost:3000
# 2. Take screenshot to verify visual result
./browser-screenshot.js
# 3. Check for any JavaScript errors
./browser-console.js --errors
# 4. If issues found, fix and repeat
```
## Structured Debugging Protocol
### Phase 1: Initial Assessment
```bash
# Get page summary
./browser-dom.js
# Take baseline screenshot
./browser-screenshot.js --output=/tmp/current-state.png
# Capture any errors
./browser-console.js --errors > /tmp/errors.txt
```
### Phase 2: Make Changes
After editing source files:
```bash
# Force reload (bypass cache)
./browser-eval.js 'location.reload(true)'
# Wait for page load
sleep 2
# Take new screenshot
./browser-screenshot.js --output=/tmp/after-change.png
```
### Phase 3: Verify
```bash
# Check for new errors
./browser-console.js --errors
# Verify element exists (example: new button)
./browser-eval.js 'document.querySelector(".new-button") ? "Found" : "Missing"'
# Verify styling applied
./browser-eval.js 'getComputedStyle(document.querySelector(".new-button")).backgroundColor'
```
### Phase 4: Iterate
If issues found:
1. Identify the problem from screenshot/console
2. Fix the source code
3. Return to Phase 2
## Common Self-Fix Patterns
### CSS Not Applying
**Symptom**: Element exists but looks wrong
**Diagnosis**:
```bash
# Check if styles computed correctly
./browser-eval.js 'getComputedStyle(document.querySelector(".my-element")).cssText.slice(0, 500)'
# Check for conflicting selectors
./browser-eval.js '(() => {
const el = document.querySelector(".my-element");
const sheets = [...document.styleSheets];
const matches = [];
sheets.forEach(sheet => {
try {
[...sheet.cssRules].forEach(rule => {
if (el.matches(rule.selectorText)) matches.push(rule.selectorText);
});
} catch {}
});
return matches;
})()'
```
**Fixes to try**:
1. Increase specificity
2. Check for `!important` overrides
3. Verify selector matches element
### Element Not Visible
**Symptom**: DOM shows element but not visible
**Diagnosis**:
```bash
./browser-eval.js '(() => {
const el = document.querySelector(".my-element");
const s = getComputedStyle(el);
const r = el.getBoundingClientRect();
return {
exists: !!el,
display: s.display,
visibility: s.visibility,
opacity: s.opacity,
width: r.width,
height: r.height,
inViewport: r.top < window.innerHeight && r.bottom > 0
};
})()'
```
**Fixes based on result**:
- `display: none` → Check CSS rules hiding it
- `width/height: 0` → Add dimensions or content
- `opacity: 0` → Check for fade animations
- Not in viewport → Check positioning/scroll
### JavaScript Not Running
**Symptom**: Interactive features don't work
**Diagnosis**:
```bash
# Check for errors
./browser-console.js --errors
# Check if script loaded
./browser-eval.js 'document.querySelectorAll("script").length'
# Check if function exists
./browser-eval.js 'typeof myFunction'
```
**Fixes based on result**:
- Syntax error → Fix the JavaScript
- Script not loaded → Check path/build process
- Function undefined → Check load order, exports
### Layout Broken
**Symptom**: Elements positioned incorrectly
**Diagnosis**:
```bash
# Take screenshot
./browser-screenshot.js
# Check flex/grid container
./browser-eval.js '(() => {
const container = document.querySelector(".container");
const s = getComputedStyle(container);
return { display: s.display, flexDirection: s.flexDirection, gridTemplateColumns: s.gridTemplateColumns };
})()'
```
**Common fixes**:
- Missing `display: flex/grid` on container
- Wrong `flex-direction`
- Missing `width` on flex children
- Grid columns not matching children
## Responsive Verification
### Test All Breakpoints
```bash
# Mobile
./browser-resize.js --mobile
./browser-screenshot.js --output=/tmp/mobile.png
./browser-console.js --errors
# Tablet
./browser-resize.js --tablet
./browser-screenshot.js --output=/tmp/tablet.png
# Desktop
./browser-resize.js --desktop
./browser-screenshot.js --output=/tmp/desktop.png
```
### Compare Screenshots
After each breakpoint, Claude should analyze the screenshot for:
- Layout shifts
- Overlapping elements
- Text overflow
- Hidden/missing elements
- Incorrect spacing
## Interactive Feature Testing
### Form Validation
```bash
# Fill form with test data
./browser-eval.js 'document.querySelector("#email").value = "test@example.com"'
./browser-eval.js 'document.querySelector("#password").value = "test123"'
# Submit and check result
./browser-eval.js 'document.querySelector("form").submit()'
./browser-screenshot.js
./browser-console.js --errors
```
### Click/Hover States
```bash
# Test button click
./browser-eval.js 'document.querySelector(".button").click()'
./browser-screenshot.js
# Check state changed
./browser-eval.js 'document.querySelector(".modal")?.style.display'
```
## Automated Verification Script
For repeated checks, use this pattern:
```bash
#!/bin/bash
# verify-page.sh - Run after changes
URL="${1:-http://localhost:3000}"
OUT_DIR="/tmp/debug-$(date +%s)"
mkdir -p "$OUT_DIR"
echo "Verifying $URL..."
# Navigate
./browser-nav.js "$URL"
sleep 2
# Screenshot
./browser-screenshot.js --output="$OUT_DIR/screenshot.png"
echo "Screenshot: $OUT_DIR/screenshot.png"
# Errors
./browser-console.js --errors > "$OUT_DIR/errors.txt"
if [ -s "$OUT_DIR/errors.txt" ]; then
echo "⚠️ Errors found:"
cat "$OUT_DIR/errors.txt"
else
echo "✓ No JavaScript errors"
fi
# DOM summary
./browser-dom.js > "$OUT_DIR/dom.txt"
echo "DOM summary saved"
echo "Done. Files in $OUT_DIR"
```
## Error Recovery Strategies
### When Screenshot Shows Blank Page
1. Check console for critical errors
2. Verify server is running
3. Check network requests (404, 500)
4. Verify correct URL
### When Console Shows Many Errors
1. Fix first error (often causes cascade)
2. Reload and recheck
3. Address errors in order
### When Element Is "Almost Right"
1. Use picker to select element: `./browser-pick.js "Select the problematic element"`
2. Get full computed styles
3. Compare to expected values
4. Adjust CSS incrementally
## Best Practices
1. **Always screenshot after changes** - Visual verification catches most issues
2. **Check console immediately** - Catch JS errors early
3. **Test responsive early** - Layout issues compound
4. **Verify one change at a time** - Easier to identify what broke
5. **Save debug output** - Reference for comparison
6. **Use the picker for precision** - Get exact selectors from user

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env node
/**
* browser-close.js - Close browser session
*
* Usage:
* ./browser-close.js # Close gracefully
* ./browser-close.js --force # Kill all Chrome instances
*/
import puppeteer from "puppeteer-core";
import { execSync } from "node:child_process";
import { platform } from "node:os";
const args = process.argv.slice(2);
const force = args.includes("--force");
const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222";
if (args.includes("--help") || args.includes("-h")) {
console.log(`
browser-close.js - Close browser session
Usage:
./browser-close.js [options]
Options:
--force Kill all Chrome debug instances
--port=PORT Connect to custom debug port (default: 9222)
Examples:
./browser-close.js
./browser-close.js --force
`);
process.exit(0);
}
if (force) {
try {
if (platform() === "darwin") {
execSync("killall 'Google Chrome' 2>/dev/null", { stdio: "ignore" });
} else if (platform() === "linux") {
execSync("pkill -f 'chrome.*remote-debugging' 2>/dev/null", { stdio: "ignore" });
}
console.log("✓ Force killed Chrome instances");
} catch {
console.log("No Chrome instances to kill");
}
process.exit(0);
}
try {
const browser = await puppeteer.connect({
browserURL: `http://localhost:${port}`,
defaultViewport: null
});
await browser.close();
console.log("✓ Browser closed");
} catch (e) {
if (e.message?.includes("ECONNREFUSED")) {
console.log("No browser session to close");
} else {
// Try force close as fallback
try {
if (platform() === "darwin") {
execSync("killall 'Google Chrome' 2>/dev/null", { stdio: "ignore" });
}
console.log("✓ Browser closed (via kill)");
} catch {
console.error(`Could not close browser: ${e.message}`);
}
}
}

View File

@@ -0,0 +1,175 @@
#!/usr/bin/env node
/**
* browser-console.js - Get console messages from page
*
* Usage:
* ./browser-console.js # Get all console messages
* ./browser-console.js --errors # Only errors
* ./browser-console.js --warnings # Errors and warnings
* ./browser-console.js --watch # Watch for new messages
*/
import puppeteer from "puppeteer-core";
const args = process.argv.slice(2);
const errorsOnly = args.includes("--errors");
const warningsPlus = args.includes("--warnings");
const watch = args.includes("--watch");
const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222";
if (args.includes("--help") || args.includes("-h")) {
console.log(`
browser-console.js - Capture console messages
Usage:
./browser-console.js [options]
Options:
--errors Show only errors
--warnings Show errors and warnings
--watch Watch for new messages in real-time
--port=PORT Connect to custom debug port (default: 9222)
Message Types:
[ERR] - console.error, exceptions
[WARN] - console.warn
[LOG] - console.log
[INFO] - console.info
[DBG] - console.debug
Examples:
./browser-console.js
./browser-console.js --errors
./browser-console.js --watch
`);
process.exit(0);
}
try {
const browser = await puppeteer.connect({
browserURL: `http://localhost:${port}`,
defaultViewport: null
});
const pages = await browser.pages();
const page = pages[pages.length - 1];
if (!page) {
console.error("✗ No active tab found");
process.exit(1);
}
if (watch) {
console.log("Watching console... (Ctrl+C to stop)\n");
const shouldShow = (type) => {
if (errorsOnly) return type === "error";
if (warningsPlus) return type === "error" || type === "warning";
return true;
};
const typeLabels = {
error: "[ERR] ",
warning: "[WARN]",
log: "[LOG] ",
info: "[INFO]",
debug: "[DBG] "
};
page.on("console", msg => {
const type = msg.type();
if (shouldShow(type)) {
const label = typeLabels[type] || `[${type.toUpperCase()}]`;
const text = msg.text();
console.log(`${label} ${text}`);
}
});
page.on("pageerror", err => {
console.log(`[ERR] Uncaught: ${err.message}`);
});
// Keep alive
await new Promise(() => {});
} else {
// Get existing console messages by injecting capture
const messages = await page.evaluate(() => {
// Return any cached messages if we have them
return window.__consoleMessages || [];
});
// Also get any runtime exceptions
const client = await page.target().createCDPSession();
await client.send("Runtime.enable");
// Collect current console messages by re-evaluating with capture
const capturedMessages = await page.evaluate(() => {
const msgs = [];
const originalConsole = {
log: console.log,
error: console.error,
warn: console.warn,
info: console.info,
debug: console.debug
};
// This is for future messages - we can't capture past ones without this being set up earlier
// So we'll note that limitation
return msgs;
});
// Get recent exceptions from CDP
const { result } = await client.send("Runtime.evaluate", {
expression: `
(function() {
// Check for any unhandled errors stored in window
const errors = window.__webdebugErrors || [];
return errors;
})()
`,
returnByValue: true
});
if (messages.length === 0 && (!result.value || result.value.length === 0)) {
console.log("No console messages captured.");
console.log("\nTip: Use --watch to capture messages in real-time, or inject capture script:");
console.log(' ./browser-eval.js "window.__consoleMessages=[];[\'log\',\'error\',\'warn\',\'info\'].forEach(t=>{const o=console[t];console[t]=(...a)=>{window.__consoleMessages.push({type:t,text:a.join(\' \'),time:Date.now()});o.apply(console,a)}});"');
} else {
const allMessages = [...messages, ...(result.value || [])];
const typeLabels = {
error: "[ERR] ",
warning: "[WARN]",
warn: "[WARN]",
log: "[LOG] ",
info: "[INFO]",
debug: "[DBG] "
};
allMessages.forEach(msg => {
const type = msg.type || "log";
const shouldShow = () => {
if (errorsOnly) return type === "error";
if (warningsPlus) return type === "error" || type === "warning" || type === "warn";
return true;
};
if (shouldShow()) {
const label = typeLabels[type] || `[${type.toUpperCase()}]`;
console.log(`${label} ${msg.text || msg.message || JSON.stringify(msg)}`);
}
});
}
}
if (!watch) {
await browser.disconnect();
}
} catch (e) {
if (e.message?.includes("ECONNREFUSED")) {
console.error("✗ Cannot connect to browser. Run: ./browser-start.js");
} else {
console.error(`✗ Console capture failed: ${e.message}`);
}
process.exit(1);
}

View File

@@ -0,0 +1,186 @@
#!/usr/bin/env node
/**
* browser-dom.js - Get DOM snapshot or element HTML
*
* Usage:
* ./browser-dom.js # Full page structure summary
* ./browser-dom.js "body" # Element's outer HTML
* ./browser-dom.js ".header" --inner # Element's inner HTML
* ./browser-dom.js --tree # DOM tree visualization
*/
import puppeteer from "puppeteer-core";
const args = process.argv.slice(2);
const selector = args.find(a => !a.startsWith("--"));
const inner = args.includes("--inner");
const tree = args.includes("--tree");
const depth = parseInt(args.find(a => a.startsWith("--depth="))?.split("=")[1]) || 3;
const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222";
if (args.includes("--help") || args.includes("-h")) {
console.log(`
browser-dom.js - DOM inspection and snapshots
Usage:
./browser-dom.js [selector] [options]
Options:
--inner Get inner HTML instead of outer
--tree Show DOM tree visualization
--depth=N Tree depth (default: 3)
--port=PORT Connect to custom debug port (default: 9222)
Examples:
./browser-dom.js # Page summary
./browser-dom.js "body" # Full body HTML
./browser-dom.js ".nav" --inner # Nav inner HTML
./browser-dom.js --tree # DOM tree
./browser-dom.js --tree --depth=5 # Deeper tree
`);
process.exit(0);
}
try {
const browser = await puppeteer.connect({
browserURL: `http://localhost:${port}`,
defaultViewport: null
});
const pages = await browser.pages();
const page = pages[pages.length - 1];
if (!page) {
console.error("✗ No active tab found");
process.exit(1);
}
if (tree) {
// DOM tree visualization
const treeData = await page.evaluate((maxDepth) => {
const buildTree = (el, currentDepth, maxDepth) => {
if (currentDepth > maxDepth) return null;
const tag = el.tagName?.toLowerCase() || "#text";
if (tag === "script" || tag === "style" || tag === "#text") return null;
let label = tag;
if (el.id) label += `#${el.id}`;
if (el.className && typeof el.className === "string") {
const classes = el.className.trim().split(/\s+/).slice(0, 2).join(".");
if (classes) label += `.${classes}`;
}
const children = [];
for (const child of el.children || []) {
const childTree = buildTree(child, currentDepth + 1, maxDepth);
if (childTree) children.push(childTree);
}
return { label, children };
};
return buildTree(document.body, 0, maxDepth);
}, depth);
const printTree = (node, prefix = "", isLast = true) => {
if (!node) return;
const marker = isLast ? "└── " : "├── ";
console.log(prefix + marker + node.label);
const childPrefix = prefix + (isLast ? " " : "│ ");
node.children.forEach((child, i) => {
printTree(child, childPrefix, i === node.children.length - 1);
});
};
console.log("DOM Tree:\n");
printTree(treeData);
} else if (selector) {
// Get specific element
const html = await page.evaluate((sel, getInner) => {
const el = document.querySelector(sel);
if (!el) return null;
return getInner ? el.innerHTML : el.outerHTML;
}, selector, inner);
if (html === null) {
console.error(`✗ Element not found: ${selector}`);
process.exit(1);
}
console.log(html);
} else {
// Page summary
const summary = await page.evaluate(() => {
const countElements = (sel) => document.querySelectorAll(sel).length;
return {
title: document.title,
url: location.href,
doctype: document.doctype?.name || "none",
charset: document.characterSet,
viewport: document.querySelector('meta[name="viewport"]')?.content || "not set",
elements: {
total: document.querySelectorAll("*").length,
divs: countElements("div"),
spans: countElements("span"),
links: countElements("a"),
images: countElements("img"),
buttons: countElements("button"),
inputs: countElements("input"),
forms: countElements("form"),
scripts: countElements("script"),
styles: countElements("style, link[rel='stylesheet']")
},
headings: {
h1: countElements("h1"),
h2: countElements("h2"),
h3: countElements("h3"),
h4: countElements("h4")
},
semantics: {
header: countElements("header"),
nav: countElements("nav"),
main: countElements("main"),
article: countElements("article"),
section: countElements("section"),
aside: countElements("aside"),
footer: countElements("footer")
}
};
});
console.log("Page Summary");
console.log("============");
console.log(`Title: ${summary.title}`);
console.log(`URL: ${summary.url}`);
console.log(`Charset: ${summary.charset}`);
console.log(`Viewport: ${summary.viewport}`);
console.log("");
console.log("Element Counts:");
console.log(` Total: ${summary.elements.total}`);
console.log(` Divs: ${summary.elements.divs}, Spans: ${summary.elements.spans}`);
console.log(` Links: ${summary.elements.links}, Images: ${summary.elements.images}`);
console.log(` Buttons: ${summary.elements.buttons}, Inputs: ${summary.elements.inputs}, Forms: ${summary.elements.forms}`);
console.log(` Scripts: ${summary.elements.scripts}, Stylesheets: ${summary.elements.styles}`);
console.log("");
console.log("Headings:", `H1:${summary.headings.h1} H2:${summary.headings.h2} H3:${summary.headings.h3} H4:${summary.headings.h4}`);
console.log("");
console.log("Semantic Elements:");
Object.entries(summary.semantics).forEach(([tag, count]) => {
if (count > 0) console.log(` <${tag}>: ${count}`);
});
}
await browser.disconnect();
} catch (e) {
if (e.message?.includes("ECONNREFUSED")) {
console.error("✗ Cannot connect to browser. Run: ./browser-start.js");
} else {
console.error(`✗ DOM inspection failed: ${e.message}`);
}
process.exit(1);
}

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env node
/**
* browser-eval.js - Execute JavaScript in page context
*
* Usage:
* ./browser-eval.js 'document.title'
* ./browser-eval.js 'document.querySelectorAll("a").length'
* ./browser-eval.js 'getComputedStyle(document.body).backgroundColor'
*/
import puppeteer from "puppeteer-core";
const code = process.argv.slice(2).filter(a => !a.startsWith("--")).join(" ");
const port = process.argv.find(a => a.startsWith("--port="))?.split("=")[1] || "9222";
const json = process.argv.includes("--json");
if (!code || process.argv.includes("--help")) {
console.log(`
browser-eval.js - Execute JavaScript in page context
Usage:
./browser-eval.js '<javascript>'
Options:
--json Output result as JSON
--port=PORT Connect to custom debug port (default: 9222)
Examples:
./browser-eval.js 'document.title'
./browser-eval.js 'document.querySelectorAll("a").length'
./browser-eval.js 'getComputedStyle(document.querySelector(".header")).display'
./browser-eval.js '[...document.querySelectorAll("h1")].map(e => e.textContent)'
CSS Debugging Examples:
# Get computed style of an element
./browser-eval.js 'getComputedStyle(document.querySelector(".btn")).padding'
# Check if element is visible
./browser-eval.js 'getComputedStyle(document.querySelector("#modal")).display'
# Get bounding rect
./browser-eval.js 'document.querySelector(".hero").getBoundingClientRect()'
# Find elements with specific style
./browser-eval.js '[...document.querySelectorAll("*")].filter(e => getComputedStyle(e).position === "fixed").length'
DOM Inspection Examples:
# Get outer HTML
./browser-eval.js 'document.querySelector("nav").outerHTML'
# Count elements
./browser-eval.js 'document.querySelectorAll(".error").length'
# Get all class names on body
./browser-eval.js '[...document.body.classList]'
`);
process.exit(0);
}
try {
const browser = await puppeteer.connect({
browserURL: `http://localhost:${port}`,
defaultViewport: null
});
const pages = await browser.pages();
const page = pages[pages.length - 1];
if (!page) {
console.error("✗ No active tab found");
process.exit(1);
}
// Execute in async context to support await
const result = await page.evaluate((c) => {
const AsyncFunction = (async () => {}).constructor;
return new AsyncFunction(`return (${c})`)();
}, code);
// Format output
if (json) {
console.log(JSON.stringify(result, null, 2));
} else if (Array.isArray(result)) {
if (result.length === 0) {
console.log("(empty array)");
} else if (typeof result[0] === "object") {
// Array of objects - format nicely
result.forEach((item, i) => {
if (i > 0) console.log("");
Object.entries(item).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
});
} else {
// Simple array
result.forEach(item => console.log(item));
}
} else if (typeof result === "object" && result !== null) {
// Single object
Object.entries(result).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
} else if (result === undefined) {
console.log("(undefined)");
} else if (result === null) {
console.log("(null)");
} else {
console.log(result);
}
await browser.disconnect();
} catch (e) {
if (e.message?.includes("ECONNREFUSED")) {
console.error("✗ Cannot connect to browser. Run: ./browser-start.js");
} else if (e.message?.includes("Evaluation failed")) {
console.error(`✗ JavaScript error: ${e.message.replace("Evaluation failed: ", "")}`);
} else {
console.error(`✗ Eval failed: ${e.message}`);
}
process.exit(1);
}

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env node
/**
* browser-nav.js - Navigate to URL in Chrome/WebKit
*
* Usage:
* ./browser-nav.js https://example.com # Navigate current tab
* ./browser-nav.js https://example.com --new # Open in new tab
*/
import puppeteer from "puppeteer-core";
const url = process.argv[2];
const newTab = process.argv.includes("--new");
const port = process.argv.find(a => a.startsWith("--port="))?.split("=")[1] || "9222";
if (!url || url.startsWith("--")) {
console.log(`
browser-nav.js - Navigate to URL
Usage:
./browser-nav.js <url> [--new] [--port=PORT]
Options:
--new Open in new tab instead of current
--port=PORT Connect to custom debug port (default: 9222)
Examples:
./browser-nav.js https://example.com
./browser-nav.js https://localhost:3000 --new
./browser-nav.js file:///path/to/page.html
`);
process.exit(1);
}
try {
const browser = await puppeteer.connect({
browserURL: `http://localhost:${port}`,
defaultViewport: null
});
let page;
if (newTab) {
page = await browser.newPage();
} else {
const pages = await browser.pages();
page = pages[pages.length - 1] || await browser.newPage();
}
await page.goto(url, {
waitUntil: "domcontentloaded",
timeout: 30000
});
const title = await page.title();
console.log(`${newTab ? "Opened" : "Navigated to"}: ${url}`);
console.log(` Title: ${title}`);
await browser.disconnect();
} catch (e) {
if (e.message?.includes("ECONNREFUSED")) {
console.error("✗ Cannot connect to browser. Run: ./browser-start.js");
} else {
console.error(`✗ Navigation failed: ${e.message}`);
}
process.exit(1);
}

View File

@@ -0,0 +1,186 @@
#!/usr/bin/env node
/**
* browser-network.js - Monitor network requests
*
* Usage:
* ./browser-network.js # Show recent requests
* ./browser-network.js --watch # Watch requests in real-time
* ./browser-network.js --failures # Show only failed requests
* ./browser-network.js --xhr # Show only XHR/fetch requests
*/
import puppeteer from "puppeteer-core";
const args = process.argv.slice(2);
const watch = args.includes("--watch");
const failures = args.includes("--failures");
const xhrOnly = args.includes("--xhr");
const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222";
if (args.includes("--help") || args.includes("-h")) {
console.log(`
browser-network.js - Network request monitoring
Usage:
./browser-network.js [options]
Options:
--watch Watch requests in real-time
--failures Show only failed requests (4xx, 5xx, network errors)
--xhr Show only XHR/fetch requests (API calls)
--port=PORT Connect to custom debug port (default: 9222)
Output includes:
- Request method and URL
- Response status
- Response time
- Content type
- Size (when available)
Examples:
./browser-network.js
./browser-network.js --watch
./browser-network.js --failures
./browser-network.js --xhr --watch
`);
process.exit(0);
}
try {
const browser = await puppeteer.connect({
browserURL: `http://localhost:${port}`,
defaultViewport: null
});
const pages = await browser.pages();
const page = pages[pages.length - 1];
if (!page) {
console.error("✗ No active tab found");
process.exit(1);
}
const client = await page.target().createCDPSession();
await client.send("Network.enable");
const requests = new Map();
const formatSize = (bytes) => {
if (!bytes) return "?";
if (bytes < 1024) return `${bytes}B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
};
const shouldShow = (req, resp) => {
if (failures) {
const status = resp?.status || 0;
return status >= 400 || status === 0;
}
if (xhrOnly) {
const type = req.type || req.resourceType;
return type === "XHR" || type === "Fetch";
}
return true;
};
const formatRequest = (req, resp, timing) => {
const method = req.method || "GET";
const url = new URL(req.url);
const path = url.pathname + url.search;
const status = resp?.status || "pending";
const time = timing ? `${Math.round(timing)}ms` : "?";
const size = formatSize(resp?.encodedDataLength);
const type = resp?.mimeType?.split("/")[1]?.split(";")[0] || "?";
const statusColor = status >= 400 ? "❌" : status >= 300 ? "↩️" : "✓";
return `${statusColor} ${method.padEnd(6)} ${status} ${path.slice(0, 60).padEnd(60)} ${time.padStart(7)} ${size.padStart(8)} ${type}`;
};
if (watch) {
console.log("Watching network requests... (Ctrl+C to stop)\n");
console.log(" Method Status URL".padEnd(80) + "Time".padStart(10) + "Size".padStart(10) + " Type");
console.log("-".repeat(110));
client.on("Network.requestWillBeSent", ({ requestId, request }) => {
requests.set(requestId, { request, startTime: Date.now() });
});
client.on("Network.responseReceived", ({ requestId, response }) => {
const req = requests.get(requestId);
if (req) {
req.response = response;
}
});
client.on("Network.loadingFinished", ({ requestId, encodedDataLength }) => {
const req = requests.get(requestId);
if (req) {
const timing = Date.now() - req.startTime;
if (req.response) {
req.response.encodedDataLength = encodedDataLength;
}
if (shouldShow(req.request, req.response)) {
console.log(formatRequest(req.request, req.response, timing));
}
requests.delete(requestId);
}
});
client.on("Network.loadingFailed", ({ requestId, errorText }) => {
const req = requests.get(requestId);
if (req) {
const timing = Date.now() - req.startTime;
console.log(`${req.request.method.padEnd(6)} ERR ${req.request.url.slice(0, 60).padEnd(60)} ${String(timing + "ms").padStart(7)} - ${errorText}`);
requests.delete(requestId);
}
});
// Keep alive
await new Promise(() => {});
} else {
// Get recent requests via Performance API
const requests = await page.evaluate(() => {
return performance.getEntriesByType("resource").map(entry => ({
name: entry.name,
type: entry.initiatorType,
duration: Math.round(entry.duration),
size: entry.transferSize,
status: entry.responseStatus
}));
});
if (requests.length === 0) {
console.log("No requests captured. Try --watch to monitor in real-time.");
} else {
console.log("Recent network requests:\n");
console.log("Type".padEnd(10) + "Status".padEnd(8) + "Duration".padEnd(10) + "Size".padEnd(10) + "URL");
console.log("-".repeat(100));
requests
.filter(r => {
if (failures) return r.status >= 400;
if (xhrOnly) return r.type === "xmlhttprequest" || r.type === "fetch";
return true;
})
.slice(-30)
.forEach(r => {
const url = new URL(r.name);
const path = url.pathname.slice(0, 50);
console.log(
`${r.type.slice(0, 9).padEnd(10)}${String(r.status || "?").padEnd(8)}${String(r.duration + "ms").padEnd(10)}${formatSize(r.size).padEnd(10)}${path}`
);
});
}
await browser.disconnect();
}
} catch (e) {
if (e.message?.includes("ECONNREFUSED")) {
console.error("✗ Cannot connect to browser. Run: ./browser-start.js");
} else {
console.error(`✗ Network monitoring failed: ${e.message}`);
}
process.exit(1);
}

View File

@@ -0,0 +1,251 @@
#!/usr/bin/env node
/**
* browser-pick.js - Interactive element picker for DOM selection
*
* IMPORTANT: Use this when the user wants to select specific DOM elements.
* The user can click elements, multi-select with Cmd/Ctrl+Click, and press Enter when done.
* Returns CSS selectors and element details.
*
* Usage:
* ./browser-pick.js "Click on the broken element"
* ./browser-pick.js "Select the buttons to style"
*/
import puppeteer from "puppeteer-core";
const message = process.argv.slice(2).filter(a => !a.startsWith("--")).join(" ") || "Select an element";
const port = process.argv.find(a => a.startsWith("--port="))?.split("=")[1] || "9222";
if (process.argv.includes("--help") || process.argv.includes("-h")) {
console.log(`
browser-pick.js - Interactive element picker
Usage:
./browser-pick.js "<message>"
The picker lets users visually select elements:
- Click: Select single element
- Cmd/Ctrl+Click: Add to multi-selection
- Enter: Finish multi-selection
- Escape: Cancel
Returns:
- CSS selector(s) for selected elements
- Tag name, ID, classes
- Text content preview
- Computed styles summary
- Parent chain for context
Examples:
./browser-pick.js "Click on the element that looks wrong"
./browser-pick.js "Select all the buttons to update"
./browser-pick.js "Which element should I fix?"
`);
process.exit(0);
}
try {
const browser = await puppeteer.connect({
browserURL: `http://localhost:${port}`,
defaultViewport: null
});
const pages = await browser.pages();
const page = pages[pages.length - 1];
if (!page) {
console.error("✗ No active tab found");
process.exit(1);
}
console.log(`Picker active: ${message}`);
console.log(" Click to select, Cmd/Ctrl+Click for multi-select, Enter to finish, Escape to cancel\n");
// Inject picker helper
await page.evaluate(() => {
if (!window.__webdebugPick) {
window.__webdebugPick = async (message) => {
return new Promise((resolve) => {
const selections = [];
const selectedElements = new Set();
// Create overlay
const overlay = document.createElement("div");
overlay.id = "__webdebug-picker-overlay";
overlay.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;z-index:2147483647;pointer-events:none";
// Highlight element
const highlight = document.createElement("div");
highlight.style.cssText = "position:absolute;border:2px solid #3b82f6;background:rgba(59,130,246,0.15);transition:all 0.05s;pointer-events:none";
overlay.appendChild(highlight);
// Banner
const banner = document.createElement("div");
banner.style.cssText = "position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#1f2937;color:white;padding:12px 24px;border-radius:8px;font:14px system-ui,sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.3);pointer-events:auto;z-index:2147483647;max-width:80vw;text-align:center";
const updateBanner = () => {
banner.innerHTML = `<strong>${message}</strong><br><span style="opacity:0.8;font-size:12px">${selections.length} selected · Cmd/Ctrl+Click to add · Enter to finish · Escape to cancel</span>`;
};
updateBanner();
document.body.appendChild(banner);
document.body.appendChild(overlay);
// Build unique selector
const buildSelector = (el) => {
if (el.id) return `#${el.id}`;
let selector = el.tagName.toLowerCase();
if (el.className && typeof el.className === 'string') {
const classes = el.className.trim().split(/\s+/).filter(c => c && !c.startsWith('__'));
if (classes.length) selector += '.' + classes.slice(0, 3).join('.');
}
// Add nth-child if needed for uniqueness
const parent = el.parentElement;
if (parent) {
const siblings = [...parent.children].filter(c => c.tagName === el.tagName);
if (siblings.length > 1) {
const index = siblings.indexOf(el) + 1;
selector += `:nth-child(${index})`;
}
}
return selector;
};
// Get full selector path
const getFullSelector = (el) => {
const parts = [];
let current = el;
while (current && current !== document.body && parts.length < 4) {
parts.unshift(buildSelector(current));
current = current.parentElement;
}
return parts.join(' > ');
};
// Build element info
const buildElementInfo = (el) => {
const rect = el.getBoundingClientRect();
const styles = getComputedStyle(el);
return {
selector: getFullSelector(el),
tag: el.tagName.toLowerCase(),
id: el.id || null,
classes: el.className?.toString().trim() || null,
text: el.textContent?.trim().slice(0, 100) || null,
dimensions: `${Math.round(rect.width)}×${Math.round(rect.height)}`,
position: `${Math.round(rect.left)},${Math.round(rect.top)}`,
display: styles.display,
visibility: styles.visibility,
opacity: styles.opacity,
zIndex: styles.zIndex,
overflow: styles.overflow
};
};
const cleanup = () => {
document.removeEventListener("mousemove", onMove, true);
document.removeEventListener("click", onClick, true);
document.removeEventListener("keydown", onKey, true);
overlay.remove();
banner.remove();
selectedElements.forEach(el => {
el.style.outline = el.__originalOutline || "";
delete el.__originalOutline;
});
};
const onMove = (e) => {
const el = document.elementFromPoint(e.clientX, e.clientY);
if (!el || overlay.contains(el) || banner.contains(el)) {
highlight.style.display = "none";
return;
}
const r = el.getBoundingClientRect();
highlight.style.display = "block";
highlight.style.top = r.top + "px";
highlight.style.left = r.left + "px";
highlight.style.width = r.width + "px";
highlight.style.height = r.height + "px";
};
const onClick = (e) => {
if (banner.contains(e.target)) return;
e.preventDefault();
e.stopPropagation();
const el = document.elementFromPoint(e.clientX, e.clientY);
if (!el || overlay.contains(el) || banner.contains(el)) return;
if (e.metaKey || e.ctrlKey) {
// Multi-select mode
if (!selectedElements.has(el)) {
selectedElements.add(el);
el.__originalOutline = el.style.outline;
el.style.outline = "3px solid #10b981";
selections.push(buildElementInfo(el));
updateBanner();
}
} else {
// Single select - finish
cleanup();
if (selections.length > 0) {
resolve(selections);
} else {
resolve([buildElementInfo(el)]);
}
}
};
const onKey = (e) => {
if (e.key === "Escape") {
e.preventDefault();
cleanup();
resolve(null);
} else if (e.key === "Enter" && selections.length > 0) {
e.preventDefault();
cleanup();
resolve(selections);
}
};
document.addEventListener("mousemove", onMove, true);
document.addEventListener("click", onClick, true);
document.addEventListener("keydown", onKey, true);
});
};
}
});
// Run picker
const result = await page.evaluate((msg) => window.__webdebugPick(msg), message);
if (result === null) {
console.log("Picker cancelled");
} else if (Array.isArray(result)) {
console.log(`Selected ${result.length} element(s):\n`);
result.forEach((info, i) => {
if (i > 0) console.log("---");
console.log(`Selector: ${info.selector}`);
console.log(`Tag: ${info.tag}${info.id ? ` #${info.id}` : ""}${info.classes ? ` .${info.classes.split(" ").join(".")}` : ""}`);
console.log(`Size: ${info.dimensions} at (${info.position})`);
console.log(`Display: ${info.display}, Visibility: ${info.visibility}, Opacity: ${info.opacity}`);
if (info.zIndex !== "auto") console.log(`Z-Index: ${info.zIndex}`);
if (info.text) console.log(`Text: "${info.text.slice(0, 50)}${info.text.length > 50 ? "..." : ""}"`);
});
}
await browser.disconnect();
} catch (e) {
if (e.message?.includes("ECONNREFUSED")) {
console.error("✗ Cannot connect to browser. Run: ./browser-start.js");
} else {
console.error(`✗ Picker failed: ${e.message}`);
}
process.exit(1);
}

View File

@@ -0,0 +1,102 @@
#!/usr/bin/env node
/**
* browser-resize.js - Resize viewport for responsive testing
*
* Usage:
* ./browser-resize.js 375 667 # Custom width x height
* ./browser-resize.js --mobile # iPhone SE (375x667)
* ./browser-resize.js --tablet # iPad (768x1024)
* ./browser-resize.js --desktop # Desktop (1920x1080)
*/
import puppeteer from "puppeteer-core";
const args = process.argv.slice(2);
const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222";
// Preset sizes
const presets = {
"--mobile": { width: 375, height: 667, name: "Mobile (iPhone SE)" },
"--iphone": { width: 390, height: 844, name: "iPhone 14" },
"--iphone-pro": { width: 393, height: 852, name: "iPhone 14 Pro" },
"--android": { width: 412, height: 915, name: "Android (Pixel 7)" },
"--tablet": { width: 768, height: 1024, name: "Tablet (iPad)" },
"--ipad-pro": { width: 1024, height: 1366, name: "iPad Pro 12.9\"" },
"--laptop": { width: 1366, height: 768, name: "Laptop" },
"--desktop": { width: 1920, height: 1080, name: "Desktop (1080p)" },
"--4k": { width: 3840, height: 2160, name: "4K" }
};
if (args.includes("--help") || args.includes("-h") || args.length === 0) {
console.log(`
browser-resize.js - Resize viewport for responsive testing
Usage:
./browser-resize.js <width> <height> # Custom dimensions
./browser-resize.js --preset # Use preset size
Presets:
--mobile 375×667 (iPhone SE)
--iphone 390×844 (iPhone 14)
--iphone-pro 393×852 (iPhone 14 Pro)
--android 412×915 (Pixel 7)
--tablet 768×1024 (iPad)
--ipad-pro 1024×1366 (iPad Pro 12.9")
--laptop 1366×768 (Laptop)
--desktop 1920×1080 (Desktop 1080p)
--4k 3840×2160 (4K)
Examples:
./browser-resize.js 375 667
./browser-resize.js --mobile
./browser-resize.js --tablet && ./browser-screenshot.js
`);
process.exit(0);
}
// Determine dimensions
let width, height, name;
const presetArg = args.find(a => presets[a]);
if (presetArg) {
({ width, height, name } = presets[presetArg]);
} else {
width = parseInt(args[0]);
height = parseInt(args[1]);
if (isNaN(width) || isNaN(height)) {
console.error("✗ Invalid dimensions. Use: ./browser-resize.js <width> <height>");
process.exit(1);
}
name = `Custom (${width}×${height})`;
}
try {
const browser = await puppeteer.connect({
browserURL: `http://localhost:${port}`,
defaultViewport: null
});
const pages = await browser.pages();
const page = pages[pages.length - 1];
if (!page) {
console.error("✗ No active tab found");
process.exit(1);
}
await page.setViewport({ width, height });
console.log(`✓ Viewport resized to ${width}×${height}`);
console.log(` ${name}`);
await browser.disconnect();
} catch (e) {
if (e.message?.includes("ECONNREFUSED")) {
console.error("✗ Cannot connect to browser. Run: ./browser-start.js");
} else {
console.error(`✗ Resize failed: ${e.message}`);
}
process.exit(1);
}

View File

@@ -0,0 +1,90 @@
#!/usr/bin/env node
/**
* browser-screenshot.js - Capture screenshot of current viewport
*
* Usage:
* ./browser-screenshot.js # Viewport screenshot
* ./browser-screenshot.js --full # Full page screenshot
* ./browser-screenshot.js --selector=".main" # Element screenshot
* ./browser-screenshot.js --output=/path.png # Custom output path
*/
import puppeteer from "puppeteer-core";
import { tmpdir } from "node:os";
import { join } from "node:path";
const args = process.argv.slice(2);
const fullPage = args.includes("--full");
const selector = args.find(a => a.startsWith("--selector="))?.split("=")[1];
const outputPath = args.find(a => a.startsWith("--output="))?.split("=")[1];
const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222";
if (args.includes("--help") || args.includes("-h")) {
console.log(`
browser-screenshot.js - Capture screenshot
Usage:
./browser-screenshot.js [options]
Options:
--full Capture full page (scrollable content)
--selector=SEL Capture specific element only
--output=PATH Save to custom path (default: temp file)
--port=PORT Connect to custom debug port (default: 9222)
Examples:
./browser-screenshot.js
./browser-screenshot.js --full
./browser-screenshot.js --selector=".hero-section"
./browser-screenshot.js --output=./screenshot.png
`);
process.exit(0);
}
try {
const browser = await puppeteer.connect({
browserURL: `http://localhost:${port}`,
defaultViewport: null
});
const pages = await browser.pages();
const page = pages[pages.length - 1];
if (!page) {
console.error("✗ No active tab found");
process.exit(1);
}
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
const filename = outputPath || join(tmpdir(), `screenshot-${timestamp}.png`);
if (selector) {
const element = await page.$(selector);
if (!element) {
console.error(`✗ Element not found: ${selector}`);
process.exit(1);
}
await element.screenshot({ path: filename });
console.log(`✓ Element screenshot saved`);
} else {
await page.screenshot({
path: filename,
fullPage
});
console.log(`${fullPage ? "Full page" : "Viewport"} screenshot saved`);
}
console.log(` Path: ${filename}`);
// Output just the path for easy piping
console.log(filename);
await browser.disconnect();
} catch (e) {
if (e.message?.includes("ECONNREFUSED")) {
console.error("✗ Cannot connect to browser. Run: ./browser-start.js");
} else {
console.error(`✗ Screenshot failed: ${e.message}`);
}
process.exit(1);
}

View File

@@ -0,0 +1,190 @@
#!/usr/bin/env node
/**
* browser-start.js - Start Chrome or WebKit with remote debugging
*
* Usage:
* ./browser-start.js # Fresh Chrome profile
* ./browser-start.js --profile # Chrome with user profile (preserves logins)
* ./browser-start.js --webkit # Playwright WebKit (Safari-like)
* ./browser-start.js --headless # Headless mode
*/
import { spawn, execSync } from "node:child_process";
import { existsSync, mkdirSync } from "node:fs";
import { homedir, platform, tmpdir } from "node:os";
import { join } from "node:path";
const args = process.argv.slice(2);
const useProfile = args.includes("--profile");
const useWebKit = args.includes("--webkit");
const headless = args.includes("--headless");
const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222";
if (args.includes("--help") || args.includes("-h")) {
console.log(`
browser-start.js - Start browser with remote debugging
Usage:
./browser-start.js [options]
Options:
--profile Copy user's Chrome profile (preserves logins, cookies)
--webkit Use Playwright WebKit instead of Chrome (Safari-like)
--headless Run in headless mode
--port=PORT Use custom debug port (default: 9222)
--help Show this help message
Examples:
./browser-start.js # Fresh Chrome profile
./browser-start.js --profile # Chrome with your logins
./browser-start.js --webkit # Safari/WebKit via Playwright
./browser-start.js --headless # Headless Chrome
`);
process.exit(0);
}
const cacheDir = join(homedir(), ".cache", "website-debug");
mkdirSync(cacheDir, { recursive: true });
async function startChrome() {
// Kill existing Chrome debug instances
try {
if (platform() === "darwin") {
execSync("killall 'Google Chrome' 2>/dev/null", { stdio: "ignore" });
} else if (platform() === "linux") {
execSync("pkill -f 'chrome.*remote-debugging' 2>/dev/null", { stdio: "ignore" });
}
} catch {}
await new Promise(r => setTimeout(r, 1000));
const profileDir = join(cacheDir, "chrome-profile");
if (useProfile) {
// Find and copy user's Chrome profile
const userProfilePaths = {
darwin: join(homedir(), "Library/Application Support/Google/Chrome"),
linux: join(homedir(), ".config/google-chrome"),
win32: join(homedir(), "AppData/Local/Google/Chrome/User Data")
};
const userProfile = userProfilePaths[platform()];
if (existsSync(userProfile)) {
console.log("Syncing user profile (this may take a moment)...");
try {
execSync(`rsync -a --delete "${userProfile}/" "${profileDir}/"`, { stdio: "pipe" });
} catch (e) {
console.log("Warning: Could not sync profile, using fresh profile");
}
}
}
// Find Chrome executable
const chromePaths = {
darwin: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
linux: "/usr/bin/google-chrome",
win32: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
};
const chromePath = chromePaths[platform()];
if (!existsSync(chromePath)) {
console.error(`✗ Chrome not found at ${chromePath}`);
process.exit(1);
}
const chromeArgs = [
`--remote-debugging-port=${port}`,
`--user-data-dir=${profileDir}`,
"--no-first-run",
"--no-default-browser-check"
];
if (headless) {
chromeArgs.push("--headless=new");
}
// Start Chrome detached
const chrome = spawn(chromePath, chromeArgs, {
detached: true,
stdio: "ignore"
});
chrome.unref();
// Wait for Chrome to be ready
const puppeteer = await import("puppeteer-core");
let connected = false;
for (let i = 0; i < 30; i++) {
try {
const browser = await puppeteer.default.connect({
browserURL: `http://localhost:${port}`,
defaultViewport: null
});
await browser.disconnect();
connected = true;
break;
} catch {
await new Promise(r => setTimeout(r, 500));
}
}
if (!connected) {
console.error("✗ Failed to connect to Chrome");
process.exit(1);
}
console.log(`✓ Chrome started on :${port}${useProfile ? " with user profile" : ""}${headless ? " (headless)" : ""}`);
}
async function startWebKit() {
const stateFile = join(cacheDir, "webkit-state.json");
try {
const { webkit } = await import("playwright");
console.log("Starting WebKit browser...");
const browser = await webkit.launchPersistentContext(join(cacheDir, "webkit-profile"), {
headless,
viewport: null,
// Save browser endpoint for other scripts
});
// Store connection info
const endpoint = browser.browser()?.wsEndpoint?.() || "direct-context";
const fs = await import("node:fs/promises");
await fs.writeFile(stateFile, JSON.stringify({
type: "webkit",
endpoint,
pid: process.pid
}));
console.log(`✓ WebKit started${headless ? " (headless)" : ""}`);
console.log(" Press Ctrl+C to stop");
// Keep process alive for WebKit
process.on("SIGINT", async () => {
console.log("\nClosing WebKit...");
await browser.close();
process.exit(0);
});
// Keep alive
await new Promise(() => {});
} catch (e) {
if (e.message?.includes("Cannot find module")) {
console.error("✗ Playwright not installed. Run: npm install -g playwright && npx playwright install webkit");
} else {
console.error(`✗ Failed to start WebKit: ${e.message}`);
}
process.exit(1);
}
}
// Main
if (useWebKit) {
startWebKit();
} else {
startChrome();
}

View File

@@ -0,0 +1,84 @@
#!/bin/bash
#
# setup.sh - Install dependencies for website-debug skill
#
set -e
echo "Setting up website-debug skill..."
echo ""
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPTS_DIR="$SKILL_DIR/scripts"
# Check Node.js
if ! command -v node &> /dev/null; then
echo "❌ Node.js not found. Please install Node.js first:"
echo " https://nodejs.org/ or: brew install node"
exit 1
fi
echo "✓ Node.js $(node --version)"
# Check npm
if ! command -v npm &> /dev/null; then
echo "❌ npm not found. Please install npm."
exit 1
fi
echo "✓ npm $(npm --version)"
# Install puppeteer-core globally
echo ""
echo "Installing puppeteer-core..."
npm install -g puppeteer-core 2>/dev/null || npm install puppeteer-core --save 2>/dev/null
echo "✓ puppeteer-core installed"
# Optional: Install Playwright for WebKit support
echo ""
echo "Installing Playwright for Safari/WebKit support (optional)..."
if npm install -g playwright 2>/dev/null; then
npx playwright install webkit 2>/dev/null || true
echo "✓ Playwright + WebKit installed"
else
echo "⚠ Playwright installation skipped (Chrome debugging will still work)"
fi
# Make scripts executable
echo ""
echo "Making scripts executable..."
chmod +x "$SCRIPTS_DIR"/*.js 2>/dev/null || true
echo "✓ Scripts are executable"
# Create cache directory
mkdir -p ~/.cache/website-debug
echo "✓ Cache directory ready"
# Check Chrome
echo ""
if [[ "$OSTYPE" == "darwin"* ]]; then
CHROME_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
if [ -f "$CHROME_PATH" ]; then
echo "✓ Chrome found at $CHROME_PATH"
else
echo "⚠ Chrome not found. Install from: https://www.google.com/chrome/"
fi
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
if command -v google-chrome &> /dev/null; then
echo "✓ Chrome found"
else
echo "⚠ Chrome not found. Install with: sudo apt install google-chrome-stable"
fi
fi
echo ""
echo "=========================================="
echo "✓ Setup complete!"
echo ""
echo "Quick start:"
echo " cd $SCRIPTS_DIR"
echo " ./browser-start.js # Start Chrome"
echo " ./browser-nav.js http://localhost:3000"
echo " ./browser-screenshot.js # Take screenshot"
echo ""
echo "Or use with Claude Code:"
echo " Ask Claude to 'debug this page' or 'check my site'"
echo "=========================================="