Repository navigation
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. To trigger a review, include ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
🤖 PR meta 🤖🎉 PropsIf you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. Updated as activity occurs, without notifying anyone named here. Add the |
|
Good thoughts, and seems to follow the same error message guidelines that exist in Storybook, which were informed by the same source. This PR uses Notice because the new Notice V2 (from
|
|
Looks like the first step here is going to be to update the "notices" package to rely on the wordpress/ui instead of wordpress/components. |
Related: #82701 (comment) With the |
|
Just to clarify, I don't think this PR needs to be blocked by using Notice V2. But I'd just use a text button instead of the text + icon button, in Notice V1, rather than the icon only button as is used at present, which looks off. |
|
For me we should swap the notices first, not because of the design itself but because the current PR is forcing the editor to have two ways to render notices. |
|
Hey 👋 I wanted to give you a heads-up since this pull request is affected by recent validation changes for changelog files. #83043 adds additional validation for changelog files. You'll note that this pull request is currently failing a "Required changes from trunk" check. What you'll need to do: You will need to either rebase or merge the latest code from |
mcsf
left a comment
There was a problem hiding this comment.
This isn't a review, just some preliminary observations before I have a proper pass at the PR :)
More than one child arrives as an array, and each one is typically rendered conditionally. The check looked at the array itself, which is neither `false` nor empty, so `[ false, false ]` counted as content and left an empty wrapper in the DOM. `Children.toArray` flattens that array and drops the nothings along the way — `null`, `undefined`, and the booleans a `&&` leaves behind — so only the empty string is left to check for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A notice's content is a plain string: `createNotice` casts it, with a comment saying a React element is not supported. The only way to carry more than a sentence was to build escaped HTML into the message and render it with `__unstableHTML`, whose own type says it should not be used for notices. Adds a `detail` option to `createNotice` and a matching `detail` prop on `Notice`. It holds a fuller account as plain text, sits behind a native `details` disclosure, and is offered for copying together with the message, so a failure can be pasted into a search or an assistant without being retyped by hand. `NoticeList` already forwards every notice field to `Notice`, so a notice dispatched with a detail reaches the rendered notice with no change to `InlineNotices`. Save failures use it in place of the escaped HTML they built by hand, which retires the `__unstableHTML` flag and the manual `speak()` call that flag forced. Markup in a server message is stripped rather than the message being dropped, and only when there is a tag to strip, so a message that merely contains a `<` keeps everything after it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5f9d9a1 to
eaa3e34
Compare
ciampo
left a comment
There was a problem hiding this comment.
Left a few more comments. We'll also need to rebase, solve conflicts, and make sure CHANGELOG entries are in the latest unreleased section.
One aspect to reason about carefully is parity between @wordpress/components and @wordpress/ui Notice.
In the new Notice we don't support a description that can be hidden behind a disclosure, and we don't embed a copy button either.
Let's agree on what features we will want to support in the @wordpress/ui Notice "natively" vs via composition of other components, and thay will inform what features should be part of Notice vs which ones would be composed by external consumers.
Adding a disclosure subcomponent may be easy enough, but adding a notice-specific copy button would be trickier, because it may be difficult to know exaclty which content to copy to the clipboard (unless we decide explicitly that 1 disclosure = 1 copy button that lives inside the diclosure).
cc @WordPress/gutenberg-components @WordPress/gutenberg-design
| const ref = useCopyToClipboard< HTMLButtonElement >( | ||
| `${ message }\n\n${ detail }`, | ||
| () => | ||
| speak( | ||
| isError | ||
| ? __( 'Error copied to clipboard.' ) | ||
| : __( 'Notice copied to clipboard.' ) | ||
| ) | ||
| ); |
There was a problem hiding this comment.
Do we want to gate the copy button only to notices that have details ?
| /** | ||
| * A fuller account of what went wrong, as plain text, for when the message | ||
| * cannot say everything a developer needs — the server's own report of a | ||
| * failure, say. It sits behind a disclosure so it stays out of the way of | ||
| * everyone else, and is offered for copying together with the message, | ||
| * ready to paste into a search or an assistant. | ||
| */ | ||
| detail?: string; |
There was a problem hiding this comment.
We should probably be more generic in the description, rather than implying that this is used for errors.
Also, maybe description is a better name, as it aligns more closely with the Notice.Description subcomponent in the new Notice component in @wordpress/ui?
| { detail ? ( | ||
| <NoticeDetail | ||
| detail={ detail } | ||
| message={ toText( spokenMessage ) } |
There was a problem hiding this comment.
We may want to derive plain text from the visible content (and not spokenMessage) for copying. JSX without an override currently copies HTML tags, and speak: false removes the main message from the copied text.
| // The clipboard itself is the browser's, so what is asserted here is what the | ||
| // component hands it. | ||
| vi.mock( import( '@wordpress/compose' ), async ( importOriginal ) => ( { | ||
| ...( await importOriginal() ), | ||
| useCopyToClipboard: vi.fn(), | ||
| } ) ); | ||
| const mockedUseCopyToClipboard = vi.mocked( useCopyToClipboard ); | ||
|
|
There was a problem hiding this comment.
We could refactor these tests to be Vitest Browser tests, so that we don't need to mock the clipboard functionality

What?
Gives every save failure notice in the editor a copy-to-clipboard button, and moves the notice out of the notices store and into a component so it can have one.
Follows up on Defensive data design, which asks for exactly this next to error messages:
It builds on the save failure notices from #76470, which put the server's account of a failure behind a "Show details" disclosure.
Why?
An error message is a common time when someone turns for help and adding a copy button, along with "show details" helps make that just a little bit easier.
The disclosure added in #76470 was built by concatenating escaped HTML into the notice string and rendering it with
__unstableHTML, because@wordpress/noticescasts its content to a string. That flag's own type says it "SHOULD NOT be used for notices", and a string cannot hold an interactive control, so a copy button was not reachable without moving the notice into a component.I pursued this path, partially because it looks like a better, more readable experience.
How?
createNoticeacceptsdetailand puts it on the notice.NoticeListalready forwards every notice field toNotice, so it arrives with no change toInlineNotices.Noticerenders it behind aCollapsibledisclosure, with the trigger and a copy button sharing the notice's existing actions row.Collapsiblesuppliesaria-expanded,aria-controlsand the panel wiring, and its panel useshiddenUntilFound, so what the notice discloses stays reachable by the browser's find-in-page while collapsed.The copy button is named for the notice — "Copy error" at the
errorstatus, "Copy details" otherwise — and keeps that name while a copy is confirmed through the live region, since renaming a focused element is announced inconsistently.savePostpasses the server's message instead of building HTML by hand, so it no longer needs the__unstableHTMLflag, thespeak: falseoption, or the manualspeak()call that flag forced. Markup in a server message is stripped rather than the message being dropped, and only when there is a tag to strip, so a parse error that merely contains a<keeps everything after it. Block boundaries become line breaks first, so two sentences reach the clipboard separated rather than run together.Also fixes
InlineNotices, which treated an array of children as content even when every member rendered nothing, leaving an empty wrapper in the DOM.Testing Instructions
Open a post in the editor.
Trigger a save failure that carries a server message. A quick way is an mu-plugin that rejects the save:
Title the post
Break save pleaseand save.Confirm the notice shows "Show details" and a copy button.
Expand it. Confirm the server's message appears, with its two sentences on separate lines and no markup.
Press the copy button. Confirm the clipboard holds the notice message, a blank line, and the detail.
Go offline (DevTools → Network → Offline) and save. Confirm the notice shows the copy button inline with the message, and no "Show details".
Save successfully. Confirm the error notice is gone.
Testing Instructions for Keyboard
Screenshots or screencast
An error with a server message to disclose:
An error without one, where the copy button sits inline:
Use of AI Tools
Claude Code was used for the implementation, the browser verification and this description.