Repository navigation
Fix a site's terminal showing stretched text when the site was not open at launch - #592
Conversation
Every site's view mounts behind `display: none`, the selected one included: the selection is made by an effect that runs after the views have mounted. The terminal was opened in that hidden container. xterm's DOM renderer derives its letter-spacing from a glyph it measures in the document, and there the glyph measures zero, so the spacing came out one whole cell wide. A row drawn while the site was still hidden inherited it and kept it once the site was opened: every letter a cell apart, each line cut off half way. That is every site but the one the app opens on, for anyone with two sites. The terminal is still created when the view mounts, so nothing written to it is lost, and is put on the page by a second effect the first time its site is the one on screen. The hook is told which site that is. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ight The step that goes back to the first site reads rows drawn before the site was hidden, so it is marked as a characterisation and says so. The help line it measures is 74 characters, not 75. 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 |
Brings in the terminal fix (#592): a site's terminal is opened the first time the site is shown. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
## Why #554 took eight domains out of `SiteRow`, and the largest thing it left behind is the one nearly every hook and card reads: what the main process says about the site. Thirteen pieces of state, set by one function, sat in the middle of the component. The redesign's page header and sites list (#556) need that status from outside the site's view, and a hook is the first step to handing it to them. ## What changes - **`src/renderer/hooks/use-site-status.jsx` is the site's status.** Whether it is installed and built, the ticket it is working on, what is applied to it, the age of its trunk, and the two states the app reads and never writes over: a site the old engine made, and a merge left unfinished outside the app. With them, `loadStatus`, which reads them all in one answer, and the effect that calls it when the site's view mounts. - **Five setters are handed out**, as they were used before: linking or leaving a ticket and moving one onto trunk, the dev server's start, skipping the first-run checklist, and discarding every change. The other eight values change only through `loadStatus`. - **It is a move.** The lines are `SiteRow`'s own. - **One new journey, `site-status.spec.js`**, for the one wiring the move reshaped: the status reports to the sites list through a ref that is now an argument, and nothing read that the report arrives. This goes to trunk and not to `redesign/wpds` because nothing on screen changes; trunk is merged into the redesign branch afterwards. ## How to test this Platforms: any. This has no surface of its own: the screen is the same before and after. From the repository root, `npm run test:e2e` runs the journeys, all of which read a site's status one way or another. The ones that would notice a status not read, or read wrong, are `legacy-site`, `merge-in-progress`, `pr-checkout`, `patch-apply`, `trunk-update-watch`, `store-persistence`, `build-watch` and the new `site-status`; the audit in the review outcome says which. `site-status.spec.js` seeds the store with a trunk thirty days old for a checkout whose trunk is minutes old, and expects the site's entry in the list to end up with the name of a site that has nothing to report. It is red when the report is dropped, when the hook is handed another ref, and when the date is left out of the report. **What must not have happened:** - A site's view does not read its status more often than before: `loadStatus` keeps its identity for as long as the site's path does, so the effect that calls it on mount runs once. ## Risks and limitations - **Two dependency lists gain a value** that was already stable: `loadStatus` lists the ref it reports through, and the callback that skips the checklist lists a setter that now comes from the hook. - **One effect changes place.** The read on mount now runs before the effect that clears the copy button's timer, which shares nothing with it. - **Six of the things the audit broke are not noticed by any journey**, and were not before the move. They are listed in the review outcome; none is new. - **`loadStatus` swallows a failed read**, as it did in `SiteRow`: the site then looks not installed and not built, nothing is logged, and the setup chain does not start. Fixing it would stop this being a move, so it is left for its own change. - **Discarding all changes with a patch applied has no journey.** That path does not read the status again, so one setter is all that clears the applied-patch banner. - **What is reported to the sites list is decided inside the hook**, out of the unit tests' reach. It should move to a module before #556 reads the status from outside the view. - **Checked on macOS only.** Windows is covered by CI. - **Pre-PR review, two passes: the move holds line for line; the hook's header comment was wrong in several places and is rewritten; four findings are left as follow-ups.** ## Related Follow-up to #554. Prepares #556, part of #542. --- <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 the move alone; second pass on the fix-up. The branch was rebased onto trunk after #592 merged, and the checks below are on the rebased head. **First pass: one finding to fix here, four follow-ups.** It diffed the hook's body against `SiteRow`'s old lines and found two differences, both intended; checked that every returned name is read only after the hook's call and that the eight setters not returned were only ever used inside `loadStatus`; and found the one effect that changed place, the copy button's timer, sharing nothing with the status. To fix: the hook's header said a finished build sets "built" (it goes through `loadStatus`; the dev server's start is what sets it), that everything comes from one read (the handed-out setters mean it does not always), and that the status is read when a site's view is opened (every listed site's view mounts as the app opens). Rewritten. Follow-ups, all in Risks above: the silent `catch`, the missing journey for a discard with a patch applied, the report to the sites list being untested, and that report's decision living in a hook. The third is closed here by `site-status.spec.js`. **Second pass: four findings, one medium, all fixed.** The medium one asked that the new journey be shown to go red, since it never sees the state it starts from: it does, three ways, as the table below records. The other three were sentences in the two headers that left a case out. **Checks, on macOS, on the head of this branch:** | Check | Result | | --- | --- | | `npx eslint . --max-warnings=0` | clean | | `npm test` | 1733 pass, 2 skipped | | `npm run test:electron` | 1734 pass, 1 skipped | | `npm run test:e2e` (journeys) | 57 of 57 on `62c5ab5`; the head differs from it by comments, and `site-status.spec.js` was run again on it | **That it is a move.** Comparing the lines removed from `index.jsx` with the hook's, every removed line is in the hook except two dependency lists and a comment that said the trunk update was "below". No name the hook returns is read in `SiteRow` above the hook's call. **The wiring was audited by breaking it**, one thing at a time, and running the journeys: 17 breaks, 11 caught. | Break | Caught by | | --- | --- | | The status is not read when the view mounts | `build-watch` | | A legacy site is not told apart | `legacy-site` | | An unfinished merge is not seen | `merge-in-progress` | | The checked-out pull request is not known | `pr-checkout` | | An unfinished update is not known | `trunk-update-watch` | | The applied patch is not known | `patch-apply` | | The linked ticket is not read back | `store-persistence` | | Whether the site is built is not known | `build-watch` | | The dev server's start does not set "built" itself | **nothing**: it decides from its own read, and the state only changes what is shown until the next status read | | Skipping the checklist does not set it itself | **nothing**: every journey starts with the checklist already skipped | | Linking a ticket does not set it itself | **nothing**: the link reads the status straight after | | Discarding does not clear the applied patch itself | **nothing**: no journey discards with a patch applied | | The status never stops loading | **nothing**: it only disables the first-run checklist's buttons, which no journey shows; `setup-steps.cjs` has unit tests for what it does with the flag | | The status does not reach the sites list | `site-status` | | The hook is handed another ref to report through | `site-status` | | The trunk's date is left out of the report | `site-status` | | `loadStatus` returns nothing to its caller | **nothing**: only the setup chain reads it, and only the opt-in real-setup suite runs that | </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Why
A site's terminal shows its text stretched, every letter a whole cell apart and each line cut off half way, for any site that was not the one on screen when the app opened. For a contributor with two sites, that is every site but one, and it stays that way: the lines printed at startup never recover.
What changes
The root cause is where the terminal was opened, not how. Every site's view mounts inside a
display: nonewrapper, the selected one included, because the selection is made by an effect that runs after the views have mounted. The hook created the terminal and calledterm.open()in that hidden container. xterm's DOM renderer sets the spacing between characters from a glyph it measures in the document, and in a hidden container the glyph measures zero, so the spacing came out one cell wide. A row drawn then kept it.The terminal is still created when the view mounts, so nothing written to it is lost: what is written before it opens stays in its buffer and is drawn when it does. Opening it moves to a second effect, which puts it on the page the first time its site is the one on screen. The hook is told which site that is, through a new
isActiveargument.How to test this
Platforms: any. Use the Buildkite build for the current head of this branch.
A journey carries this. From the repository root,
npx playwright test --project=journeys tests/e2e/journeys/terminal.spec.js; the second test in that file is new. It starts the app with two sites, opens the one that was hidden at launch, and measures how far the longest line of the terminal's help runs past the end of its row: nothing, for the site open at launch, for the one opened afterwards, and for the first again. On trunk it is red at the second measurement, with the line running 532 pixels past its row.What it cannot reach is the look of it, which takes a minute by hand:
Starting state: the app with two or more sites.
What must not have happened:
Risks and limitations
Related
Found while building the new shell for #555, where switching sites is the main way around the app. After this merges, trunk is merged into
redesign/wpds.Review outcome (required — see AGENTS.md)
Run as the repository's review procedure, with the judgement pass in a fresh context that took no part in the change, on
848f596againste1b17dd. The head,7e76e31, differs from it by two comments in the journey, which that review asked for.No finding to fix here; three follow-ups, all low. The reviewer read xterm 5.5's source for everything the app does with a terminal before it is opened and found each of it safe: writes are buffered and drawn by
open(),scrollToBottomandfocusdo nothing without a viewport or a textarea,disposeof a terminal never opened removes nothing that is not there, andelementis set byopen()alone, which returns at once when called again. It also confirmed that a site's view and itsisActiveare decided by the same expression in the same render, so the terminal cannot be opened while its container is hidden, and that the site the app opens on getsisActiveonly after the selection effect, when its container is already shown.Left as follow-ups:
Checks, on macOS, on the head of this branch:
npx eslint . --max-warnings=0npm testnpm run test:electronnpm run test:e2e(journeys)848f596;terminal.spec.jsagain on the headThe journey was audited by breaking the fix and running it: with the terminal opened as it is made, it is red on the measurement for the site opened later; with the terminal never opened, the first journey in the file is red at its first read of the screen. A third break, opening the terminal again each time its site is shown, turned nothing red: xterm returns at once from a second
open(), so there is nothing for a test to see, and the journey does not claim there is.🤖 Generated with Claude Code