feat: add dev server log panel with real-time streaming

Add the ability to view dev server logs in a dedicated panel with:
- Real-time log streaming via WebSocket events
- ANSI color support using xterm.js
- Scrollback buffer (50KB) for log history on reconnect
- Output throttling to prevent UI flooding
- "View Logs" option in worktree dropdown menu

Server changes:
- Add scrollback buffer and event emission to DevServerService
- Add GET /api/worktree/dev-server-logs endpoint
- Add dev-server:started, dev-server:output, dev-server:stopped events

UI changes:
- Add reusable XtermLogViewer component
- Add DevServerLogsPanel dialog component
- Add useDevServerLogs hook for WebSocket subscription

Closes #462

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Shirone
2026-01-13 20:42:57 +01:00
parent 7ef525effa
commit 073f6d5793
17 changed files with 1277 additions and 30 deletions

View File

@@ -510,7 +510,53 @@ type EventType =
| 'ideation:analysis'
| 'worktree:init-started'
| 'worktree:init-output'
| 'worktree:init-completed';
| 'worktree:init-completed'
| 'dev-server:started'
| 'dev-server:output'
| 'dev-server:stopped';
/**
* Dev server log event payloads for WebSocket streaming
*/
export interface DevServerStartedEvent {
worktreePath: string;
port: number;
url: string;
timestamp: string;
}
export interface DevServerOutputEvent {
worktreePath: string;
content: string;
timestamp: string;
}
export interface DevServerStoppedEvent {
worktreePath: string;
port: number;
exitCode: number | null;
error?: string;
timestamp: string;
}
export type DevServerLogEvent =
| { type: 'dev-server:started'; payload: DevServerStartedEvent }
| { type: 'dev-server:output'; payload: DevServerOutputEvent }
| { type: 'dev-server:stopped'; payload: DevServerStoppedEvent };
/**
* Response type for fetching dev server logs
*/
export interface DevServerLogsResponse {
success: boolean;
result?: {
worktreePath: string;
port: number;
logs: string;
startedAt: string;
};
error?: string;
}
type EventCallback = (payload: unknown) => void;
@@ -1710,6 +1756,24 @@ export class HttpApiClient implements ElectronAPI {
this.post('/api/worktree/start-dev', { projectPath, worktreePath }),
stopDevServer: (worktreePath: string) => this.post('/api/worktree/stop-dev', { worktreePath }),
listDevServers: () => this.post('/api/worktree/list-dev-servers', {}),
getDevServerLogs: (worktreePath: string): Promise<DevServerLogsResponse> =>
this.get(`/api/worktree/dev-server-logs?worktreePath=${encodeURIComponent(worktreePath)}`),
onDevServerLogEvent: (callback: (event: DevServerLogEvent) => void) => {
const unsub1 = this.subscribeToEvent('dev-server:started', (payload) =>
callback({ type: 'dev-server:started', payload: payload as DevServerStartedEvent })
);
const unsub2 = this.subscribeToEvent('dev-server:output', (payload) =>
callback({ type: 'dev-server:output', payload: payload as DevServerOutputEvent })
);
const unsub3 = this.subscribeToEvent('dev-server:stopped', (payload) =>
callback({ type: 'dev-server:stopped', payload: payload as DevServerStoppedEvent })
);
return () => {
unsub1();
unsub2();
unsub3();
};
},
getPRInfo: (worktreePath: string, branchName: string) =>
this.post('/api/worktree/pr-info', { worktreePath, branchName }),
// Init script methods