Skip to content

Draw the list of a site's tickets with the design system - #599

Merged
zaerl merged 4 commits into
redesign/wpdsfrom
add/ticket-list
Oct 3, 2026
Merged

zaerl merged 4 commits into
redesign/wpdsfrom
add/ticket-list

Conversation

@zaerl

@zaerl zaerl commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Why

Part of the redesign in #542, item 5 of #557: the list of a site's tickets. It was the last card in the site's view still drawn by hand, with fixed colours and English written into the markup.

The prototype has no such list anywhere: its site holds one ticket. So nothing is taken from it here and nothing is taken away. The list stays where it is, does what it did, and is redrawn.

What changes

The list is a component of its own, TicketListCard, drawn with the design system: a card that is a named region, with one row for each ticket.

  • A row says the ticket's number and when it was last worked on, and has the same two actions as before: go back to it, and delete its work.
  • Going back is Switch while a ticket is linked and Continue working while none is. The sentence "You also have work on #N — switch" is gone; the card's heading, "Other tickets on this site", says it once for all rows.
  • Deleting asks the same question first, and the row being deleted shows it is busy.
  • A held button says why, as before, through the same button the ticket card uses.
  • Each button is named for its row: the words it shows and then the ticket's number, "Switch #60001". Before, every row's delete button had one name.
  • The card's words are translatable, in whole sentences for a ticket and for an issue. They are in ticket-branch-list.cjs, where the rows were already decided.

Not in this pull request:

  • The confirmation is still the system's own dialog. A dialog of the design system comes with item 7 of Server section and process menus, ticket card, apply patch and others #557, the rename and delete dialogs.
  • The delete button is not red. The design system's button has no destructive tone, and the other cards draw theirs the same way.
  • What the ticket card says back after a switch (the refusal, the progress line, the question about loose edits) is not redrawn here.

How to test this

Platforms: any. The change is in the renderer only.

The journeys walk the list on both platforms. From the repository's root:

npm run build:once && npx playwright test --project=journeys ticket-list ticket-branches i18n

ticket-list.spec.js is new, five journeys: what the card lists and what its buttons are called, in both states; the buttons held with their reason while the site builds; saying no to the question; the row that is being deleted; and the same card on a Gutenberg site. i18n.spec.js gains a scan of the card in the pseudo-locale. Each claim was checked by breaking it: 31 breaks, all red.

What a journey cannot judge is how it looks. By hand, on the current head, on either platform:

Starting state: a Core site that is set up, with no ticket linked.

  1. In Trac ticket, link 60001, then Unlink, then link 60002.
    • Below Apply a patch or PR there is a card headed Other tickets on this site, with one row: #60001, "Edited just now", Switch and Delete this ticket's work.
  2. Narrow the window until the row's buttons no longer fit beside the number.
    • They move under it. Nothing is cut off and nothing scrolls sideways.
  3. Type npm run build in the terminal and, while it runs, put the pointer on Switch.
    • The button is greyed and a tooltip says "Wait for the build to finish."
  4. When the build has ended, click Unlink.
    • The card is headed Your tickets on this site and has two rows, #60002 first. Each offers Continue working.
  5. Click Delete this ticket's work on #60001 and answer Cancel.
    • The row is still there.
  6. Click it again and answer OK.
    • The row goes. #60002 is still listed.

What must not have happened:

  • Step 5 deleted nothing: ticket/60001 is still a branch of the site.
  • Step 6 deleted only what was asked for: ticket/60002 is still a branch, and the site is on trunk.
  • A site with no parked tickets shows no card at all, not an empty one.

Risks and limitations

  • Review: two passes by a separate context. 3 to fix here on the first, all fixed; none on the second. Details below.
  • Windows failed once on this pull request, and the cause was this change. The journeys know a switch has ended by the linked ticket's number appearing, and looked for it by its text anywhere on the page. The redrawn row shows a parked ticket's number as text of its own, so ticket-rebase.spec.js was answered by the row it had just pressed and asked for a trunk update while the switch still held the terminal. macOS was fast enough to hide it. The helper now looks for the number in the card's heading, a journey pins that a row does not answer it, and ticket-rebase waits for the terminal to be free. Reproduced, and checked fixed, by slowing the site's status read by a second: the five journey files that use the list pass that way, 20 of 20. Otherwise checked on macOS only.
  • The manual steps above were not run by a person. I looked at screenshots of both states and of a narrow window.
  • The reason on a held button is not translated yet. It comes from ticket-actions.cjs, which the ticket card shares, and it still puts the noun into the sentence. The pseudo-locale scan does not see it, because nothing is held while it scans.
  • Focus falls to the page when a row's own action removes the row: after a delete, and after a switch makes the row's ticket the linked one. It did before this change too.
  • A reasoned button's id now goes to its tooltip's trigger. This touches the button the ticket card and the apply card use. Neither passes an id, so nothing changes for them; their journeys pass.
  • The guide still describes the old row, and its screenshot shows it: docs/guide/ticket-branches.md. That is Retake all screenshots and update guide text #561.
  • "Edited on" a date still writes the date the way the computer does, not the way the app's language does.

Related

Part of #557. Part of #542. Follows #597.


Design decisions and alternatives considered
  • Short words on the buttons, the number in the name. "Continue working on #60001" on every row repeats the number the row already shows. The button shows what it does, and its name is that and then the row's number, by aria-labelledby, so that the name begins with what is seen in any language. The ticket card names its two Refresh buttons the same way.
  • Two words for going back, as before. With nothing linked a row is where work starts again; with a ticket linked it is a change of ticket. The app made that difference before and it is kept.
  • A flat list with a rule between rows, not the tree the ticket card draws for pull requests: those hang off a ticket, these are peers.
  • The ticket's number is not a link. A way out to Trac from each row would be new, and this pull request adds nothing the list did not do.
  • provider in place of noun. The module was handed "ticket" or "issue" and put it into a sentence. It now picks a whole sentence, which is what a translator needs.
Review outcome (required — see AGENTS.md)

3 [fix here] · 3 [follow-up] on the first pass, all 3 fixed. 0 · 0 on the second.

  • Review: completed — a separate agent context, against .github/instructions/code-review.instructions.md; first pass on the staged change over base 74f3c231272d226a410d6d0d0309938c24a48f5d (it became 6bd89d1), reading the diff and running lint and the list's unit tests, not the journeys. Fixed:
    1. A row button's id was put on the button while its tooltip kept the trigger under an id of its own. It behaved when I ran it (hover, focus, Escape, the same as Unlink), and leaned on the library's insides to. The id is the trigger's now.
    2. The journey step "a held button does nothing when pressed" read the branch a round trip later, which a switch that had started would also have passed. It now reads what the page would say of a press that got through, and goes red when one does.
    3. "Switch" and "Deleting" had nothing to tell a translator what they are.
    • Also taken: the journeys' helper asks the module for a row button's words.
    • Follow-ups, none introduced here: the untranslated reason, the guide, and the lost focus, all in Risks above.
  • Second pass: completed — a separate agent context; head 4a4637cbea2329fcc9bcef927a591d160b032d02, base 74f3c231272d226a410d6d0d0309938c24a48f5d; no findings, three notes on wording.
  • Since review: 4a4637c → e091bb4: three wordings from the second pass, in two comments and one translators' note. e091bb4 → 96a120e2875254a7a4516c137b297cfdaeba59fb: journeys only, the fix for the Windows failure described in Risks (tests/e2e/helpers/ui.cjs, ticket-rebase.spec.js, one assertion in ticket-list.spec.js). No source under src/ changed since the second pass. Not reviewed again; lint and all journeys were run on it, and the new assertion goes red with the old helper.

Checks on 96a120e2875254a7a4516c137b297cfdaeba59fb, macOS (the unit suites and the packaged smoke on e091bb4; only journeys changed since):

Check Result
npx eslint . --max-warnings=0 clean
npm test 1830 pass, 2 skipped
npm run test:electron 1831 pass, 1 skipped
npm run test:e2e 90 of 90
npm run test:e2e:packaged 15 of 15, on an unsigned build
Eight journey files in a 1024×728 window 36 of 36
Implementation notes
  • src/renderer/components/ticket-list.jsx (new): TicketListCard and its row. It draws what it is given.
  • src/renderer/ticket-branch-list.cjs: ticketListCard returns the card's words (heading, action, remove, removing) or null for no card; deleteWorkQuestion; a row carries its number; "Edited 2 days ago" through _n.
  • src/renderer/index.jsx: the inline rows are gone.
  • src/renderer/components/reasoned-button.jsx: id to the tooltip's trigger.
  • tests/e2e/helpers/ui.cjs: ticketListCard, deleteWorkButton; switchBackButton, continueWorkingButton and ticketRow find a row by role inside the card, and no longer by the shape of the markup.
  • The list's journeys answer for GitHub and Trac themselves. Reading a ticket's attachments opens a window of its own, and a journey that speaks to the app's window by position would find that one.
Screenshots or recording

Not attached: I have no way to upload an image from here. What the screenshots I took show:

  • A ticket linked: a card headed "Other tickets on this site"; each row has the number in bold with "Edited just now" under it in grey, and to the right an outlined Switch and a plain Delete this ticket's work; a thin rule between rows.
  • None linked: the same card headed "Your tickets on this site", three rows, each with an outlined Continue working.
  • A narrow window: each row's two buttons sit under its number.

🤖 Generated with Claude Code

zaerl and others added 3 commits October 3, 2026 09:25
The prototype has no list of a site's parked tickets: its site holds one
ticket. The app's list is kept as it is, in the same place, and redrawn
(#557): a card that is a named region, one row a ticket, with the ticket's
number, when it was last worked on, and the same two actions, going back
to it and deleting its work behind the same question.

A row's buttons say what they do and are named for their row, the words
they show and then the ticket's number. A held button says why through the
design system's button, as the ticket card's do.

What the card says is in ticket-branch-list.cjs, translated, in whole
sentences for a ticket and for an issue.

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

A reasoned button given an id put it on the button, while the tooltip knew
its trigger by an id of its own. It behaved, and leaned on the library's
insides to; the id is the trigger's now.

The journey that says a held row button does nothing when pressed read the
branch a round trip later, which a switch that had started would also have
passed. It now reads what the page would say of a press that got through.

"Switch" and "Deleting" say what they are to a translator, and the
journeys' helper asks the module for a row button's words.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three wordings from the second review: the translators' comments for
"Switch" and "Deleting" name an issue as well as a ticket, the note on a
reasoned button's id says the earlier arrangement worked, and the journey's
comment says which of its checks the app can fail today.

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

coderabbitai Bot commented Oct 3, 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: dcdbe2de-9a89-43a3-b273-611017a6a73b

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.

…list

The journeys know a switch has ended by the ticket's number appearing as
the card's subject, and found it by its text anywhere on the page. The
redrawn list shows each parked ticket's number as text of its own, so a
journey that pressed a row was answered by that row, at once, and went on
while the switch still held the terminal. On macOS the switch was over
first; on the Windows runner the update of trunk that ticket-rebase asks
for next was turned away, every time.

The number is looked for in a heading, where the card says it, and a
journey pins that a row's number does not answer. ticket-rebase also waits
for the terminal to be given back before it asks for the update, which is a
moment after the card names the ticket.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@zaerl
zaerl merged commit 1319e26 into redesign/wpds Oct 3, 2026
8 checks passed
@zaerl
zaerl deleted the add/ticket-list branch October 3, 2026 08:08
zaerl added a commit that referenced this pull request Oct 3, 2026
…has it (#600)

## Why

Part of the redesign in #542, item 6 of #557: the create-site dialog. It
was the older component library's modal, with its colours written into
the markup.

## What changes

The dialog is the design system's, laid out as the prototype has it:

- **Title and close:** "Create site", with a close button. Escape and a
press outside close it too. The **Cancel** button is gone, as in the
prototype.
- **Project:** a segmented choice of two, labelled "Project", where
there was a radio list labelled "Contribute to". Under it is what the
chosen project is, which each radio option used to say of itself, and "A
site's project cannot be changed later."
- **Location:** a field of its own, with the folder that was chosen
under it.
- **A missing answer** is said as an alert, so it is announced when it
appears.

What it does is what it did:

- It refuses a missing name or folder, one at a time, and starts nothing
while one is missing.
- It opens empty, on the name, with Core chosen, however the last one
was left.
- It sends the setup what was chosen and closes; the setup goes on on
the site's page.
- Opened while a setup runs (from the notice on a site the old engine
made), it is inert and stays until the setup has ended.

Its decisions are in `create-site.cjs`, with unit tests: the projects it
offers, what it says of the chosen one, and which answer is missing.

Not in this pull request, by decision:

- **Setup progress inside the dialog.** The prototype keeps the dialog
open with a spinner through download, install and compile, and has no
setup checklist. Here the dialog still closes and the site's page runs
the checklist, with its retry buttons and its log. Moving the setup into
the dialog is its own change.
- **A default name.** The prototype creates an unnamed site as "My
WordPress site". An empty name is still refused.
- **A default location.** The prototype fills the folder from its
settings, which are sub-issue #559.

## How to test this

Platforms: any. The change is in the renderer only.

The journey walks the dialog on both platforms. From the repository's
root:

```bash
npm run build:once && npx playwright test --project=journeys create-site.spec legacy-site.spec i18n.spec
```

`create-site.spec.js` covers what the dialog refuses, that it starts
clean each time (also when opened again while it fades), what it sends,
and how it stands while a setup runs and when one fails. Each claim was
checked by breaking it: 28 breaks, all red.

What a journey cannot reach is the system's folder dialog and a real
setup. By hand, on the current head, on either platform:

**Starting state:** the app with at least one site, so that the sites
list is showing.

1. Click **Create new site**.
- A small dialog titled **Create site** opens, with the cursor in **Site
name**, **WordPress Core** chosen, and "No folder selected yet." under
**Location**.
2. Click **Gutenberg**.
- The lines under the choice change to describe the block editor, and
still end with "A site's project cannot be changed later."
3. Click **Create site** with the name empty.
- A red notice says "Please provide a site name." Nothing else happens.
4. Type a name and press Enter.
   - The notice now says "Please choose where to create the site."
5. Click the file button under **Location** and choose a folder in the
system's dialog.
   - The folder's path appears under the field and the notice goes.
6. Press Escape, then click **Create new site** again.
   - The dialog is empty again, on WordPress Core.
7. Fill it in and click **Create site**.
- The dialog closes and the new site's page shows the setup running, as
before this change.

**What must not have happened:**

- Steps 3 and 4 started nothing: no new entry in the sites list, no
folder created.
- Step 6 kept nothing: not the name, the project, or the folder.
- The site created in step 7 is in a new subfolder of the folder chosen,
named after the site, and is a checkout of the project chosen.

## Risks and limitations

- **Review:** two passes by a separate context. 2 to fix here on the
first, both in the journey, both fixed; none on the second. Details
below.
- **A lint rule is switched off on one import.** The segmented control
is exported by `@wordpress/components` only under an `__experimental`
name, which `@wordpress/no-unsafe-wp-apis` forbids. The design system
has no other segmented control and documents this one under that name,
and the prototype uses it.
- **Checked on macOS only**, in a 1024×728 window and with the site's
status read slowed by a second as well as at the default size. Windows
is CI's.
- **The manual steps above were not run by a person.** I looked at
screenshots of the dialog empty, filled and with a complaint.
- **The field for the folder is the browser's file input**, as before
and as in the prototype. Its button and "No file chosen" are the
system's words, in the system's language, and it never shows a
selection: the folder is said under it.
- **A dialog opened during a setup cannot be closed until the setup
ends.** That is how it was, and the journey marks it as a
characterisation. It has no close button then, where the old one showed
one that did nothing.
- **The two ways into the dialog treat a leftover message differently**,
as before: the sites list's button clears it, the button in the
old-engine notice does not. A message is now an alert, so one left over
is announced when the dialog opens from that notice. Where a failed
setup is said belongs with the toasts, item 8 of #557.
- **Choosing a folder fails silently if the system's dialog throws.**
Carried over unchanged.
- **Three labels are new strings** ("Project", "Location", and the
sentence about the project being for good), so they are in English in
the shipped translations until they are translated.
- **The guide still describes the old dialog**, and its screenshot shows
it: `docs/guide/creating-a-site.md`, `docs/guide/getting-started.md`.
That is #561.

## Related

Part of #557. Part of #542. Follows #599.

---

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

- **Form only.** Asked and decided before building: the dialog keeps
closing on Create. The prototype's spinner covers a setup that takes
minutes here and can fail at each of its steps, which the site's page
shows with a retry for each.
- **The project's description under the choice.** A segment has room for
a name and nothing else. The line each radio option carried is said
under the control, for the project that is chosen, and the sentence that
the choice is for good with it. They are two sentences one under the
other, and not side by side with a space typed between them.
- **No Cancel button**, as the prototype: the close button, Escape and a
press outside do it.
- **The form is the dialog's content, and the button is in its footer**,
tied to the form by its id, so that Enter in the name asks for the site
as the button does.
- **The dialog is always in the tree and told whether it is open**, so
that it can fade and give the focus back. Its three answers are kept by
a component inside the popup, which is not there while the dialog is
closed.
- **No focus is asked for.** The design system puts it on the first
thing that is not the close button, which is the name. The audit showed
that asking was redundant.

</details>

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

2 [fix here] · 2 [follow-up] on the first pass, both fixed. 0 defects on
the second.

- **Review:** completed — a separate agent context, against
`.github/instructions/code-review.instructions.md`; head `d2f33e7`, base
`1319e26e51c5d5e94e02182b521dbbb24ef429ec`; it read the diff and the
libraries' source and ran lint and the unit tests, not the journeys.
Fixed:
1. "Escape does not close it while a setup runs" asked whether the
dialog could be seen, which a dialog that is closing still can for the
length of its fade. It asks whether it is open.
2. "A press on the held button asks for nothing" counted the setups
asked for, which an empty form would not have added to. It reads what
the form would say of a press that got through.
- Also taken: the two help sentences one under the other, the folder
field's rules scoped to the dialog, a comment about how long the popup
is there.
- Follow-ups, neither introduced here: the leftover message at the
notice's door, and the guide. Both in Risks above.
- **Second pass:** completed — a separate agent context; head
`e06a2a7f2ba062518424e3ed98d706d30c11bc28`, same base; no defects. It
confirmed from the libraries' source that the dialog's open state and
its fade are separate attributes, that the held button is held by the
design system and not by the browser, and that the page's rules win over
the design system's. It asked whether a dialog opened again while it
fades keeps its answers: it does not, and the journey now pins that.
- **Since review:** `e06a2a7` →
`6d8ce06957d6068a2980af2850bd3478ff50d9d5`: that journey step, and one
sample string in a unit test. No source under `src/` changed. Not
reviewed again; every suite was run on it.

Checks on `6d8ce06957d6068a2980af2850bd3478ff50d9d5`, macOS:

| Check | Result |
|---|---|
| `npx eslint . --max-warnings=0` | clean |
| `npm test` | 1836 pass, 2 skipped |
| `npm run test:electron` | 1837 pass, 1 skipped |
| `npm run test:e2e` | 90 of 90 |
| `npm run test:e2e:packaged` | 15 of 15, on an unsigned build |
| Four journey files in a 1024×728 window | 14 of 14 |
| Four journey files with the status read slowed | 15 of 15 |

</details>

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

- `src/renderer/components/create-site-dialog.jsx`: was
`create-site-modal.jsx`. `CreateSiteDialog`, its form and its folder
field.
- `src/renderer/create-site.cjs` (new): `projectChoices`, `projectHelp`,
`createSiteProblem`.
- `src/renderer/index.jsx`: the dialog is rendered always and given
`open`.
- `src/renderer/shell.css`: the folder field, and the type of the older
library's help text inside the dialog.
- `tests/e2e/real-setup/create-site.spec.js` is the journey run by hand
against a real clone; only the field's label changed in it, and it was
not run.

</details>

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

Not attached: I have no way to upload an image from here. What the
screenshots I took show:

- **Empty:** a small centred dialog, "Create site" and a close button at
the top; **Site name** with the placeholder "My WordPress site" and the
focus ring; **Project** with two equal segments, WordPress Core
selected, and two grey lines under it; **Location** with the file
button, "No folder selected yet." and a grey description; a blue
**Create site** button at the bottom right.
- **Filled:** Gutenberg selected, its description under the control, and
the chosen folder's path, wrapped, under the file button.
- **With a complaint:** a red notice with an icon, "Please provide a
site name.", above the button.

</details>

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
zaerl added a commit that referenced this pull request Oct 3, 2026
…ion in the app's own dialog (#601)

## Why

Part of the redesign in #542, item 7 of #557: the rename and delete
dialogs. Renaming a site was the older component library's modal.
Deleting a site, and deleting a ticket's work, asked through the
system's own confirm box, which says one sentence, cannot say what goes,
and has two buttons called OK and Cancel.

## What changes

**The rename dialog** is the design system's, as the prototype has it: a
small dialog with the name and one button.

- The button says **Rename** (it said Save). There is no Cancel: the
close button, Escape and a press outside leave it.
- It still opens on the name the site has, selected; refuses an empty
name; and cannot be left while the name is being written.
- A name that could not be written is said in the dialog, as an alert,
with the focus back on the name. Before, it was a system alert box,
after which the dialog closed as if the rename had worked.

**Deleting asks in a dialog of the app's own**, as the prototype has it
for a site:

- It is titled with what would go: "Delete *site name*?", or "Delete all
work on ticket #60001?".
- It says what that means and that it cannot be undone.
- It opens on **Cancel**. The other button is red and says what it does:
**Delete site**, **Delete this ticket's work**.
- A press outside is not an answer. Cancel and Escape are a no.

The same dialog asks before a ticket's work is deleted, which #599 left
to this pull request. On a Gutenberg site it says "issue".

The questions' words are in `site-dialogs.cjs` and
`ticket-branch-list.cjs`, with unit tests.

Not in this pull request:

- **The three discards** (loose edits on trunk, "discard all changes",
and the discard before a trunk update) still ask through the system's
box. Each is asked from, or over, a modal of the older library, and two
libraries' dialogs on top of one another is a risk better taken when
those modals are redrawn.
- **A default for an empty name.** The prototype keeps the old name when
the field is emptied. An empty name is still refused, as decided for the
create-site dialog.

## How to test this

Platforms: any. The change is in the renderer only.

The journeys walk all three on both platforms. From the repository's
root:

```bash
npm run build:once && npx playwright test --project=journeys rename-site.spec delete-site.spec ticket-list.spec ticket-branches.spec i18n.spec
```

`delete-site.spec.js` is new: the question names the site, nothing but a
yes deletes it, and a yes removes the folder. `rename-site.spec.js`
gains a journey for a rename that is pending and one that fails.
`ticket-list.spec.js` asks its question through the new dialog. Each
claim was checked by breaking it: 37 breaks, all red.

By hand, on the current head, on either platform:

**Starting state:** a Core site that is set up, with one ticket parked
(link a ticket, then **Unlink**).

1. Open the site's menu (⋮, top right) and choose **Rename…**.
- A small dialog titled **Rename site** opens with the site's name
selected in the field.
2. Empty the field and click **Rename**.
- A red notice says "Site name cannot be empty." and the cursor is in
the field.
3. Type a new name and press Enter.
- The dialog closes; the heading and the sites list show the new name.
4. Open the menu and choose **Delete site**.
- A dialog asks "Delete *the new name*?", says the site and all its
files will be permanently deleted, and has **Cancel** focused beside a
red **Delete site**.
5. Click outside the dialog, then press Escape.
- The click does nothing. Escape closes the dialog. The site is still
there.
6. In **Your tickets on this site**, click **Delete this ticket's
work**.
- A dialog asks "Delete all work on ticket #…?" with a red **Delete this
ticket's work**.
7. Click **Cancel**, then ask again and click the red button.
   - Cancel leaves the row. The red button removes it.

**What must not have happened:**

- Step 5 deleted nothing: the site's folder is still on disk.
- Step 7's Cancel deleted nothing: the ticket's branch is still in the
site.
- Step 7's yes deleted that ticket's work only, and the site is still on
trunk.

## Risks and limitations

- **Review:** two passes by a separate context. No defects on either. 3
follow-ups on the first, one taken here; 1 on the second, taken here.
Details below.
- **Checked on macOS only**, at the default size, in a 1024×728 window,
and with the site's status read slowed by a second. Windows is CI's.
- **The manual steps above were not run by a person.** I looked at
screenshots of the three dialogs.
- **A question can now stay open while the app goes on.** The system's
box froze the window; this one does not. Nothing that holds the delete
buttons can start behind an open question, since the question is modal,
so a yes is not checked again.
- **A failed rename shows the error as the main process words it**
("Error invoking remote method…"), untranslated. It is the same text the
alert box showed. A sentence of the app's own means changing the handler
in the main process.
- **A deletion that fails is said where it was before**: for a site, in
the notice at the top of the window; for a ticket's work, in the ticket
card. Not in the question, which has closed by then.
- **The guide quotes the old question** word for word, and still
describes the old rename: `docs/guide/ticket-branches.md`,
`docs/guide/managing-sites.md`. That is #561.
- **`session.acceptConfirms()`** is still used by three journey files,
all for discards.

## Related

Part of #557. Part of #542. Follows #600. Does what #599 left for the
ticket list's question.

---

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

- **One dialog for both questions.** `ConfirmDialog` takes what is asked
and what a yes does. It is the design system's alert dialog with its
"irreversible" intent, which is what makes the button red, puts the
focus on Cancel and turns a press outside away.
- **A yes closes the dialog at once.** The alert dialog can stay open
with a spinner until the work is done. A site's deletion already says
how it stands in the sites list, and a ticket's in its row, and both say
a failure where they always did. The dialog does not take that over.
- **The title says what goes, by name.** The system's box said "this
site". The dialog can be read without looking behind it.
- **The rename dialog stays held while it fades after a rename that
worked**, and is let go when its popup has gone. Otherwise its button
and close button come back for a fifth of a second.
- **A complaint puts the focus on the field and is the field's
description**, so that it is read with the field as well as when it
appears.
- **The note about the system's alerts and confirms** moved from
`onRename`, which no longer has one, to `confirmAnd`, which is where the
remaining ones are.

</details>

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

0 [fix here] · 3 [follow-up] on the first pass. 0 defects · 1
[follow-up] on the second.

- **Review:** completed — a separate agent context, against
`.github/instructions/code-review.instructions.md`; head `389e081`, base
`abb144269b9b108f0cd17e739b620c9199b1e89c`; it read the diff and the
libraries' source and ran lint and the unit tests, not the journeys. No
defects. Follow-ups:
1. "A no deletes nothing" looked for the folder, or the branch, which is
still there at the start of a deletion too. **Taken:** the journeys also
read what a deletion says as it begins.
  2. The guide quotes the old question. Left for #561.
  3. A failed rename shows the main process's wording. Left; in Risks.
- Notes taken: the dialog coming back to life while it fades after a
rename, and the focus after a failed one.
- **Second pass:** completed — a separate agent context, on the fix-up
before it was committed (staged over `389e081`), same base. No defects.
It checked that the held state cannot be left stuck (no way to reopen
the dialog while it fades: the backdrop takes the pointer and the focus
is given back after), that the focus is not taken at a wrong moment, and
that the slowed fade in the journey applies to the popup. One follow-up,
**taken:** the complaint is now the field's description.
- **Since review:** the second pass's fix-up plus that description, two
comments and one assertion read once instead of retried, committed
together as `db9e863a9a0e71218bcdb713d782b0be61445efa`. Not reviewed
again; every suite was run on it, and the new assertion goes red when
its line is removed.

Checks on `db9e863a9a0e71218bcdb713d782b0be61445efa`, macOS:

| Check | Result |
|---|---|
| `npx eslint . --max-warnings=0` | clean |
| `npm test` | 1840 pass, 2 skipped |
| `npm run test:electron` | 1841 pass, 1 skipped |
| `npm run test:e2e` | 93 of 93 |
| `npm run test:e2e:packaged` | 15 of 15, on an unsigned build |
| Seven journey files in a 1024×728 window | 26 of 26 |
| Seven journey files with the status read slowed | 26 of 26 |

</details>

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

- `src/renderer/components/rename-site-dialog.jsx`: was
`rename-site-modal.jsx`.
- `src/renderer/components/confirm-dialog.jsx` (new): `ConfirmDialog`.
- `src/renderer/site-dialogs.cjs` (new): `renameProblem`,
`deleteSiteQuestion`.
- `src/renderer/ticket-branch-list.cjs`: `deleteWorkQuestion` gives a
title, a description and the button's words, for a ticket and for an
issue.
- `src/renderer/index.jsx`: `asking` and `askFirst` in the site's view;
`onRename` rejects.
- `tests/e2e/helpers/ui.cjs`: `confirmDialog`, `confirmYesButton`,
`confirmNoButton`.
- The rename journey slows the dialog's fade to three seconds for one
step, to look at the dialog on its way out. It costs that test three
seconds.

</details>

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

Not attached: I have no way to upload an image from here. What the
screenshots I took show:

- **Rename:** a small centred dialog, "Rename site" and a close button,
the field with the name, a blue **Rename** button at the bottom right;
with an empty name, a red notice "Site name cannot be empty." between
the field and the button.
- **Delete site:** a wider dialog with no close button, "Delete
first-name?" as its title, two lines under it, and at the bottom right
**Cancel** with the focus ring and a red **Delete site**.
- **Delete a ticket's work:** the same, titled "Delete all work on
ticket #60001?", with a red **Delete this ticket's work**.

</details>

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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