@@ -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, '&' )
1745+ . replace ( / < / g, '<' )
1746+ . replace ( / > / g, '>' ) ;
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