feat(web): Host the web console in the daemon - #119
Merged
Merged
Conversation
Co-Authored-By: Claude <noreply@anthropic.com>
The web server is about to outlive the command that starts it, so API routes now need the session cookie on every method and pages need the token or the cookie. Tests send the cookie through a shared helper. Co-Authored-By: Claude <noreply@anthropic.com>
A throwing or rejecting route used to escape the dispatcher. Now it gets a JSON 500, or an ended response when headers were already sent, and the server keeps serving. Co-Authored-By: Claude <noreply@anthropic.com>
The setup API routes discarded their promises with `void`, so a rejection outside the route's own try/catch became an unhandled rejection and left the request hanging. Returning the promise lets the dispatcher answer 500 instead. Co-Authored-By: Claude <noreply@anthropic.com>
Extract listenWebServer, which binds, creates the session security and closes, with no signal handlers and no browser. The daemon's web child needs this. startWebServer delegates to it and passes an optional fallbackToEphemeral through for the in-process CLI fallback. Co-Authored-By: Claude <noreply@anthropic.com>
A shared web server cannot use its own cwd as the review root, so /api/review now takes an absolute repo parameter and rejects a missing or relative one. The page forwards repo on every request, shows a hint without one, and keys drafts by repo so two repos never mix comments. The in-process review command opens /review?repo=<cwd>. Co-Authored-By: Claude <noreply@anthropic.com>
`usage --web --interval` will open a shared server by URL, so the /usage page reads interval from its query instead of from process-local page options. The page keeps its existing normalization. Co-Authored-By: Claude <noreply@anthropic.com>
`setup --refresh` will open a shared server by URL, so the page itself sends one catalog refresh after its initial load when the query has refresh=1, instead of the command refreshing before it serves. Co-Authored-By: Claude <noreply@anthropic.com>
The daemon needs to know when its web child runs code older than the CLI calling it. The build id is the newest .js mtime under the dist root, so any rebuild changes it. Co-Authored-By: Claude <noreply@anthropic.com>
The daemon will host the console in a supervised child instead of in its own event loop. The child serves the full console route table, prints one JSON handshake line with its port, token and build, and exits on stdin EOF or SIGTERM without waiting for in-flight requests. Co-Authored-By: Claude <noreply@anthropic.com>
WebSupervisor keeps at most one web child. It shares a start in flight, reads the JSON handshake line with a timeout, maps failures to WEB_LISTEN_FAILED, WEB_START_FAILED and WEB_BAD_ENTRY, and restarts the child when the caller's build or entry differs. The child's stderr goes to logs/web-child.log and the daemon never imports web code. Co-Authored-By: Claude <noreply@anthropic.com>
The daemon answers web.ensure by starting or reusing its supervised web child and returns the base URL, port and token. Supervisor failures become IPC errors with the same code and details, and shutdown sends SIGTERM to the child before the session drain. Co-Authored-By: Claude <noreply@anthropic.com>
launchWebPage ensures the daemon, asks it for the web child with the local build and entry, then opens or prints the page URL and returns. A busy explicit port fails with exit 1. Any other daemon error, or a daemon that cannot start, falls back to serving the full console from this process. Co-Authored-By: Claude <noreply@anthropic.com>
ui opens / and review opens /review?repo=<cwd> through launchWebPage, so both return to the shell instead of holding a server. The commander "3100" defaults are gone so an omitted --port reaches the daemon as no port. The route table tests now serve createUiRoutes and createReviewRoutes directly; the opener-failure case lives in the launcher tests. Co-Authored-By: Claude <noreply@anthropic.com>
setup's web branch opens /setup through launchWebPage and returns, and it no longer needs a TTY because it prints or opens a URL. Only --tui still requires a terminal. --refresh becomes refresh=1 on the page URL, so the command-side route override is gone, and an omitted --port no longer defaults to 3100. Co-Authored-By: Claude <noreply@anthropic.com>
usage --web resolves its filters against the caller's cwd and passes the non-empty ones, plus by and interval, as the page query to launchWebPage. The shared server no longer needs a process-local page configuration, and the command returns to the shell. Co-Authored-By: Claude <noreply@anthropic.com>
Describe web.ensure, its params, result and error codes, the web child handshake and restart on a build change, the token and cookie flow, and the in-process fallback. Verified with npm run build, the pty gate and an isolated smoke run of review, setup, usage --web and ui. Co-Authored-By: Claude <noreply@anthropic.com>
The verifier showed seven spec values could change without a failing test. Tests now run against the real 5000 ms start and 3000 ms stop timeouts, the default child entry, the child's own build id, and the daemon.log lines through the daemon. They also cover the fallback for WEB_START_FAILED and WEB_BAD_ENTRY, the transitive import boundary, and an invalid --port on setup and usage. The daemon and child gain small seams (spawnWebChild, distRoot) for this. Co-Authored-By: Claude <noreply@anthropic.com>
Validation round 2 left four mutants alive: the build and entry launchWebPage sends by default, the child's default build root, and an import-boundary walker that skipped side-effect imports. Co-Authored-By: Claude <noreply@anthropic.com>
The verifier passed round 3: 50 of 50 acceptance criteria have discriminating evidence and all 19 injected mutants break a test. Co-Authored-By: Claude <noreply@anthropic.com>
4ndreello
marked this pull request as ready for review
September 25, 2026 01:00
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



review,setup,usage --webanduinow ask the daemon for the web console through a newweb.ensureIPC method instead of each starting its own HTTP server.Before this, every command bound its own server on 3100 (or an ephemeral port), so running
reviewwhilesetupwas open meant two servers, two tokens and a port clash. The daemon already owns sessions and outlives the terminal, so it is the natural owner of a single console.How it works:
WebSupervisor(src/daemon/web-supervisor.ts) runs at most one child,node dist/web/child.js --web-child, which prints a JSON handshake (port,token,build). Concurrent callers share one start. A different build or entry replaces the child (SIGTERM, then SIGKILL after 3 s). The daemon stops it first on shutdown.src/weborsrc/cli, and a test walks the import graph to keep it that way.launchWebPage(src/cli/web-launch.ts) sends the local build id and child entry. If the daemon is down or answers any error other thanWEB_LISTEN_FAILED, the CLI serves in-process as before and says so. A busy explicit--portexits 1.Behavior changes worth a look:
/api/reviewnow requires an absolute?repo=; review drafts are keyed by repo. Theuinav's Review link has no repo and shows a hint instead.setupno longer requires a TTY; the check applies only to--tui./usagereadsintervalfrom the URL and/setuphonoursrefresh=1, since one shared server can no longer bake per-command options into the page.The spec, tasks and verifier report live in
.specs/features/web-daemon/. The verifier passed on round 3 with 50/50 acceptance criteria covered and 19/19 injected mutants killed.