Skip to content

Move the pure components and the hooks out of index.jsx into files of their own - #567

Merged
zaerl merged 2 commits into
trunkfrom
update/renderer-components
Sep 30, 2026
Merged

zaerl merged 2 commits into
trunkfrom
update/renderer-components

Conversation

@zaerl

@zaerl zaerl commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Why

src/renderer/index.jsx is 6,420 lines, and the redesign in #542 has to change almost all of it. Between its imports and the two components that are most of the file sat seven components that take props and nothing else, and five hooks. They can be changed one at a time once they are files of their own, and what is left in index.jsx afterwards is the part that still has to be taken apart.

What changes

The twelve move, verbatim, and index.jsx imports them back:

  • src/renderer/components/: ReasonedButton, DiscardChangesLink, DiffText, LogText, Destination and DestinationGroup (one file), TerminalCommandLink.
  • src/renderer/hooks/: useDetectedEditors, useContributorProvenance, useNextActionCue, useSites, and useConfirmation with ConfirmationContext.

Nothing is rewritten. Each function gains export, each file gains the imports it needs, and five comments that pointed "above" or "below" now name the file they mean. index.jsx is 6,085 lines.

Two things follow from there being more than one renderer source:

  • build.files excludes every .jsx under src/renderer/, where it excluded index.jsx alone. In that directory .jsx now means "bundled by esbuild", and without this the new files would ship uncompiled beside the bundle that already contains them. The hooks are named .jsx for that reason, although they hold no JSX. The packaged smoke test reads the same rule.
  • The review standard and TESTING.md said a decision made in index.jsx is one the suite cannot reach. That is as true of a component or a hook beside it, and they say so.

Deliberately not in this PR: any change to what a component renders or a hook does, the five modals (#553), and SiteRow's state (#554).

How to test this

Platforms: any. Use the Buildkite build for the current head of this branch.

The journeys carry this: npm run test:e2e, from the repository root, drives every flow the moved components and hooks take part in. The packaged smoke test, after npm run build:once && CSC_IDENTITY_AUTO_DISCOVERY=false npm run pack:dir, proves the app boots from an archive without the sources: npm run test:e2e:packaged. It is red if build.files excludes only index.jsx; that was checked by putting the old pattern back.

Nothing on screen should differ, so a manual pass is a look for what the journeys do not reach:

Starting state: the app open on a site that has finished setup, with an edited file.

  1. Hover a disabled button, for example "Unlink" while a command runs in the terminal. The tooltip with the reason appears.
  2. Click "Review & submit changes". The diff is coloured, and the destinations are grouped in cards.
  3. Start the dev server and open the "Logs" tabs. Log lines are coloured by severity.
  4. Under the terminal, click one of the command links in the hints. The command is typed at the prompt and not run.
  5. Click "Open directory in". The list of editors on this machine appears.

What must not have happened:

  • No confirmation toast is missing after an action that shows one ("Updated to the latest trunk" after an update, "Patch saved to …" after saving a patch, "Local changes discarded." after a discard): they come through the context that moved.
  • The page still scrolls to the next step when it changes.

Risks and limitations

  • One journey failed once and could not be reproduced. In the first full run after rebasing onto Make the first-run screen translatable, with a pseudo-locale test #541, "a failed review reports an error instead of claiming there are no changes" failed. It passed in the next two full runs, in 25 isolated repeats and in 4 repeats of its whole file. The failure output was not kept, so its cause is unknown; nothing the test exercises changed in this PR.
  • npm test was not rerun on the last commit. It changes two comments, the README and two smoke-test assertions. The unit suites ran on the commit before it.
  • Checked on macOS only. Windows is covered by CI's journeys and packaged smoke. Checked on Windows also
  • The moved code keeps index.jsx's two-space indentation, where .editorconfig asks for tabs in new code, so that the diff reads as a move. .editorconfig steers editors and fails nothing.
  • Review: no findings; three style notes, applied.

Related

Closes #552

Part of #542. Follows #566.


Design decisions and alternatives considered

hooks/ beside components/, where the issue's title names components/ for both. The step that splits SiteRow's state (#554) will add a hook per domain, and they are not components.

Kebab-case file names, like every other file under src/, rather than the PascalCase.tsx of the prototype.

One pattern, !src/renderer/**/*.jsx, rather than a line per directory. A directory list has to be remembered the next time one is added; the extension is a rule a new file follows by being what it is. The .cjs modules beside them are not covered, deliberately: the main process requires some of them.

No rewrite on the way. formatEmailDate, the style constants and the labels at the top of index.jsx stay where they are; they are not components, and moving them is not what the issue asks.

Review outcome (required — see AGENTS.md)

0 [fix here] · 0 [follow-up] — no findings across the five dimensions.

The review checked the move mechanically: it stripped each new file's imports and export, found the rest as an exact substring of trunk's index.jsx, and diffed what remained against the branch's. All eleven files are byte-identical to trunk apart from the comment edits. It also confirmed no circular import, one shared ConfirmationContext, an unchanged order of style registration, that the packaging pattern matches all twelve .jsx and nothing that must ship, and that the translation extraction script still sees the new files.

Style notes, applied in the second commit: two comments in index.jsx still pointed at a note that had moved; the smoke test's directory checks were written through arrayContaining where toContain says it; the README named index.jsx alone as the bundle's source.

Style notes, left alone as unrelated to this change: .editorconfig and eslint.config.mjs say index.jsx uses four spaces, and the review standard still says "4000-line component".

  • Review: completed — fresh agent context, local, before the PR existed, read-only; head 1ab1520 / base 1f3808a; committed work only, working tree clean; outcome above. CodeRabbit: not run yet.
  • Since review: 1ab1520 → 4c98b4c checked. The difference is the three style notes. It was not reviewed again by a fresh context.

Checks, macOS, local:

Check On Result
eslint . --max-warnings=0 4c98b4c clean (run with a local agent worktree under .claude/worktrees/ ignored; it is not part of the repository)
npm test 1ab1520 1691 pass, 2 skipped, 0 fail
npm run test:electron 1ab1520 1693 pass, 0 fail
npm run test:e2e 4c98b4c 34 of 34 pass
npm run test:e2e:packaged 4c98b4c 15 of 15 pass
Implementation notes

How the move was checked before the review: every line that left index.jsx was looked up in the new files. Of 335, all but eight are there unchanged; the eight are five import lines and three comments, each rewritten.

@wordpress/i18n's extraction (scripts/generate-pot.cjs, from #541) walks src/ and picks up .jsx, so a string wrapped in a component later is found without a change there.

Screenshots or recording

Nothing on screen changed, and no images are attached. The twelve shots npm run shots captures were taken on trunk and on this branch with a two-second settle before each capture. Eleven are pixel-identical. The twelfth, gutenberg-site-view, differed once in its terminal, which is an intermittent rendering problem on trunk itself: eight captures of that shot on unchanged trunk code showed it twice, and eight on this branch not at all. It is being looked at separately.

With the harness's own 300ms settle, one shot differs from trunk by 14 corner pixels of a sidebar entry: a transition caught part-way, which is why the comparison was repeated with a longer settle.

🤖 Generated with Claude Code

zaerl and others added 2 commits September 30, 2026 15:40
… their own

Fifth step of the redesign's groundwork (#542, #552). index.jsx held
seven components that take props and nothing else, and five hooks,
between its imports and the two components that are most of the file.
They move, verbatim, to src/renderer/components/ and src/renderer/hooks/,
so the redesign can change one at a time and so what is left in
index.jsx is the part that still has to be taken apart.

Nothing is rewritten. Each function gains `export` and its file gains
the imports it needs; three comments that said "above" or "below" now
name the file they mean.

`build.files` excluded the bundle's one source, index.jsx. It excludes
every `.jsx` under src/renderer/ now: in that directory `.jsx` means
"bundled by esbuild", and the new files would otherwise ship beside the
bundle that already contains them. The hooks are `.jsx` for that reason,
although they hold no JSX. The packaged smoke test reads the same rule.

The review standard and TESTING.md said a decision made in index.jsx is
one the suite cannot reach. That is as true of a component or a hook
beside it, and they say so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…heck plainly

Notes from the review of the previous commit; it had no findings.

Two eslint-disable comments in index.jsx said "see the note on the
first console.error above". That note went to the hooks directory with
useDetectedEditors, and nothing above them holds one now.

The packaged smoke test asserted that `components` and `hooks` are not
in the archive through arrayContaining; toContain says the same thing.

The README named index.jsx as what the bundle is rebuilt from. It is
rebuilt from the components and hooks too.

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

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Advanced

Run ID: 058c422a-efc5-4c9c-ac10-05d6b317f637

📥 Commits

Reviewing files that changed from the base of the PR and between 1f3808a and 4c98b4c.

📒 Files selected for processing (17)
  • .github/instructions/code-review.instructions.md
  • README.md
  • TESTING.md
  • package.json
  • src/renderer/components/destination.jsx
  • src/renderer/components/diff-text.jsx
  • src/renderer/components/discard-changes-link.jsx
  • src/renderer/components/log-text.jsx
  • src/renderer/components/reasoned-button.jsx
  • src/renderer/components/terminal-command-link.jsx
  • src/renderer/hooks/use-confirmation.jsx
  • src/renderer/hooks/use-contributor-provenance.jsx
  • src/renderer/hooks/use-detected-editors.jsx
  • src/renderer/hooks/use-next-action-cue.jsx
  • src/renderer/hooks/use-sites.jsx
  • src/renderer/index.jsx
  • tests/e2e/packaged/smoke.spec.js

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The renderer’s seven UI components and five hooks are defined in dedicated JSX modules. The renderer entry point imports those modules. Electron packaging excludes all renderer JSX files, and packaged smoke checks verify that exclusion. README, TESTING.md, and review instructions now cover renderer components and hooks.

Assessment against linked issues

Objective Addressed Explanation
Move seven pure components and five hooks into src/renderer/components/ [#552] ❓ The seven components are under src/renderer/components/, but the five hooks are under the separate src/renderer/hooks/ directory.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 4c98b

No actionable merge-blocking issue is established. The renderer modularization and packaging changes appear consistent with the behavior-preserving objective and are mergeable subject to normal checks.

Security Architecture Review

Security architecture risk: ⚪ Minimal · up to 4c98b

The new files remain internal to the existing renderer bundle. The reviewed changes preserve action controls and shared-state ownership, while packaging excludes the uncompiled sources. No material security risk was found to be introduced or worsened.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The reviewed extraction retains the existing desktop renderer's effective exposure: the same bundle, callers, and window.api operations. The new exports do not establish additional service, tenant, credential, or environment authority.

Trust Boundaries and Controls

  • observed — ConfirmationContext is a post-action notification channel, not the destructive-action approval gate. Sensitive actions retain their existing confirmAnd calls and native window.confirm approval before action execution; rejection does not invoke the action callback.

Resilience and Maintainability Implications

  • observed — The same App-level notification queue remains above SiteRow consumers, preserving ownership across row unmounts. Existing duplicate suppression, burst limits, identifier-based removal, and explicit error dismissal remain unchanged rather than becoming new extraction-related recovery behavior.
🚥 Pre-merge checks | ✅ 3 | ❓ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The pull request title is not included, so its required action-based format cannot be verified. Provide the pull request title and verify that it follows the format '[Action] [what] [where or why]'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description includes the required rationale, changes, testing steps, risks, related issues, review outcome, implementation notes, and visual validation.
Linked Issues check ✅ Passed The description links the change to issue #552 and identifies related work in #542 and #566.
Out of Scope Changes check ✅ Passed The changes match the stated scope: component and hook extraction, packaging updates, tests, and documentation. Behavior changes, modals, and SiteRow state remain out of scope.
  • 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 commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@zaerl zaerl self-assigned this Sep 30, 2026
@zaerl
zaerl merged commit 2b6a69a into trunk Sep 30, 2026
8 checks passed
@zaerl
zaerl deleted the update/renderer-components branch September 30, 2026 14:13
zaerl added a commit that referenced this pull request Sep 30, 2026
## Why

`SiteRow` is about 5,000 lines and holds the state of everything a
site's view can do. The redesign in #542 needs its dialogs as components
it can restyle one at a time, and #553 asks for them to leave
`index.jsx` one per pull request. This is the first: the rename dialog,
chosen because nothing else in `SiteRow` reads its state.

## What changes

`src/renderer/components/rename-site-modal.jsx` is new. It holds what
was inline in `SiteRow`: the value being typed, the refusal, the busy
flag, two effects, the handlers and the markup. `SiteRow` keeps the one
thing that is its business, whether the dialog is open.

One thing is different in structure. **The component is mounted while
the dialog is open and not otherwise.** Opening used to reset the value
to the site's name and clear the last refusal by hand; a fresh mount
does both, so those two lines are gone rather than moved. Everything
else is the code that was there, with `onClose()` where it set
`SiteRow`'s flag.

**No journey drove this dialog**, so one does now. `rename-site.spec.js`
was written against the inline version first and passes on both: the
dialog opens on the site's name with it selected, refuses an empty one,
writes nothing when cancelled, and puts the saved name in the sidebar
and the heading. `siteHeading` joins the shared locators in `ui.cjs`,
now that two files use it.

Deliberately not in this PR: the other four dialogs, and the two things
listed under Risks that would change what the dialog does.

## How to test this

Platforms: any. Use the Buildkite build for the current head of this
branch.

The journey carries this: `npx playwright test --project=journeys -g
"rename dialog"`, from the repository root. It passes on `trunk`'s
inline dialog and on the component, and it is red when Save does not
rename, when an empty name is accepted, when Cancel saves what was
typed, and when the name is not selected on opening.

What it does not reach is the dialog while a rename is pending, which is
too brief to stage against a local store:

**Starting state:** the app open on any site.

1. Click the pencil beside the site's name. The "Rename site" dialog
opens with the name selected.
2. Type a new name and press Enter. The dialog closes and the name
changes in the sidebar and in the heading.
3. Open it again, clear the field, and click "Save". "Site name cannot
be empty." appears under the field and the dialog stays open.
4. Press Escape. The dialog closes and the name is unchanged.

**What must not have happened:**

- The name typed and then abandoned in step 4's dialog is not in the
field the next time it opens.
- No other site's name changed.

## Risks and limitations

- **A failed rename still closes the dialog.** `App`'s rename handler
catches every error, shows it in a native alert and resolves, so the
dialog's own place for a refusal can never be reached and what was typed
is lost. This is how `trunk` behaves; the move keeps it, and fixing it
changes behaviour.
- **The empty-name check is still in a `.jsx` file**, where the unit
suite cannot reach it. It was moved, not added. The rule in the review
standard says it belongs in a `.cjs` module with a test; the journey
covers it at layer 4 until then.
- **The journey costs one app launch** on every pull request, on both
platforms: about a second and a half locally on macOS.
- **Two of its assertions could not be shown to be load-bearing on their
own.** The saved name's surrounding spaces are removed by the main
process as well as the dialog, and what was typed is forgotten both by
the fresh mount and by an effect; breaking one of each pair leaves the
test green.
- **Checked on macOS only.** Windows is covered by CI.
- Review: 1 [fix here], fixed; 2 [follow-up], the first two items above.

## Related

Part of #553, which has four more dialogs. Part of #542. Follows #567.

---

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

**Mounted while open, rather than always mounted with an `isOpen`
prop.** An always-mounted component would have to reset its own state
each time it opens, from an effect, which is the code this removes.
Mounting it fresh is the reset.

**`SiteRow` still owns the open flag.** The button that opens the dialog
is in the site's header, which `SiteRow` renders, so the flag has to
live where both can reach it.

**The state keeps its names**, `renameValue`, `renameError`, `renaming`,
although inside a component called `RenameSiteModal` the prefix says
nothing. Keeping them makes the diff readable as a move.

**A journey of its own rather than a step in `engine.spec.js`.** That
file's restart test renames through the handler directly, deliberately,
because what it proves is the write reaching disk. The dialog is a
different claim.

</details>

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

**1 [fix here] · 2 [follow-up]** — the one fixed, the two recorded under
Risks.

- Tests, low, [fix here], fixed: TESTING.md and the helper's header
listed what `ui.cjs` holds as the shared controls and four locators, and
`siteHeading` is neither. Both say five.
- Architecture, low, [follow-up], deferred: the empty-name check and its
sentence are in JSX. Moved, not added; extracting it to a module changes
nothing a contributor sees but is a change to the code's shape beyond a
move.
- Architecture, low, [follow-up], deferred: `App.onRename` swallows a
failed write, so the dialog closes on failure. It predates this change,
and fixing it is a behaviour change.

The review went through every piece of state, effect, handler and prop
against `trunk` and found the behaviour the same, including a state
update after unmount on success, closing while a rename is pending, and
focus and selection on opening.

- **Review:** completed — fresh agent context, local, before the PR
existed, read-only; head b6c996d / base
2b6a69a; committed work only, working
tree clean; outcome above. CodeRabbit: not run yet.
- **Since review:** `b6c996d → 655a5ed` checked. The difference is the
fix and one comment in the component. It was not reviewed again by a
fresh context.

Checks, macOS, local:

| Check | On | Result |
|---|---|---|
| `eslint . --max-warnings=0` | 655a5ed | clean |
| unit suite on Node | b6c996d | 1691 pass, 2 skipped, 0 fail |
| unit suite on Electron's Node | b6c996d | 1693 pass, 0 fail |
| `npm run test:e2e` | b6c996d | 35 of 35 pass |
| the two journeys that use what the second commit touched | 655a5ed | 2
of 2 pass |
| `npm run test:e2e:packaged` | b6c996d | 15 of 15 pass |

Lint and the unit suites were run on this checkout's files only: a
worktree left by another local session sits under `.claude/worktrees/`,
and the bare commands collect it.

</details>

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@ryanwelcher ryanwelcher mentioned this pull request Oct 1, 2026
@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.

Move the 7 pure components and 5 hooks into src/renderer/components/

1 participant