Skip to content

Move the mail into the bottom tray, the last of the three - #615

Merged
zaerl merged 2 commits into
redesign/wpdsfrom
add/bottom-tray-mail
Oct 5, 2026
Merged

zaerl merged 2 commits into
redesign/wpdsfrom
add/bottom-tray-mail

Conversation

@zaerl

@zaerl zaerl commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Why

Third and last part of #558. The terminal (#612) and the logs (#613) are in the tray along the bottom of the window; the mail a site's WordPress sent was still a section at the foot of each site's page. This moves it into the tray, as the prototype has it.

What changes

A third button in the footer, "Toggle Email". The tray holds one thing at a time: the terminal, the logs or the mail.

The mail in the tray. The open site's list, newest first, filling the tray, and under it where the mail server is listening. A row opens its mail in the dialog, as before. The Mail section is gone from the page. Each site keeps its own list, as each keeps its own terminal and logs.

"Clear emails" is in the tray's heading, beside the button that closes it, and cannot be pressed while the list is empty. That is the prototype's, and it takes one thing away: the list is empty until the dev server has first run, whatever the app holds from before, so mail kept from an earlier session can be cleared only once the server has been started. Before, the button could always be pressed.

The logs take nothing's place when the app brings them up. They gave way to the terminal alone, where someone may be typing; with the mail open someone may be about to open one. They come up by themselves only in a tray that is closed or already showing them, and the page says where a failure's lines are, as it does under the terminal. The terminal still takes the tray whatever is in it.

The count on the Logs button starts at the footer's top edge and is one line tall, so that a wide one is clear of the envelope beside it, and is drawn over the buttons.

Labels follow the prototype: the tray is "Email", and an empty list says "No mail yet. WordPress transactional email will appear here once the site sends any."

Not in this pull request:

  • A sign that mail has arrived while the tray is closed or showing something else. On the page the list was always in view; now such a mail shows nowhere until the tray is opened. Left out on purpose, by the maintainer's decision.
  • The dialog a mail opens in. Mail HTML rendering is unchanged and tracked separately.
  • The user guide and the README, which still describe a Mail panel on the page: Retake all screenshots and update guide text #561.

How to test this

Platforms: any. The journeys drive this on macOS and Windows. From the repository root, after npm ci and npm run build:once:

npx playwright test --project=journeys mail.spec tray.spec site-header.spec
  • mail.spec.js: the mail is in the Email tray, named and headed for it, and with the tray put away no row is on screen; a row opens the mail that was clicked; mail that arrives joins the list for its own site only; "Clear emails" is disabled with nothing to clear, empties the list and what the app keeps, and is disabled again.
  • tray.spec.js: each of the three footer buttons puts its own in the tray in the others' place; with two sites the heading has one "Clear emails"; a build that fails with its output in the logs leaves an open Email tray where it is, and the page says where the lines are.
  • site-header.spec.js: two journeys used the Mail section as the end of the page and now give the page its height with the tray.

I broke seven behaviours of the mail tray one at a time and each turned a journey red, and the details' two rules (always let go, never let go) at three window sizes. tests/unit/site-mail.test.cjs and tests/unit/tray.test.cjs hold the decisions.

Worth a look by hand (any platform, the current head; I have not done this, the journeys stand in for the server): on a site that is set up, press Start development server, then press the envelope in the footer. The tray says where SMTP is listening. Make WordPress send a mail, for example Lost your password? on the site's login page: the mail is in the list. Press its row: the dialog opens. Close it and press Clear emails: the list is empty and the button cannot be pressed.

What must not have happened: a site's mail in another site's tray; the list losing mail when the tray is closed, shows something else, or another site is opened; the logs coming up over an open mail list; anything on the page ending up under the tray.

What cannot be tested by hand here: I had no real server to send a mail through. The journeys say what the main process says when one is caught, on the channels it says it on.

Risks and limitations

  • Review: 2 passes; 10 findings fixed here, 2 settled by the maintainer's decisions (below), 4 left as follow-ups. Details below.
  • Mail that arrives unseen shows nowhere until the tray is opened. See "Not in this pull request".
  • Mail kept from an earlier session cannot be cleared until the server has run once. See "What changes".
  • Focus drops to the page when the app itself puts the terminal in place of the mail while the focus is in the mail's tray. The logs and the terminal already behave this way when one replaces the other.
  • A row's hover is faint on the list's ground, the same weak surface the logs are on.
  • A clear that fails says nothing, as before.
  • toasts.spec.js:52 failed once in a full local run with "Cannot fast-forward to the past" and passes on its own; it is the third time I have seen it, it is not from this change, and it is flagged separately.

Related

Part of #558, and the last of its three. Follows #614. Mail HTML rendering is unchanged and tracked separately.


Design decisions and alternatives considered
  • What the prototype lacks is kept and redrawn. Its tray has a title, "Clear emails" and a placeholder text: no list, no line for the mail server, no dialog. The app has all three and keeps them, in the tray.
  • A slot in the tray's heading, filled by the open site while the mail is what the tray shows, for what is done to the whole of what the tray holds. The tray's heading is the window's, and the list is a site's.
  • The dialog is left exactly as it is, old components and all, so that this change and the one that redraws how a mail is shown do not meet in the same file.
  • The logs give way to everything, not only to the terminal. One rule for "the app asks for the logs" that does not have to know what each tray is for: they never take a tray from under someone.
  • The count stays the design system's badge, one line tall by losing its vertical padding, and not a smaller drawing of my own.
Review outcome (required — see AGENTS.md)

Pass 1: 7 [fix here] · 5 [follow-up]. Pass 2 (on the fix-up): 3 [fix here] · 1 [follow-up]. 10 fixed here (7 and 3, one of pass 1's being a follow-up applied here), 2 settled by the maintainer's decisions, 4 left as follow-ups.

  • Review: completed — a separate agent context, read-only; head b9b7daa / base 351d34b; lint and the two unit files run, the journeys read and not run; 7 [fix here], 5 [follow-up].
    • Fixed: the check that the mail is nowhere but the tray ran before the list had loaded and could not fail; the details journey guarded on a margin of my own and not on the app's boundary, and read it before the page was at its top; the count on the Logs button was drawn under the next button when that was pressed, and its comment was wrong; the step that leaves an open mail list where it is did not read that the page says where the lines are; one journey needed a page longer than the room above a tray at its first height; the check that the tray covers nothing read the last card and not what ends the page.
    • Applied here though marked follow-up: who a mail is from now gives way before its subject in a narrow window.
    • Decided by the maintainer: "Clear emails" stays disabled on an empty list, as the prototype has it (one [fix here], recorded above as what it takes away); no sign for mail that arrives unseen (one [follow-up], not wanted).
    • Follow-ups left: focus when the app swaps the tray from under it; the row's faint hover; a failed clear that says nothing.
  • Review: completed — a second separate agent context, read-only; b9b7daa plus the fix-up as staged, before it could be committed (it is 09ab94d), base 351d34b; lint and the two unit files run, each fix-up traced, every journey that depends on the page's length listed; 3 [fix here], 1 [follow-up].
    • Fixed: a rule and its comment left from the heading the mail had on the page; a comment that said who a mail is from gives way first, which the rules did not do (they do now); a comment in the details journey that did not say what its loop does.
    • Follow-up left: a comment in the mail dialog's file still says the list is under "Mail". The dialog is not touched here.
    • Nothing found: the seven fix-ups, the journeys' absences, the other journeys against a shorter page, the screenshot script.
  • Since review: b9b7daa → 09ab94d is the two passes' fixes; the second pass's three are not reviewed again, and are a deleted rule, one flex value and comments. Lint, npm test, npm run test:electron, all 113 journeys, the affected journeys at three smaller window heights and with a slowed status read, and the packaged smoke were run on the tree that became 09ab94d, the journey suite before the second pass's three fixes and the affected journeys after them.
Implementation notes
  • New: src/renderer/site-mail.cjs (smtpNote, noMailNote, mailRow), src/renderer/components/mail-panel.jsx (MailPanel, MailTrayActions), tests/unit/site-mail.test.cjs.
  • src/renderer/components/bottom-tray.jsx: SiteTrayActionsFill and its slot in the heading. src/renderer/tray.cjs: the third tray and trayAfterReveal's rule.
  • A mail's row is a native button in a list item, where it was a div with a role and key handlers. Who a mail is from now gives way before its subject does in a narrow window.
  • scripts/screenshots/shots.cjs: the mail's shot is taken of the tray. I ran that one shot to check the script and did not commit the image; Retake all screenshots and update guide text #561 retakes them all.
  • The details journey reads both of its halves part of the way down the page, where a column that is held is not yet pushed by the end of the cards, and guards on the app's own boundary: the details fit until they are taller than the page, which is 24px less than nothing to spare under them.
  • Local checks on macOS: npm run lint clean, npm test and npm run test:electron pass (1948 tests), npm run test:e2e 113 pass, npm run test:e2e:packaged 15 pass.
Screenshots or recording

Not attached: I have no way to upload images from the command line. The steps under "Worth a look by hand" show everything that changed.

🤖 Generated with Claude Code

zaerl and others added 2 commits October 5, 2026 10:18
The third and last of what the tray holds (#558). The footer has a button
for it, "Toggle Email", and the tray shows the open site's mail: the list,
newest first, and under it where the mail server is listening. "Clear
emails" is in the tray's heading, beside the button that closes it, and
cannot be pressed while there is nothing to clear. A row opens its mail in
the dialog, as before. The Mail section is gone from the page.

The logs now take nothing's place when the app brings them up, where they
gave way to the terminal alone: someone may be about to open a mail.

The count on the Logs button sits in the room the footer leaves above its
buttons, so that a wide one is clear of the envelope beside it.

Three journeys used the Mail section as the end of the page. With it gone
a page is not much taller than a window, and they read what they were
reading part of the way down the page, with the tray giving the page its
height.

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

From the two reviews. The check that the mail is nowhere but the tray ran
before the list had loaded, and could not fail: it now sees a row in the
tray, puts the tray away, and reads that no row is on screen. The details
journey guards on the app's own boundary, read with the page at its top.
The journey that needs a page longer than a window puts the tray at its
largest first. The check that the tray covers nothing of the page reads
the foot of the details as well as the last card. The step that leaves an
open mail list where it is reads that the page says where the failure's
lines are.

The count on the Logs button is drawn over the buttons: the next one's
ground, when it is pressed, covered it. Who a mail is from gives way long
before its subject does in a narrow window. The rule for the heading the
mail had on the page is gone with the heading.

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

coderabbitai Bot commented Oct 5, 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: 9eae2c24-69c8-49a3-8fa5-6825e0f9e867

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

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 self-assigned this Oct 5, 2026
@zaerl
zaerl merged commit 548c020 into redesign/wpds Oct 5, 2026
8 checks passed
@zaerl
zaerl deleted the add/bottom-tray-mail branch October 5, 2026 10:27
zaerl added a commit that referenced this pull request Oct 5, 2026
## Why

#542 replaces the app's interface with the one in the
[prototype](https://github.com/jameskoster/wordpress-contributor-toolkit-prototype),
drawn with the WordPress Design System. It was built in slices on
`redesign/wpds`, so that trunk, which releases are cut from, never held
a half-redesigned window. The window is whole now: the shell, the page
header and details, every card and dialog, and the tray. This pull
request is how the branch reaches trunk.

It lands before the last three sub-issues, on purpose. The settings
dialog (#559) and dark mode (#560) add to a finished window and do not
need a branch to hide in, and the guide (#561) is easier to rewrite
against what is on trunk. They follow as pull requests against trunk.

## What changes

Everything `redesign/wpds` holds; this pull request adds nothing of its
own. Each slice was reviewed in its own pull request against that
branch, and says there how to test it and what it leaves out.

- **The shell** (#555): a sites list, the page area and a footer, in a
larger window. #590
- **The page header and the details** (#556): the site's menu in the
header, and what the checkout is in a sidebar that can be put away.
#594
- **The site's page** (#557): the server and the build watch in the
header and the details
(#595); the ticket
card, the apply card with a preview dialog, and the list of a site's
tickets (#596,
#597,
#599); the
create-site, rename and delete dialogs
(#600,
#601);
confirmations as notices in the window's corner
(#602); the page's
notices, update card and setup checklist
(#605); the
terminal, logs, mail and the review dialogs painted with the design
system's tokens
(#606,
#608); and lint
rules that keep it so
(#609,
#610).
- **The tray** (#558): along the bottom of the window, holding the
terminal, the logs or the mail, with a count of unseen debug.log lines
on the footer
(#612,
#613,
#614,
#615).
- Along the way:
#611 (the site
menu's update item says why it is held), and three that tidy up before
landing: #616 (a
journey's race with the page's clock),
#617 (comments the
finished redesign made untrue) and
#618 (the server's
button by its present name in `AGENTS.md`).

**What a contributor will notice first:** the terminal, the logs and the
mail are no longer on the page but behind three buttons at the foot of
the window; the terminal is dark text on light; the server and the build
watch are started from the header; a patch is previewed in a dialog
before it is applied; and what worked is said in the window's corner.

**Not in this pull request:**

- The settings dialog (#559). Until it, nothing about how the window is
arranged is remembered across restarts: the details, the tray's height,
the apply card's fold.
- Dark mode (#560). The terminal reads its colours once, when it is
made, and will need to follow the theme.
- The user guide and its screenshots (#561), which describe the old
interface. They are published from release tags, so they are not wrong
until a release ships this; #561 is next, ahead of #559 and #560, for
that reason.
- How a mail is drawn, and how the window handles a link. Both are
unchanged and tracked separately.

## How to test this

Platforms: macOS and Windows, both. Buildkite builds signed artifacts
for this pull request; use the build for its current head, and check
that it is: a push to the branch makes earlier builds stale.

The suite, from the repository root, after `npm ci`:

```
npm run lint
npm test
npm run test:e2e
```

113 journeys walk every slice on macOS and Windows on each push. What
they do not do is run a real site: a clone of `wordpress-develop`, an
install, a build and a server are stood in for. The one test that does,
`real-setup.yml`, is run by hand and was run on this branch at `548c020`
(https://github.com/WordPress/contributor-toolkit/actions/runs/37298667056):
a Core site on macOS and on Windows and a Gutenberg site on macOS were
created, installed, built and served, and passed. **The Gutenberg site
on Windows did not finish.** Its first attempt was cancelled after 74
minutes inside the journey, a minute short of the limit that would have
failed it with evidence, so it says nothing about where it was; a second
attempt was still running when this was written and its result is not in
this description. The commits since `548c020` change a journey, comments
and prose.

**A pass by hand, on a real machine** (any platform; Apple Silicon for
the server, which no test on a pull request starts for real). It follows
the order a contributor meets things in:

1. Open the app with no sites. **Create new site**, a Core site, in a
folder with a space in its path. The setup checklist runs its steps; the
terminal's button at the foot of the window shows what each is doing.
2. Let the checklist's last step start the server, and finish
WordPress's own setup in the browser. Back in the app the header says
"Server running" and "Build watching". Stop the server and start it
again: from **Server running** in the header, or with **Stop** and
**Start** beside "Server" in the details.
3. Press the three buttons at the left of the footer in turn. Terminal:
type `help`. Logs: the server's lines, the build watch's, and Debug.log.
Email: where SMTP is listening. Drag the tray's top edge.
4. Link a ticket by its number. The card shows the ticket and its pull
requests and patches.
5. Apply one of them. The preview dialog names the files; **Apply and
rebuild** runs the steps on the card.
6. Edit a file under `src/`, then **Review & submit changes**. The diff
is the edit and nothing else.
7. Create a second site, or open one from before, and move between the
two from the sites list. Each has its own terminal, logs and mail.
8. **Update to latest trunk** from the site's menu.

**What must not have happened:** work lost in a link, an apply, a switch
or an update that did not say so; `node_modules` rebuilt where nothing
changed; a failure said only in a tray that stayed closed; one site's
output in another's tray; anything on the page under the tray.

**What cannot be tested by hand, and what was not:** none of the slices'
manual steps were run by a person. I looked at screenshots and drove the
app through the journeys; everything local was on macOS, with Windows
left to CI. This pass is the first one by hand.

## Risks and limitations

- **It is one commit on trunk**, since the repository allows only squash
merges. The slices' pull requests are its record. `redesign/wpds` should
not be branched from after this: it will have diverged from trunk.
- **Reviewed in slices, not as a whole.** Twenty-two pull requests, all
but a two-word change to `AGENTS.md` reviewed by a separate reviewer
context before they were opened, most in two passes; see below. The
branch was then swept once for what falls between slices, which found
comments and one unread field
(#617) and the
three points that follow.
- **Three questions and two failures still use the system's own
dialogs**: the discards ask through its confirm box, and a path that
could not be copied and a server started before there is a build are
said in its alert. #557 meant to replace them and did so for deletions
only.
- **Some dialogs are repainted, not rebuilt.** The review dialog and its
destinations, the mail dialog and the question an update asks about
loose edits have the design system's colours and type on the old
component library's parts.
- **A real Gutenberg setup on Windows is not shown to pass on this
branch.** See "How to test this". It passed on trunk before the
redesign, in 35 minutes; here it was past 46 on its second attempt.
Whether that is the runner or the branch is not known.
- **The guide describes the old interface** until #561. Do not cut a
release before it.
- **The bundle roughly doubled**
(#590): the sites
list brings `DataViews` and what it needs. Launch time was not measured.
- **Less is always in view.** A build's output, a new line in debug.log
and a caught mail are in a tray that may be closed. A failure opens the
terminal, or the logs if nothing else is open; unseen debug.log lines
are counted on the footer; a mail that arrives shows nowhere until its
tray is opened, which was decided and is not an oversight.
- **Translation is about half done.** The shell, the cards, the tray and
most dialogs go through the translator. Roughly 200 strings in
components and hooks do not yet (the review dialog and its destinations,
the setup checklist, the logs, most of what the hooks say), nor about
150 in the modules they take sentences from. Strings that are new are in
English in the shipped translations until they are translated.
- **Focus is lost in a few places**: when the app swaps what the tray
holds from under it, when a notice with a focused button goes, when a
row's own action removes the row.
- **Some failures are still worded by the main process** ("Error
invoking remote method…"): a failed rename, a failed setup.
- **Things carried over, not made by this:** a start that throws leaves
the server's button dead until a restart; erasing typed input in the
terminal breaks once the line wraps; a line written to debug.log while
it is being attached to can be shown twice.
- **#571 and
#603 will conflict
with trunk after this**, in the mail's journey and helpers and in the
dev server's hook.
- Each slice's own list has more, and says which of its limits were
chosen.

## Related

Closes #557. Closes #558. Part of #542, which stays open for #559, #560
and #561. #555 and #556 are closed already.

---

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

- **An integration branch, and one landing.** A window drawn half by the
old view and half by the new one could not be released, and the slices
were too many to hold back one by one behind a flag. Fixes and changes
with no visible effect went to trunk first and were merged across, so
this diff is the redesign and little else.
- **Landing before #559 to #561.** The plan was to land when all seven
sub-issues were in the branch. The remaining three do not change what is
there: two add to it and one documents it. Holding a finished window on
a branch for them only lengthens the time trunk and the branch can drift
apart.
- **What the prototype lacks is kept and redrawn**, and what it has
somewhere else moves to where it puts it. Its labels are adopted. Where
a slice departs from it, the slice says so.

</details>

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

**No review of the whole diff was run. The record is the slices':
twenty-two pull requests into `redesign/wpds`, each but one reviewed
before it was opened by a separate, read-only reviewer context following
`.github/instructions/code-review.instructions.md`, most in two passes
(the change, then its fix-up), with the counts and the findings in each
pull request's own review outcome. The one is
#618, two words in
a line of prose.**

- **Review:** completed, per slice —
#590,
#594 to
#602,
#605,
#606,
#608 to
#617 (no review was
run for #618);
everything marked to fix in a slice was fixed in that slice, and what
was left is in its Risks.
- **Review:** completed, for leftovers only — a separate agent context,
read-only; `redesign/wpds` at `548c020` against trunk; not a review of
correctness, which the slices had: a search for dead CSS and code,
comments that waited for a slice that has landed, what is left of the
old component library and of the system's dialogs, strings not yet
translatable, and repository docs that name the branch. Nothing found in
the stylesheet, the dependencies or the docs; thirteen comments and one
unread field, fixed in
#617; the rest is
in Risks above.
- **CodeRabbit:** not run. Its check reads "Review skipped: automatic
reviews are disabled" on this pull request and on the slices, so the
slices' pre-PR reviews are the reviews of record, each for the head it
names.
- **Since review:** nothing was added to the branch outside a reviewed
slice, apart from three merges of trunk (`da19ac6`, `3344172`,
`23d46e7`).

</details>

<details>
<summary>Implementation notes</summary>

- `git diff origin/trunk...origin/redesign/wpds`: 148 files, about
14,800 lines added and 3,000 removed; about 6,300 of the added lines are
tests.
- The site's view, which was one component of five thousand lines, is
spread over the four regions the layout has: the header, the cards, the
details and the tray. Decisions are in `src/renderer/*.cjs` with unit
tests; components draw them.
- New for the suite: journeys per region under `tests/e2e/journeys/`,
lint rules of the repository's own (`eslint-rules/`), and Stylelint over
the window's stylesheet.
- The main process changed in one place for the redesign's sake: the
debug.log tail says which lines the file already held
(#614).

</details>

<details>
<summary>Screenshots or recording</summary>

Not attached: I have no way to upload images from the command line. The
prototype shows the design; the signed build shows the app.

</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