Add streaming terminal panel, status bar, and toast notifications

Step 3 of the build plan. Long-running ddev commands (start/stop/
restart) now spawn via commandRunner.ts and stream stdout/stderr to
the renderer over IPC (terminal:data/terminal:exit), instead of the
previous fire-and-forget JSON exec. A single useTerminalEvents hook
owns turning those events into terminal panel lines, status bar
progress, and success/error toasts, decoupled from whichever mutation
triggered the command so later features (snapshots, addons) can reuse
the same pipeline. Cancel is wired end-to-end: the status bar's Cancel
button kills the underlying child process via a tracked operation id.

Verified against the real scratch DDEV project via a CDP driver
script (Electron launched with --remoteDebuggingPort, driven by
clicking real DOM buttons): confirmed live streaming output, the
status bar's spinner/cancel affordance, and that cancelling mid-
restart genuinely interrupts the process rather than just hiding the
UI (only the first output line appears, vs. full output on a normal
run).
This commit is contained in:
R3ap3R
2026-08-02 23:40:29 -05:00
parent 602659c45f
commit 199c4d254e
17 changed files with 478 additions and 55 deletions
+11 -4
View File
@@ -1,10 +1,17 @@
import { ipcMain } from 'electron'
import { describeProject, listProjects, restartProject, startProject, stopProject } from '../ddev'
import { describeProject, listProjects } from '../ddev'
import { runStreamed } from '../commandRunner'
export function registerProjectsIpc(): void {
ipcMain.handle('projects:list', () => listProjects())
ipcMain.handle('projects:describe', (_event, name: string) => describeProject(name))
ipcMain.handle('projects:start', (_event, name: string) => startProject(name))
ipcMain.handle('projects:stop', (_event, name: string) => stopProject(name))
ipcMain.handle('projects:restart', (_event, name: string) => restartProject(name))
ipcMain.handle('projects:start', (event, operationId: string, name: string) =>
runStreamed(operationId, ['start', name], event.sender)
)
ipcMain.handle('projects:stop', (event, operationId: string, name: string) =>
runStreamed(operationId, ['stop', name], event.sender)
)
ipcMain.handle('projects:restart', (event, operationId: string, name: string) =>
runStreamed(operationId, ['restart', name], event.sender)
)
}