Browse the ~270-entry DDEV add-on registry with search, install via
`ddev addon get`, remove via `ddev addon remove` — both routed through
the streaming commandRunner. `addon get`/`remove` support `--project
<name>` directly (unlike snapshot restore), so no cwd trick needed
here. Verified working with the project stopped, per the plan's
requirement.
Found and fixed a real bug via live UI testing: `ddev addon list
--installed --json-output` omits the `raw` key entirely when nothing
is installed (unlike `ddev list`/`ddev snapshot --list`, which include
`raw: null`). The shared runDdevRead helper treated that as an error,
so after removing the last add-on the query errored on refetch and
React Query kept showing the stale cached row instead of clearing it.
Split the helper into runDdevRead (strict, for describeProject where
missing data is a real error) and runDdevReadList (treats missing raw
as empty, for every list-style command) and fixed all four list call
sites to use it.
Verified end-to-end via CDP-driven clicks against the real scratch
project: search filtering, install (streamed output, confirmed via
`ddev addon list --installed` on the CLI), and remove — including
re-confirming after the fix that the installed-add-ons table actually
clears instead of showing stale data.
Snapshot create/list/restore/delete and DB import/export via native
file dialogs, all routed through the streaming commandRunner from
step 3. `ddev snapshot restore` has no project-name flag (unlike every
other ddev command used so far) — it resolves the project from cwd,
so commandRunner/execDdev now accept an optional cwd and every
database command runs with cwd = project approot instead of passing
the name positionally.
Fixed a real bug found via live testing: the snapshot-naming UI used
window.prompt(), which Electron's renderer does not support (it
returns null with no dialog, silently). window.confirm() does work
(verified: real native dialog, respects accept/dismiss) so the delete
confirmation was left as-is. Snapshot naming now uses an inline text
input instead.
Verified end-to-end against the real scratch DDEV project via CDP-
driven clicks: named snapshot creation, deletion, and restore
(streamed output ending in "Database snapshot restoretest was
restored in 7s") all confirmed working through the actual UI.
import-db/export-db verified via direct CLI round-trip using the
exact --file= flag syntax the app invokes (skipped UI-driving these
since they open native OS file dialogs that CDP cannot dismiss).
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).
Wire up the full stack for step 2 of the build plan: a ddev.ts CLI
wrapper in the main process (execFile + JSON envelope parsing, with a
PATH fallback since GUI apps on macOS don't inherit Homebrew's PATH),
IPC handlers exposed through a typed preload window.api surface,
TanStack Query hooks for polling/mutations, and a two-pane UI (project
list sidebar + detail panel with URLs, services, and DB credentials).
Verified end-to-end against a real scratch DDEV project: typecheck,
lint, and tests pass, and the running app correctly displays live
project data and reflects state changes (start/stop) via polling.
Add Tailwind (via Vite plugin), Vitest+RTL test setup, and the
Zustand/TanStack Query/Lucide libraries the architecture plan calls
for. Strip the electron-vite demo boilerplate (logo, IPC ping demo,
Versions component) in favor of a minimal placeholder screen.
Verified: typecheck, lint, and test suite all pass; pnpm dev boots
and serves the correct renderer HTML.