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

@@ -8,12 +8,28 @@ import type { Request, Response, NextFunction } from 'express';
import { validatePath, PathNotAllowedError } from '@automaker/platform';
/**
* Creates a middleware that validates specified path parameters in req.body
* Helper to get parameter value from request (checks body first, then query)
*/
function getParamValue(req: Request, paramName: string): unknown {
// Check body first (for POST/PUT/PATCH requests)
if (req.body && req.body[paramName] !== undefined) {
return req.body[paramName];
}
// Fall back to query params (for GET requests)
if (req.query && req.query[paramName] !== undefined) {
return req.query[paramName];
}
return undefined;
}
/**
* Creates a middleware that validates specified path parameters in req.body or req.query
* @param paramNames - Names of parameters to validate (e.g., 'projectPath', 'worktreePath')
* @example
* router.post('/create', validatePathParams('projectPath'), handler);
* router.post('/delete', validatePathParams('projectPath', 'worktreePath'), handler);
* router.post('/send', validatePathParams('workingDirectory?', 'imagePaths[]'), handler);
* router.get('/logs', validatePathParams('worktreePath'), handler); // Works with query params too
*
* Special syntax:
* - 'paramName?' - Optional parameter (only validated if present)
@@ -26,8 +42,8 @@ export function validatePathParams(...paramNames: string[]) {
// Handle optional parameters (paramName?)
if (paramName.endsWith('?')) {
const actualName = paramName.slice(0, -1);
const value = req.body[actualName];
if (value) {
const value = getParamValue(req, actualName);
if (value && typeof value === 'string') {
validatePath(value);
}
continue;
@@ -36,18 +52,20 @@ export function validatePathParams(...paramNames: string[]) {
// Handle array parameters (paramName[])
if (paramName.endsWith('[]')) {
const actualName = paramName.slice(0, -2);
const values = req.body[actualName];
const values = getParamValue(req, actualName);
if (Array.isArray(values) && values.length > 0) {
for (const value of values) {
validatePath(value);
if (typeof value === 'string') {
validatePath(value);
}
}
}
continue;
}
// Handle regular parameters
const value = req.body[paramName];
if (value) {
const value = getParamValue(req, paramName);
if (value && typeof value === 'string') {
validatePath(value);
}
}