Before starting, each port (8080, 5173) is checked via ss; an existing
listener is reported with PID and command, and an interactive run asks
permission to kill it (default yes). Non-interactive runs abort
instead: never kill processes silently. Born from a real incident: an
orphaned smoke-test server held 8080 and dev.sh only said
'address already in use'.
Starts the Go API and the Vite dev server as background jobs; 'wait -n'
returns when the first one exits and the EXIT trap kills the rest, so
one Ctrl-C (or either server crashing) stops everything. Installs
web/node_modules on first run. Verified: both ports respond, SIGINT
leaves no orphan processes.