Skip to content

Commit fb70626

Browse files
GeneAIclaude
authored andcommitted
feat(vscode): Add interactive action buttons to report webviews
Reports now open in HTML webview panels with clickable action buttons: - Fix All Issues button (on all reports except fix-all itself) - Run Tests button (on morning/ship reports) - Security Scan button (on morning/ship reports) - Learn Patterns button (on ship/security reports) - Sync to Claude button (on learn/morning reports) Clicking a button closes the current report and runs the selected action. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent f4a902a commit fb70626

File tree

1 file changed

+169
-7
lines changed

1 file changed

+169
-7
lines changed

vscode-extension/src/panels/EmpathyDashboardPanel.ts

Lines changed: 169 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -811,14 +811,10 @@ export class EmpathyDashboardProvider implements vscode.WebviewViewProvider {
811811
cp.execFile(pythonPath, args, { cwd: workspaceFolder, maxBuffer: 1024 * 1024 * 5 }, async (error, stdout, stderr) => {
812812
const output = stdout || stderr || (error ? error.message : 'No output');
813813

814-
// Open in webview report panel
814+
// Open in webview report panel with action buttons
815815
try {
816-
await vscode.commands.executeCommand('empathy.openReportInEditor', {
817-
workflowName: command.split(/\s+/)[0], // Use first word as workflow name
818-
output,
819-
input: title
820-
});
821-
vscode.window.showInformationMessage(`${title} report opened`);
816+
const cmdBase = command.split(/\s+/)[0];
817+
await this._openReportWebview(cmdBase, title, output);
822818
} catch (openErr) {
823819
vscode.window.showErrorMessage(`Failed to open report: ${openErr}`);
824820
}
@@ -1594,6 +1590,172 @@ export class EmpathyDashboardProvider implements vscode.WebviewViewProvider {
15941590
return content;
15951591
}
15961592

1593+
/**
1594+
* Open a report in a webview panel with action buttons.
1595+
*/
1596+
private async _openReportWebview(cmdBase: string, title: string, output: string): Promise<void> {
1597+
const panel = vscode.window.createWebviewPanel(
1598+
'empathyReport',
1599+
`Empathy: ${title}`,
1600+
vscode.ViewColumn.One,
1601+
{ enableScripts: true }
1602+
);
1603+
1604+
// Define action buttons based on report type
1605+
const actionButtons: Array<{ label: string; icon: string; command: string }> = [];
1606+
1607+
// Common actions for most reports
1608+
if (cmdBase !== 'fix-all') {
1609+
actionButtons.push({ label: 'Fix All Issues', icon: '🔧', command: 'fix-all' });
1610+
}
1611+
1612+
// Report-specific actions
1613+
if (cmdBase === 'morning' || cmdBase === 'ship') {
1614+
actionButtons.push({ label: 'Run Tests', icon: '🧪', command: 'run-tests' });
1615+
actionButtons.push({ label: 'Security Scan', icon: '🔒', command: 'securityScan' });
1616+
}
1617+
if (cmdBase === 'ship' || cmdBase === 'security') {
1618+
actionButtons.push({ label: 'Learn Patterns', icon: '📚', command: 'learn' });
1619+
}
1620+
if (cmdBase === 'learn' || cmdBase === 'morning') {
1621+
actionButtons.push({ label: 'Sync to Claude', icon: '🔄', command: 'sync-claude' });
1622+
}
1623+
1624+
// Generate action buttons HTML
1625+
const buttonsHtml = actionButtons.map(btn =>
1626+
`<button class="action-btn" data-cmd="${btn.command}">${btn.icon} ${btn.label}</button>`
1627+
).join('\n ');
1628+
1629+
// Convert output to HTML (preserve formatting)
1630+
const outputHtml = this._formatOutputAsHtml(output);
1631+
1632+
panel.webview.html = `<!DOCTYPE html>
1633+
<html>
1634+
<head>
1635+
<style>
1636+
body {
1637+
font-family: var(--vscode-font-family, -apple-system, BlinkMacSystemFont, sans-serif);
1638+
padding: 20px;
1639+
color: var(--vscode-foreground, #ccc);
1640+
background: var(--vscode-editor-background, #1e1e1e);
1641+
line-height: 1.6;
1642+
}
1643+
h1 {
1644+
border-bottom: 1px solid var(--vscode-panel-border, #444);
1645+
padding-bottom: 10px;
1646+
margin-bottom: 20px;
1647+
}
1648+
.timestamp {
1649+
color: var(--vscode-descriptionForeground, #888);
1650+
font-size: 12px;
1651+
margin-bottom: 20px;
1652+
}
1653+
.actions {
1654+
display: flex;
1655+
gap: 10px;
1656+
flex-wrap: wrap;
1657+
margin-bottom: 20px;
1658+
padding: 15px;
1659+
background: var(--vscode-input-background, #2d2d2d);
1660+
border-radius: 6px;
1661+
}
1662+
.action-btn {
1663+
padding: 8px 16px;
1664+
background: var(--vscode-button-background, #0e639c);
1665+
color: var(--vscode-button-foreground, #fff);
1666+
border: none;
1667+
border-radius: 4px;
1668+
cursor: pointer;
1669+
font-size: 13px;
1670+
display: flex;
1671+
align-items: center;
1672+
gap: 6px;
1673+
}
1674+
.action-btn:hover {
1675+
background: var(--vscode-button-hoverBackground, #1177bb);
1676+
}
1677+
.output {
1678+
background: var(--vscode-input-background, #2d2d2d);
1679+
padding: 15px;
1680+
border-radius: 6px;
1681+
white-space: pre-wrap;
1682+
font-family: var(--vscode-editor-font-family, monospace);
1683+
font-size: 13px;
1684+
overflow-x: auto;
1685+
}
1686+
.section-title {
1687+
font-size: 14px;
1688+
font-weight: 600;
1689+
margin-bottom: 10px;
1690+
color: var(--vscode-foreground, #ccc);
1691+
}
1692+
</style>
1693+
</head>
1694+
<body>
1695+
<h1>📊 ${title}</h1>
1696+
<div class="timestamp">Generated: ${new Date().toLocaleString()}</div>
1697+
1698+
<div class="section-title">⚡ Quick Actions</div>
1699+
<div class="actions">
1700+
${buttonsHtml}
1701+
</div>
1702+
1703+
<div class="section-title">📋 Report Output</div>
1704+
<div class="output">${outputHtml}</div>
1705+
1706+
<script>
1707+
const vscode = acquireVsCodeApi();
1708+
document.querySelectorAll('.action-btn').forEach(btn => {
1709+
btn.addEventListener('click', () => {
1710+
vscode.postMessage({ type: 'runCommand', command: btn.dataset.cmd });
1711+
});
1712+
});
1713+
</script>
1714+
</body>
1715+
</html>`;
1716+
1717+
// Handle messages from webview
1718+
panel.webview.onDidReceiveMessage(async (message) => {
1719+
if (message.type === 'runCommand') {
1720+
// Close the report panel first
1721+
panel.dispose();
1722+
// Run the command
1723+
const webviewCommands: Record<string, { cmd: string; title: string }> = {
1724+
'fix-all': { cmd: 'fix-all', title: 'Auto Fix' },
1725+
'run-tests': { cmd: 'ship --tests-only', title: 'Test Results' },
1726+
'securityScan': { cmd: 'ship --security-only', title: 'Security Scan' },
1727+
'learn': { cmd: 'learn --analyze 20', title: 'Learn Patterns' },
1728+
'sync-claude': { cmd: 'sync-claude', title: 'Sync to Claude Code' },
1729+
};
1730+
const cmdConfig = webviewCommands[message.command];
1731+
if (cmdConfig) {
1732+
await this._runQuickAction(cmdConfig.cmd, cmdConfig.title);
1733+
}
1734+
}
1735+
});
1736+
}
1737+
1738+
/**
1739+
* Format raw output as HTML with proper escaping and formatting.
1740+
*/
1741+
private _formatOutputAsHtml(output: string): string {
1742+
// Escape HTML entities
1743+
let html = output
1744+
.replace(/&/g, '&amp;')
1745+
.replace(/</g, '&lt;')
1746+
.replace(/>/g, '&gt;');
1747+
1748+
// Highlight checkmarks and X marks
1749+
html = html.replace(//g, '<span style="color: #4ec9b0;">✓</span>');
1750+
html = html.replace(/||/g, '<span style="color: #f14c4c;">❌</span>');
1751+
html = html.replace(//g, '<span style="color: #cca700;">⚠</span>');
1752+
1753+
// Highlight section headers (lines with === or ---)
1754+
html = html.replace(/^(={3,}|{3,})$/gm, '<span style="color: #569cd6;">$1</span>');
1755+
1756+
return html;
1757+
}
1758+
15971759
/**
15981760
* Save security audit findings to file and trigger diagnostics refresh
15991761
*/

0 commit comments

Comments
 (0)