Skip to content

Move a site's trunk update out of SiteRow into a hook - #585

Merged
zaerl merged 3 commits into
trunkfrom
update/site-trunk-update-hook
Oct 1, 2026
Merged

zaerl merged 3 commits into
trunkfrom
update/site-trunk-update-hook

Conversation

@zaerl

@zaerl zaerl commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

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.


Design decisions and alternatives considered

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.

Review outcome (required — see AGENTS.md)

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.

🤖 Generated with Claude Code

zaerl and others added 3 commits October 1, 2026 15:59
The fifth domain of #554. useTrunkUpdate holds the update's chain: the
fetch and reset, the install when the lockfile moved, the rebuild, the
hand-off to a resumed watch where the watcher rebuilds everything, the
question it asks first about edits in the tree, and the retry.

It is a chain and not a process of its own, so what it runs through
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. Its functions are plain
closures, as they were.

The trunk's date and the incomplete marker stay in SiteRow with the rest
of what the site's status says. The hook is called above the ticket
card's gate, which reads the update's stage.

Pausing the watch and bringing it back had no journey with a watch
running; #582 left that for here. Two journeys do it now, on Core where
the update builds, and where the resumed watch does the update's build.
Both 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.

"Stops the watcher before it touches the tree" checked only the stop.
The stand-in now keeps what the file about to be replaced held when the
watcher was stopped, and the journey reads it; with the watcher stopped
only after the reset, it goes red on that line. "The one thing that can
be pressed" checked two controls, and says so. A check that the
incomplete marker was gone from the screen could not fail where it
stood, since nothing had re-read the site yet; the store is the check
there. The watcher's first line is said only once the main process has
answered that it started, or it would be said to nobody. The script the
watcher is on Gutenberg is a characterisation.

build-watch.spec.js said no journey pauses the watch; the update's now
does. And the journey that saves edits before an update reads that the
notice names the file they went to, which nothing had checked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
From the review of the previous commit. A comment only: checking out a
pull request and leaving one pause the watch too, and their journey
runs with no watch running, like the others the header lists.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: WordPress/contributor-toolkit/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: c6073a34-9851-466b-930c-38891b8bf67d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@zaerl
zaerl merged commit 600b277 into trunk Oct 1, 2026
8 checks passed
@zaerl
zaerl deleted the update/site-trunk-update-hook branch October 1, 2026 14:28
zaerl added a commit that referenced this pull request Oct 1, 2026
)

## 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. Six have left. These are the last
two on the issue's list, and they come together: the ticket a site is
working on, and the chain that applies someone else's patch or pull
request.

## What changes

Two hooks, in one PR because each calls the other.

- **`src/renderer/hooks/use-site-ticket.jsx` is the ticket.** Linking
one, leaving it, switching between the ones with work on the site,
moving one onto the current trunk and deleting one's work, with the
question a switch asks when trunk has loose edits.
- **`src/renderer/hooks/use-apply-patch.jsx` is the apply chain.** What
a ticket offers to apply (its linked pull requests and its Trac
attachments), the preview, and the chain that applies a patch, takes it
off, checks a pull request out or leaves one, and rebuilds the site
around the result.
- **They reach each other through three refs, as the inline code did.**
A ticket switch can put a parked pull request back, which ends in the
apply chain's install and build; and a checkout refused for trunk's
loose edits is tried again from the ticket's panel. The ticket hook is
called first, because the apply chain needs the terminal and the build
watch, which are set up further down. So the ticket hook makes the refs
and the apply hook fills them.
- **What the site's status says stays in `SiteRow`**: which ticket is
linked, which patch is applied, which pull request is checked out. The
status loader writes them, and both hooks read them.
- **One function in `SiteRow` is now memoised**, `applyDiscardToNote`.
It touches a ref and a setter and nothing else. Inside a hook it has to
be listed as a dependency, and unmemoised it would have given two of the
ticket's callbacks a new identity on every render.

**The journeys #585 left for here are in a new
`watch-during-changes.spec.js`**: a ticket switch, a patch and a pull
request's checkout with the build watch running. They were written
against the inline code first and pass on both.

**One assertion from #585 is corrected in
`trunk-update-watch.spec.js`.** "The update is not complete while the
watch is still rebuilding" waited for the announcement to be absent, and
the announcement goes away by itself, so it could not fail. It now
counts the request that records a completed update, which the page sends
in the same turn as it hears the watcher's line.

## 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, `npm run test:e2e`
runs them all; the ones that walk these two hooks are `ticket-branches`,
`ticket-rebase`, `patch-apply`, `pr-checkout`, `store-persistence`,
`merge-in-progress`, `gutenberg-site`, `review-changes` and the new
`watch-during-changes`.

- The existing ones are unchanged: linking, unlinking, switching and
deleting tickets; moving a ticket onto trunk; applying and reverting a
patch file, with its refusals; checking a pull request out and leaving
it, with ticket work and edits kept on their own branches; and all of it
surviving a restart.
- The first new journey, on Core with the watch running, asserts that
linking a ticket and applying or reverting a patch of source files stop
nothing and build nothing, with the watch's tab saying it is compiling;
that a ticket linked by hand has its Trac page read once without
anything else being pressed; and that a pull request's checkout stops
the watcher, lands, runs the build itself, announces nothing until that
build ends, and brings the watch back.
- The second, on a built Gutenberg checkout, asserts that a pull
request's checkout stops the watcher, runs no build, brings the watcher
back, and announces the checkout only when the watch prints its ready
line.

What they cannot reach is the network and a real build:

**Starting state:** a Core site that has finished setup, with no ticket
linked.

1. In the ticket card, type an open Trac ticket's number that has a pull
request, and click "Link ticket". The ticket's title and its linked pull
requests appear without anything else being clicked.
2. Click "Apply…" on a pull request, then "Apply and rebuild". The
site's terminal shows the checkout, then the build; when it ends, a
message reads "Checked out the pull request".
3. Click "Revert this PR". The terminal shows the way back and a
rebuild; the ticket card is as it was after step 1.
4. Edit a file, then click "Unlink". The panel asks nothing: the edit
stays on the ticket's branch. The ticket is listed under the tickets
with work on this site, and "Continue working on #" and its number
brings the edit back.

**What must not have happened:**

- The edit made in step 4 is not lost by unlinking and continuing.
- `node_modules` was not reinstalled in steps 2 or 3 unless the pull
request changes the lockfile: the step list names the install as
skipped.

## Risks and limitations

- **This is the largest move of the series**: about 790 lines leave
`index.jsx` for two hooks, which is over the size where this repository
asks whether to split. It was kept whole because the two halves call
each other, and a split would have meant an interface between them that
the next PR deleted. The check that it is only a move is mechanical and
is in the review outcome below: every line removed from `index.jsx` is
in one of the two hooks, apart from imports and four dependency lists.
- **The apply hook takes thirty-one arguments.** It is the chain that
touches everything: the runner, the terminal, the watch, the status, and
the ticket hook's refs and setters.
- **Four dependency lists changed**, all by gaining values that were
already stable: two state setters and a ref that are now arguments in
the ticket hook, the memoised `applyDiscardToNote`, and a ref in the
apply hook's effect.
- **Effects run in a different order relative to the setup chain's.**
The effects that read a ticket's pull requests and fill the refs used to
be declared after the setup chain's and are now run before them, where
the apply hook is called. Neither reads the other's state.
- **Not covered by a journey with the watch running:** leaving a pull
request, a ticket switch that puts a parked pull request back, a patch
that changes the lockfile, a switch while the watch is still building,
and a hand-off to the watch that a later apply or a stop by hand cuts
short. All of it is moved code that behaved this way before.
- **No journey reads the unsubmitted-changes note after a switch**, with
or without a watch. The ticket hook re-measures it in four places.
- **One journey failed once in three full runs**, and not in a way that
points at this change: in the second run the app's window closed under
`pr-checkout.spec.js:177` three seconds in ("Target page, context or
browser has been closed"). That file then passed five times in a row and
the next full run was 55 of 55. Windows opened by the journeys take
keyboard focus on the machine they run on, and it was in use.
- **The manual steps above were not run.**
- **Checked on macOS only.** Windows is covered by CI.
- **Pre-PR review: 4 findings fixed, 2 left as follow-ups, none in the
moved code.** All were in journeys or comments. Details are in the
review outcome below.

## Related

Part of #554: the ticket and the apply chain, the last two domains on
its list, after the trunk update in #585. Part of #542.

---

<details>
<summary>Design decisions and alternatives considered</summary>

**Two hooks, not one.** A ticket is linked, switched and deleted without
anything being applied, and the panel that does it is a different card.
Together they would be one hook of nine hundred lines with two jobs.

**What a ticket offers to apply is in the apply hook.** The linked pull
requests and the Trac attachments are fetched for the ticket, but they
are read by the panel that previews and applies them, reading them
clears the apply's error, and a failed apply counts them. In the ticket
hook they would need the apply hook's functions, and the apply hook
would need them back.

**The refs between the two hooks were kept as refs.**
`ticketSwitchLifecycleRef` and `retryPrSwitchRef` exist because the
functions they hold are declared after the code that calls them, and are
re-created on every render so that they see current state. That is still
true with the caller in one hook and the functions in another. A cleaner
interface would be the apply hook passed to the ticket hook as an
argument, which the order of calls does not allow.

**The linked ticket, the applied patch and the checked-out pull request
stay in `SiteRow`.** `loadStatus` sets them with the rest of what the
main process says about the site. They are the next thing to move, as a
hook for the site's status, and that is outside #554's list.

</details>

<details>
<summary>Review outcome (required — see AGENTS.md)</summary>

Run as the repository's review procedure, with the judgement pass in a
fresh context that took no part in the change. First pass on `82d7071`
against `600b277`; second pass on the fix-up `4acbe42`; the head is
`005a80e`, which adds the one comment the second pass asked for.

**First pass: 4 to fix here, 3 follow-ups (one finding counted in both).
Nothing in architecture, security, performance or cross-platform; the
move was found faithful.** It repeated the line-by-line comparison,
checked that each function body is intact and in its old order, that
every returned name is read only after its hook is called and every
argument is declared before it, which effects changed order relative to
`SiteRow`'s and that none reads what the other writes, that the three
refs are filled before anything can call them, and that late callbacks
read the same render's values as before.

Fixed:

- **The absence read in `trunk-update-watch.spec.js` was not sound**,
and the comment added with it said it was. The update announces only
after the main process answers the request that records it, so one look
at the screen could come back before a wrong announcement. The journey
now counts that request, on the app's real handler, and keeps the look
at the screen for an announcement made without completing.
- **Nothing asserted that a ticket linked by hand has its page read
(#292)**, which is what one of the three refs between the hooks is for.
The new journey counts the read and expects exactly one.
- **A wait that could be late on a slow runner.** The watch's tab says
"compiling" for three seconds and was looked at after the announcement;
it is now looked at first.
- **Stale words**: two journey headers that this change made untrue, and
four comments in the hooks that pointed at code now in the other hook.

Left as follow-ups, both in Risks above: the flows with the watch
running that have no journey, and the unsubmitted-changes note.

**Second pass: 1 to fix here, 1 follow-up.** The count of completion
requests is how the app records an update today and not a promise, so it
is now marked as a characterisation. The follow-up: inner waits of 60
seconds in `pr-checkout.spec.js` and `trunk-update-watch.spec.js` cannot
be reached inside the 60-second budget of a test; the ones in the new
file are 30.

Noted and left: the stand-in for the runner in
`watch-during-changes.spec.js` is a near copy of the one in
`trunk-update-watch.spec.js`.

**Checks, on macOS, on the head of this branch:**

| Check | Result |
| --- | --- |
| `npx eslint . --max-warnings=0` | clean |
| `npm test` | 1697 pass, 2 skipped |
| `npm run test:electron` | 1699 pass |
| `npm run test:e2e` (journeys) | 55 of 55 on the head; one failure in
an earlier run, described in Risks |
| `npm run test:e2e:packaged` | 15 of 15, on `4acbe42`; the head differs
from it by a comment in a journey |
| `watch-during-changes.spec.js` as first written, against trunk's
inline `index.jsx` | pass |

**That it is a move.** Comparing the lines removed from `index.jsx` with
the lines of the two hooks, every removed line is in a hook except the
import lines, four dependency lists, the first line of
`applyDiscardToNote` and one heading comment. The comments corrected
after the review are the only lines of the hooks that differ from what
was removed. No name a hook returns is read in `SiteRow` above the
hook's call.

**The assertions were audited by breaking what they claim**, one break
at a time in the moved code, and running the journeys that should
notice. 26 breaks: 23 turn a journey red on the line that states the
claim, 3 do not.

| Break | Caught by |
| --- | --- |
| The apply chain does not tell a switch what to call |
`ticket-branches` |
| A checkout refused for loose edits cannot be tried again |
`pr-checkout` |
| A checkout refused for loose edits does not ask about them |
`pr-checkout` |
| A link made by hand does not ask for the ticket's details |
`pr-checkout` |
| A switch is not refused while a command runs | `pr-checkout` |
| The apply chain is not told which pull request is checked out |
`pr-checkout` |
| The apply chain is not told which ticket is linked | `pr-checkout` |
| The apply chain is not told this site is in front | `pr-checkout` |
| A switch does not re-read the branch list | `ticket-branches` |
| The branch list is not read when the site opens | `store-persistence`
|
| A ticket switch is not handed to the watch | `watch-during-changes` |
| A patch of source files is not handed to the watch |
`watch-during-changes` |
| A patch of source files is built all the same | `watch-during-changes`
|
| A pull request's checkout leaves the watch running |
`watch-during-changes` |
| The watch is not brought back when the apply ends |
`watch-during-changes` |
| The checkout is announced before its build ends |
`watch-during-changes` |
| The checkout builds even where the watch will | `watch-during-changes`
|
| The checkout is announced before the watch is ready |
`watch-during-changes` |
| The watch being ready is not announced | `watch-during-changes` |
| The update is announced, without completing, before the watch is ready
| `trunk-update-watch` |
| The update completes before the watch is ready | `trunk-update-watch`
|
| The apply chain is handed another ref for the ticket just linked |
`watch-during-changes` |
| A ticket's page is read twice on a link | `watch-during-changes` |
| A switch does not re-read the site | **nothing**: the end of the
switch re-reads it anyway |
| The unsubmitted-changes note is not measured again after a switch |
**nothing**: no journey reads that note |
| An applied patch is not recorded on the screen | **nothing**: the
build that follows re-reads the site |

The three that nothing catches were not caught before the move either.

</details>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@zaerl zaerl added this to the v2.0.0 milestone Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant