Repository navigation
Move a site's script runner and build watch out of SiteRow into hooks - #582
Conversation
The fourth domain of #554, in its first half: the dev server follows. useSiteScripts holds the npm runs a site's view starts: the install and the scripts, whether one is running, whether the last build failed or was cut short, and the run a stop should reach. Everything that runs one goes through it, the terminal and the build watch included. useBuildWatch holds the build watch: its state, its run, who is waiting for it to be ready, and start, stop, pause, resume and hand-off. It takes the runner and the terminal's lock as arguments, because a site with no build is built once, under the lock, before it can be watched. What the chains read from outside a render is handed out under the names SiteRow already used, so the update, the apply and the ticket switch are untouched. One dependency list gains a ref. No journey touched the build watch, so two do now, and the terminal's journey pins the build and install flags, which nothing on screen had depended on alone. All were written against the inline code and pass on both. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
From the review of the previous commit. The build watch's journey said pausing and resuming the watch had journeys of their own. They do not: the update, the apply and the pull request journeys run with no watch running, where both do nothing. The header says so. The hint under the terminal and a tab of the Logs panel were each located in more than one file, so both are in ui.cjs. The wait on the hint before the first click is what keeps the click off a site not yet known to be built, and now says so. The button the terminal's journey watches for the build and install flags is shown to stay enabled while the lock alone is held; with the button rewired to follow the lock, the journey goes red on that line. Three comments in the hooks corrected with it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
From the review of the previous commit. Comments only. Only an applied patch can both pause the watch and hand it a change; an update and a restored pull request pause it, and a ticket switch hands it files. The watch is stopped by its button, and also by Ctrl+C while its first build holds the terminal. Its run is killed on a pause or its own stop, not when the dev server stops. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository: WordPress/contributor-toolkit/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
## Why `SiteRow` holds the state of eight separate things in one function, and #554 splits it into hooks by domain so the redesign in #542 can rebuild the screen around pieces it can hold. The issue's item "dev server and watch" was split in two: the build watch and the script runner left in #582. This is the other half: the dev server. ## What changes `src/renderer/hooks/use-dev-server.jsx` is new. It holds whether the server is starting or up, its address, the guards around a start and a stop, and the one button that does both. - **Starting the server is what brings the rest of a running site to life, so the hook takes what it needs from the other domains as arguments**: the build watch it starts behind, the Server pane and the debug.log tail, the site's mail, the terminal's lock and the script runner's current run. They are the calls the inline code made, in the same order. - **It returns what the screen shows**: the address, whether it is starting or up, the button's label and its handler, and how long a start has been going. - **It is called where the button's handler was**, below the build watch. Nothing in `SiteRow` read the server's state above that point, so nothing else moved. - **The markup stays in `SiteRow`.** **No journey pressed the button for its own sake**: the mail and the logs journeys use it to get a running site. A new `dev-server.spec.js` has two journeys. Both were written against the inline code first and pass on both. ## How to test this Platforms: any. Use the Buildkite build for the current head of this branch. The journeys carry this: `npx playwright test --project=journeys tests/e2e/journeys/dev-server.spec.js`, from the repository root. - The first, on a built Core site, asserts that the button asks for one server for this site and says it is starting; that pressed again while it starts it starts nothing more; that once the server has an address the site is opened in the browser once, the address is shown and the button offers to stop it; that stopping asks for the server to be stopped, takes the address away and leaves the build watch running; that the exit which follows a stop that was asked for is not reported as a crash; that an exit nobody asked for is; and that a server which could not start says why and gives the button back. - The second, on a Gutenberg checkout with no build, asserts that the button runs the build first and starts no server; that a failed build cancels the start and says so; that after a good build the server still waits until the watcher prints its ready line; and that on a build which is already there the server starts at once and does not start the watcher that would remove it. What they cannot reach is a real server: **Starting state:** a Core site that has finished setup, with no dev server running. 1. Click "Start dev server". The button reads "Starting dev server..." and the line under it counts the seconds. The build watch's button reads "Stop build watch". 2. The site opens in your browser by itself, once. The button reads "Stop dev server", and the site's address, "wp-admin" and "DB inspect (Adminer)" are under it. 3. Click "Stop dev server". The address goes and the button reads "Start dev server". The build watch's button still reads "Stop build watch". 4. Look at the "Server" tab under "Logs". It does not say the server stopped unexpectedly. **What must not have happened:** - The browser did not open the site twice. - Stopping the server did not stop the build watch. ## Risks and limitations - **The hook takes eighteen arguments.** That is the dev server's real reach: it touches the watch, the logs, the mail, the terminal and the runner. It is written down in one place now, where before it was the whole of `SiteRow`'s scope. - **Moved as it was: `waitingForWatch` is never true.** The code only ever sets it to false, so the part of "is the server starting" that reads it does nothing. It predates this PR. Removing it is a change of its own. - **Three older bugs the reviews found in this code, moved as they were and not fixed here.** After a start that throws, the button does nothing until the app restarts. "Stop dev server" releases the terminal's lock, which the server has not held since #247, so a build typed in the terminal can lose its Ctrl+C. And a server that exits before it has an address is probably reported as a crash with its reason lost, because its exit is announced before its failure is answered; that last one was read from the code and not confirmed. They change behaviour, so they are for PRs of their own. - **States with no journey, as before this PR:** the alert when the site has no build and the setup was not skipped, the setup checklist's own start button, a start that throws, a stop pressed while a start is under way (the button does not offer one), and a failed start in any of the orders the real server produces: the journey's failed start is an answer with no exit. - **The journey reaches one of the app's handlers through a map Electron does not document**, to count a question being asked and answered while leaving the answer real. If an Electron upgrade moves it, the journey fails at its first line and says why. - **The manual steps above were not run.** - **The journeys cost two more app launches** on every pull request, on both platforms: about three and a half seconds locally on macOS. - **Checked on macOS only.** Windows is covered by CI. - Review, two passes: 5 findings to fix, all in the new journey, all fixed here; 6 follow-ups, all older than this PR, the first three above. ## Related Part of #554: the second half of "dev server and watch", after #582. Part of #542. --- <details> <summary>Design decisions and alternatives considered</summary> **Arguments, not hooks calling hooks.** The dev server could call `useSiteMail` and `useSiteLogs` itself. Then the Mail panel and the Logs panel would have to reach through the server for their own state. Called side by side in `SiteRow`, each hook owns one thing and the server is told what to call. **`toggleDevServer` stays a plain function.** It was one, recreated on every render, and nothing memoised depends on it. Making it a `useCallback` would add a long dependency list to get nothing. **The derived values moved with the state.** Whether the server is starting, whether anything is running and what the button says are three expressions over the hook's state. Left in `SiteRow`, they would be the only reason to return `waitingForWatch`. </details> <details> <summary>Review outcome (required — see AGENTS.md)</summary> **First pass: 4 [fix here] · 6 [follow-up].** It diffed each old block against the hook and found one difference, the comment that was reworded on purpose. - Tests, low, [fix here], fixed in 05e8508: the check that Core's watch was started read the stand-in once, though the watch and the server are asked for in one go. It is polled. - Tests, low, [fix here], fixed in 05e8508: the wait before counting servers after a second press was a guess. The stand-in now counts the app's question about the site being asked and answered, and the journey waits for that. - Tests, low, [fix here], fixed in 05e8508: a read of "no script was stopped" after the crash did not wait for the main process first. - Tests, low, [fix here], fixed in 05e8508: the header left out two things the stand-in omits. - Architecture, medium, [follow-up]: the button goes dead after a start that throws. - Architecture, medium, [follow-up]: stopping the server releases a terminal lock it does not hold. - Architecture, low, [follow-up]: a stop during a start cannot be reached from the screen; only the main process's two-minute timeout ends a hung boot. - Architecture, low, [follow-up]: `waitingForWatch`, its ref and its effect are dead. - Architecture, low, [follow-up]: the button's label, the crash-or-stop decision and the failed-start test are decisions in a hook, where no unit test reaches them. - Tests, low, [follow-up]: two reads of the Server pane in the second journey rely on the watch's button not opening the watch's tab, which is a bug with a task of its own; fixing it will turn them red, honestly. **Second pass, on 05e8508 alone: 1 [fix here] · 0 [follow-up]**, fixed in 9845ffa. - Tests, low, [fix here]: the header said a real failed start is followed by an announced exit. That is one order of three. The header names all three and says none is walked. Reading the main process for this is what turned up the third bug in Risks. The second pass traced the new wait step by step with the guard removed and found that it bounds the path, and confirmed that the map the stand-in reaches through exists at the pinned Electron version and that wrapping the handler changes neither its answer nor its timing. - **Review:** completed — fresh agent context, local, before the PR existed, read-only; head e85ea99 / base eed1ab8; committed work only, working tree clean; then a second fresh context on 05e8508 alone; outcomes above. CodeRabbit: not run yet. - **Since review:** `05e8508 → 9845ffa` checked. The difference is two comments in the journey. It was not reviewed again by a fresh context. Checks, macOS, local: | Check | On | Result | |---|---|---| | `eslint . --max-warnings=0` | 9845ffa | clean | | unit suite on Node | e85ea99 | 1697 pass, 2 skipped, 0 fail | | unit suite on Electron's Node | e85ea99 | 1699 pass, 0 fail | | `npm run test:e2e` | 05e8508 | 51 of 51 pass | | `npm run test:e2e:packaged` | e85ea99 | 15 of 15 pass | After e85ea99 the only changes outside the journey are one removed heading and one comment. Audit of the journeys in `dev-server.spec.js`, with `mail.spec.js` and `logs.spec.js` run beside them, one break at a time, each restored before the next. Every one goes red on the line that makes the claim: | Broken | Goes red at | |---|---| | A second press is not stopped at the button | one server having been asked for | | The button does not say it is starting | the button's name, in both journeys | | The site is not opened in the browser | what was opened | | The server having an address does not make it running | the button offering to stop | | Stop does not ask for the server to be stopped | the stop asked for | | Stop leaves the address on screen | the address being gone | | Stop stops the last script in the directory too | no script having been stopped | | A stop that was asked for is read as a crash | no crash being reported | | A crash is not said to be one | the crash being reported | | A failed start gives no reason | the reason | | A failed start leaves the button starting | the button offering to start | | The server starts with no build to serve | the build being asked for first, in both | | A failed build leaves the button starting | the button offering to start | | A failed build says nothing about the server | the sentence saying the start was cancelled | | The server does not wait for the watcher to be ready | no server having been asked for yet | | The watcher being ready does not start the server | the server being asked for, in both | | A build that is already there is rebuilt by the watcher first | the sentence saying it was served as it is | | Whether the site is built is not read afresh | the same sentence | | The server starting does not bring the mail list to life | the mail journey | | The server starting does not start the debug.log tail | the logs journey | | What the server prints is not kept | the logs journey | The first audit showed the "pressed again" check was too weak: with the guard removed the journey went red, but four steps later and for another reason. A second press asks the main process how the site is before it asks for a server, and that question takes as long as reading the checkout takes, so one quick round trip was not a long enough wait. After the review the journey waits for that question itself to have been answered, and with the guard removed it goes red on the count. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
## Why `SiteRow` holds the state of eight separate things in one function, and #554 splits it into hooks by domain so the redesign in #542 can rebuild the screen around pieces it can hold. Five have left. Three remain, and they are coming in two PRs: this one is the trunk update, and the ticket and the apply chain, which call each other in both directions, follow together. ## What changes `src/renderer/hooks/use-trunk-update.jsx` is new. It holds the update's chain: the fetch and reset, the install when the lockfile moved, the rebuild, the hand-off to a resumed watch on projects whose watcher rebuilds everything, the question the update asks first when the tree has edits in it, and the retry after a run that was cut short. - **It is a chain, not a process, so most of what it does comes in as arguments**: the script runner, the terminal's lock and writer, the build watch's pause and resume, and the functions that re-read the site once the tree has changed. They are the calls the inline code made, in the same order. - **It returns what the screen shows**: whether an update is under way and at which step, the summary of the last one, the dirty-tree question's values, and the four handlers the buttons call. - **The date of the site's trunk and the "update incomplete" marker stay in `SiteRow`.** They are read from the site's status with everything else the status says; the hook only asks for the status to be read again. - **The hook is called earlier than the code it replaces was written**, straight after the helpers it needs, because the ticket card's gate reads the update's stage a few lines further down. **The journey owed from #582 is here.** Pausing the build watch and bringing it back had no journey with a watch running. A new `trunk-update-watch.spec.js` has two: the update with the watch running on Core, and on a project where the resumed watch does the update's build (#507). Both were written against the inline code first and pass on both. ## How to test this Platforms: any. Use the Buildkite build for the current head of this branch. The journeys carry this, from the repository root: `npx playwright test --project=journeys tests/e2e/journeys/trunk-update.spec.js tests/e2e/journeys/trunk-update-watch.spec.js`. - `trunk-update.spec.js` is the update with no watch running: a clean tree, a linked ticket, and the question about edits in the tree. It gains one assertion: after saving edits and updating, the notice names the file they went to. - The first new journey, on Core with the watch running, asserts that the update stops the watcher before it touches the tree (the file about to be replaced still held the old trunk when the watcher was stopped); that the tree is reset and the update runs the build itself; that the watch is not brought back while the build runs; that a failed build leaves the update incomplete and the watch is brought back all the same; that a retry stops the watch again, installs and builds; that a good build completes the update on the screen and in the store; and that a watcher the update stopped, exiting late, is not taken for the one running now. - The second, on a built Gutenberg checkout, asserts that the update runs no build of its own and brings the watch back at once; that it is not complete while the watch is still rebuilding, with the watch's stop enabled while the dev server's button is not; that the watch's ready line is what completes it; and that a watch stopped before it is ready leaves the update incomplete, on the screen and in the store, with the retry offered. In both new journeys the fetch and the reset are real, against an origin on disk; the install and the scripts are answered by stubs. What they cannot reach is a real install and a real build: **Starting state:** a Core site that has finished setup, whose trunk is behind `wordpress-develop`, with the build watch running and one file edited. 1. Click "More", then "Update to latest trunk". A dialog asks what to do with the edit. Choose to save it and pick a location. 2. The card "Updating to latest trunk" appears and steps through fetching, installing or skipping the install, and building. The "Build watcher" tab reads "Build watcher (paused)". 3. When it ends, a green notice says the site is up to date and names the file your edit was saved to. The tab reads "Build watcher (watching)" again. **What must not have happened:** - The edit is not lost: it is in the file you chose, and the notice names that file. - `node_modules` was not reinstalled if the lockfile did not change: the card names the install step as skipped. ## Risks and limitations - **The hook takes twenty arguments.** The update touches the runner, the terminal, the watch and the site's status, and takes each as it was used. Every function in it is a plain closure, as before, so none of those arguments is a dependency of anything. - **Not covered by a journey, as before this PR:** an update that finds nothing to fetch, an install that fails, and a hand-off where the watch was stopped during the reset. - **Pausing the watch still has no journey for the other three things that do it**: checking out or leaving a pull request, applying a patch, and switching tickets. Those belong with the PR that moves the apply chain and the ticket. - **The manual steps above were not run.** - **The new journeys cost two more app launches** on every pull request, on both platforms: about four seconds locally on macOS. - **Checked on macOS only.** Windows is covered by CI. - Review, two passes: 5 findings to fix, all in the journeys or their comments, all fixed here; 3 follow-ups, one of them applied. ## Related Part of #554: the trunk update, after the dev server in #583. It carries the pause-and-resume journey that #582 left for it. Part of #542. --- <details> <summary>Design decisions and alternatives considered</summary> **The trunk's date and the incomplete marker are not the update's.** `loadStatus` sets them with the rest of what the main process says about the site, and the staleness notice and the sidebar read them whether or not an update ever runs. Moving them would mean the status loader writing into the update's hook. **Plain functions, not `useCallback`.** `beginTrunkUpdate`, `finishUpdate` and the rest were closures recreated on every render, called from handlers and from each other. Memoising them here would add twenty-item dependency lists and change when they see fresh state, which is the kind of change a move should not make. **The dirty-tree question's state moved with the update.** The dialog that asks it is a component of its own (#570) and holds nothing; what it shows was the update's all along, and the two answers it offers both end by starting the update. **A new journey file, not more tests in `trunk-update.spec.js`.** The existing three run the real build script of the fixture. The new two need the runner answered by a stub, so that a watch can be "running" and a build can be made to fail or to wait. </details> <details> <summary>Review outcome (required — see AGENTS.md)</summary> **First pass: 5 [fix here] · 2 [follow-up].** It diffed the old blocks against the hook with indentation removed and found one blank line of difference. - Tests, low, [fix here], fixed in 54620e5: `build-watch.spec.js` still said no journey pauses the watch. - Tests, low, [fix here], fixed in 54620e5: in the second journey, "the incomplete marker is gone from the screen" could not fail where it stood, since nothing had re-read the site by then. It is removed; the store is the check there. - Tests, low, [fix here], fixed in 54620e5: two comments claimed more than their assertions. "Before it touches the tree" is now checked: the stand-in keeps what the file about to be replaced held when the watcher was stopped. "The one thing that can be pressed" says what is checked. - Tests, low, [fix here], fixed in 54620e5: the watcher's first line was said before the main process was known to have answered the start, so on a slow runner it could be said to nobody. The journey waits for the run to have been asked for. - Tests, low, [fix here], fixed in 54620e5: the script the watcher is on Gutenberg was marked an invariant. It is a characterisation. - Tests, low, [follow-up]: paths with no journey, in Risks above. One of them, the notice naming the saved patch, is one assertion in the existing journey and was added in 54620e5, since the ref that carries it moved here. - Architecture, low, [follow-up]: which step runs the build, and the "already up to date" branch, are decisions in a hook where no unit test reaches them, as they were in `index.jsx`. **Second pass, on 54620e5 alone: 0 [fix here] · 1 [follow-up]**, applied in 07f5ddc: the header of `build-watch.spec.js` listed what pauses the watch and left out checking out a pull request and leaving one. Both passes confirmed that no moved name is read before the hook is called, that each of the twenty arguments is the value the old closures read, and that the callbacks which fire long after they were made (a fetch ending, a build ending, the watch being ready) see the same render's values as before. - **Review:** completed — fresh agent context, local, before the PR existed, read-only; head 309d129 / base 62ec328; committed work only, working tree clean; then a second fresh context on 54620e5 alone; outcomes above. CodeRabbit: not run yet. - **Since review:** `54620e5 → 07f5ddc` checked. The difference is one comment. It was not reviewed again by a fresh context. Checks, macOS, local: | Check | On | Result | |---|---|---| | `eslint . --max-warnings=0` | 07f5ddc | clean | | unit suite on Node | 309d129 | 1697 pass, 2 skipped, 0 fail | | unit suite on Electron's Node | 309d129 | 1699 pass, 0 fail | | `npm run test:e2e` | 54620e5 | 53 of 53 pass | | `npm run test:e2e:packaged` | 309d129 | 15 of 15 pass | After 309d129 every change is in the journeys. Audit of the journeys, on the commit each line was written for, one break at a time, each restored before the next. Every one goes red on the line that makes the claim: | Broken | Goes red at | |---|---| | The update leaves the watch running | the stop asked for, in both new journeys | | The watch is not brought back when the update ends | the watcher being asked for again | | The site is not re-read when the update ends | the incomplete marker being gone from the screen | | A retry leaves the watch running | the second stop asked for | | A retry does not install | the install asked for | | The update ending does not give the terminal back | the retry, which is then refused | | A completed update is not recorded | the marker gone from the screen and the store, and the old journey's summary | | A completed update is not announced | the announcement, in three journeys | | The update builds even where the watch will | the scripts asked for | | The update is complete as soon as the watch is brought back | the card still showing | | Nothing can be pressed while the update waits on the watch | the watch's stop being enabled | | The watch being ready does not complete the update | the announcement | | A watch stopped before it is ready leaves the update hanging | the incomplete marker | | Other buttons can be pressed during an update | the card showing | | An update does not ask about edits in the tree | the question, in the old journey | | Saving the edits first saves nothing | the save dialog, in the old journey | | The question about edits cannot be closed | the question being gone, in the old journey | | The watcher is stopped only after the tree was reset | what the file held when the watcher was stopped | | The notice does not say where the edit was saved | the notice, in the old journey | One break was not caught, in a journey this PR does not change: with "discard my edits, then update" made to discard nothing, `trunk-update.spec.js` still passes, because the update's own reset removes the edits a moment later. The screen and the tree end the same either way. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Why
SiteRowholds the state of eight separate things in one function, and #554 splits it into hooks by domain so the redesign in #542 can rebuild the screen around pieces it can hold. Mail, the logs and the terminal have left. The issue's next item is "dev server and watch". This is the first half of it: the build watch, and the script runner it and everything else runs through. The dev server follows in a PR of its own.What changes
Two hooks, because they are two things.
src/renderer/hooks/use-site-scripts.jsxis the npm runner. It holdsrunInstall,runScriptandkillCurrent, whether an install or a build is running, whether the last build failed, whether the last thing to writebuild/was cut short, and the run a stop should reach. The terminal, the setup, the update, the apply, the ticket switch and the build watch all run through it.src/renderer/hooks/use-build-watch.jsxis the build watch. It holds the watch's state, its run, who is waiting for it to be ready, and start, stop, pause, resume and hand-off. It takes the runner and the terminal's lock as arguments: a site with no build is built once before it can be watched, and that one build holds the terminal.SiteRowtakes them out under the names it always used, so the update, the apply and the ticket switch did not change.stopDevServer's, for the reason the terminal's PR gave: the linter cannot know that a ref from a hook is stable.Not in this PR: the dev server (
stopDevServer,startPhpServer,toggleDevServerand their state), which is next.No journey touched the build watch, so a new
build-watch.spec.jshas two, and the terminal's journey now pins the "a build is running" and "an install is running" flags, which nothing on screen had depended on alone. All were written against the inline code first and pass on both.How to test this
Platforms: any. Use the Buildkite build for the current head of this branch.
The journeys carry this, from the repository root:
npx playwright test --project=journeys tests/e2e/journeys/build-watch.spec.js tests/e2e/journeys/terminal.spec.js.What they cannot reach is a real watcher and a real build:
Starting state: a Core site that has finished setup, with no dev server and no build watch running.
src/. The tab shows the watcher compiling it.npm run buildand press Enter. It runs: the watch does not hold the terminal.What must not have happened:
pgrep -fl gruntin a terminal shows none for the site's directory.Risks and limitations
pauseWatcherandresumeWatcherwith no watch running, where both do nothing. A journey for it belongs with the PR that moves the trunk update's state, which needs the same stand-ins; it is left for that one on purpose.build-watch.spec.json the final test code, one failed at the first click of the second journey; the 14 after it passed, and the full suite passed. My script did not keep that run's output, so the cause is unknown. Test windows take focus on the desktop they run on, and a click or a key from the person at it is the likeliest cause, but that is a guess. CI is the clean run.SiteRowinto the hook; the code is the same.SiteRowthan it was, where the hooks are called. Nothing reads it before that point; the one thing that did, the list of log tabs, moved below the hook.Related
Part of #554: the first half of "dev server and watch", after mail in #579, the logs in #580 and the terminal in #581. Part of #542.
Design decisions and alternatives considered
The watch and the runner now, the dev server next. The three together are about 450 lines of logic that guards against each other's late callbacks. The dev server starts behind the watch and reads its state; the watch knows nothing of the server. So the watch can move first and the server can then depend on a hook.
"The build was cut short" lives with the runner, not with the watch. The watch sets it and a build ending well clears it, and the runner is where a build ends. With the flag in the watch's hook, the runner would need the watch and the watch needs the runner.
Which apply's hand-off is current stays in the watch's hook for now. It is the apply's state, and pausing the watch is what invalidates it. When the apply chain moves, it can take the ref with it and give the watch a function to call.
The watch takes eleven arguments. Eight of them are other hooks' functions and refs. That is the coupling the code already had, written down in one place; making it smaller means changing what the watch does, not where it lives.
Review outcome (required — see AGENTS.md)
First pass: 4 [fix here] · 2 [follow-up]. It compared the lines removed from
index.jsxwith the two hooks as a sorted set and in order, and found them identical apart from the headers, the imports and the return values.tests/e2e/helpers/ui.cjs.Second pass, on 4cecbb6 alone: 2 [fix here] · 1 [follow-up], all three about comments, the first two fixed in e947290.
SiteRow'sstopDevServersays the watch is stopped only by its own control, with the same gap. It is in the code the next PR moves.Both passes confirmed that no moved name is read before its hook is called, including in dependency lists, that no callback is less stable than before, and that nothing real can be started by the journeys.
4cecbb6 → e947290checked. The difference is comments in five files. It was not reviewed again by a fresh context.Checks, macOS, local:
eslint . --max-warnings=0npm run test:e2ebuild-watch.spec.js, repeatednpm run test:e2e:packagedAfter 6c1327b the only changes outside the tests are comments in the two hooks.
Audit of the journeys, on the commit each line was written for, one break at a time, each restored before the next. Every one goes red on the line that makes the claim:
The first audit found one break nothing caught: the "a build is running" flag. Wherever a build ran in a journey the terminal's lock was held too, and every assertion was satisfied by the lock. The terminal's journey now watches a button that depends on the flag and not on the lock.
🤖 Generated with Claude Code