diff --git a/.github/workflows/enforce-pr-labels.yml b/.github/workflows/enforce-pr-labels.yml index be2e46af5b55f..6dc9cb6b17add 100644 --- a/.github/workflows/enforce-pr-labels.yml +++ b/.github/workflows/enforce-pr-labels.yml @@ -1,7 +1,7 @@ name: Enforce labels on Pull Request on: pull_request_target: - types: [opened, labeled, unlabeled, synchronize] + types: [labeled, unlabeled, ready_for_review, review_requested] jobs: type-related-labels: runs-on: ubuntu-latest diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 0faaf9f39f182..5c4c7b068108f 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -87,7 +87,7 @@ jobs: uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 with: name: performance-results - path: ${{ env.WP_ARTIFACTS_PATH }}/*.performance-results.json + path: ${{ env.WP_ARTIFACTS_PATH }}/*.performance-results*.json - name: Publish performance results if: github.event_name == 'push' diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml index 1832625466cae..dd1f188e0ae2f 100644 --- a/.github/workflows/publish-npm-packages.yml +++ b/.github/workflows/publish-npm-packages.yml @@ -32,7 +32,7 @@ jobs: - name: Checkout (for CLI) uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: - path: main + path: cli ref: trunk - name: Checkout (for publishing) @@ -52,13 +52,13 @@ jobs: - name: Setup Node.js uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: - node-version-file: 'main/.nvmrc' + node-version-file: 'cli/.nvmrc' registry-url: 'https://registry.npmjs.org' - name: Publish development packages to npm ("next" dist-tag) if: ${{ github.event.inputs.release_type == 'development' }} run: | - cd main + cd cli npm ci ./bin/plugin/cli.js npm-next --ci --repository-path ../publish env: @@ -67,7 +67,7 @@ jobs: - name: Publish packages to npm with bug fixes ("latest" dist-tag) if: ${{ github.event.inputs.release_type == 'bugfix' }} run: | - cd main + cd cli npm ci ./bin/plugin/cli.js npm-bugfix --ci --repository-path ../publish env: @@ -76,8 +76,8 @@ jobs: - name: Publish packages to npm for WP major ("wp/${{ github.event.inputs.wp_version || 'X.Y' }}" dist-tag) if: ${{ github.event.inputs.release_type == 'wp' && github.event.inputs.wp_version }} run: | - cd main + cd publish npm ci - ./bin/plugin/cli.js npm-wp --wp-version=${{ github.event.inputs.wp_version }} --ci --repository-path ../publish + npx lerna publish patch --dist-tag wp-${{ github.event.inputs.wp_version }} --no-private --yes --no-verify-access env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index c2c953cbc9483..b5cd0124e5d4b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ build-types node_modules gutenberg.zip coverage -*-performance-results.json .phpunit.result.cache .reassure diff --git a/bin/plugin/commands/packages.js b/bin/plugin/commands/packages.js index c085201a23500..87ecc6eb89cfc 100644 --- a/bin/plugin/commands/packages.js +++ b/bin/plugin/commands/packages.js @@ -25,6 +25,7 @@ const { findPluginReleaseBranchName, } = require( './common' ); const { join } = require( 'path' ); +const pluginConfig = require( '../config' ); /** * Release type names. @@ -99,7 +100,7 @@ async function checkoutNpmReleaseBranch( { * `trunk` commits from within the past week. */ await SimpleGit( gitWorkingDirectoryPath ) - .fetch( npmReleaseBranch, [ '--depth=100' ] ) + .fetch( 'origin', npmReleaseBranch, [ '--depth=100' ] ) .checkout( npmReleaseBranch ); log( '>> The local npm release branch ' + @@ -411,13 +412,27 @@ async function publishPackagesToNpm( { ); } else if ( [ 'bugfix', 'wp' ].includes( releaseType ) ) { log( '>> Publishing modified packages to npm.' ); - await command( - `npx lerna publish ${ minimumVersionBump } --dist-tag ${ distTag } --no-private ${ yesFlag } ${ noVerifyAccessFlag }`, - { - cwd: gitWorkingDirectoryPath, - stdio: 'inherit', - } - ); + try { + await command( + `npx lerna publish ${ minimumVersionBump } --dist-tag ${ distTag } --no-private ${ yesFlag } ${ noVerifyAccessFlag }`, + { + cwd: gitWorkingDirectoryPath, + stdio: 'inherit', + } + ); + } catch { + log( + '>> Trying to finish failed publishing of modified npm packages.' + ); + await SimpleGit( gitWorkingDirectoryPath ).reset( 'hard' ); + await command( + `npx lerna publish from-package --dist-tag ${ distTag } ${ yesFlag } ${ noVerifyAccessFlag }`, + { + cwd: gitWorkingDirectoryPath, + stdio: 'inherit', + } + ); + } } else { log( '>> Bumping version of public packages changed since the last release.' @@ -431,13 +446,27 @@ async function publishPackagesToNpm( { ); log( '>> Publishing modified packages to npm.' ); - await command( - `npx lerna publish from-package ${ yesFlag } ${ noVerifyAccessFlag }`, - { - cwd: gitWorkingDirectoryPath, - stdio: 'inherit', - } - ); + try { + await command( + `npx lerna publish from-package ${ yesFlag } ${ noVerifyAccessFlag }`, + { + cwd: gitWorkingDirectoryPath, + stdio: 'inherit', + } + ); + } catch { + log( + '>> Trying to finish failed publishing of modified npm packages.' + ); + await SimpleGit( gitWorkingDirectoryPath ).reset( 'hard' ); + await command( + `npx lerna publish from-package ${ yesFlag } ${ noVerifyAccessFlag }`, + { + cwd: gitWorkingDirectoryPath, + stdio: 'inherit', + } + ); + } } const afterCommitHash = await SimpleGit( gitWorkingDirectoryPath ).revparse( @@ -530,7 +559,11 @@ async function runPackagesRelease( config, customMessages ) { config.abortMessage, async () => { log( '>> Cloning the Git repository' ); - await SimpleGit( gitPath ).clone( config.gitRepositoryURL ); + await SimpleGit().clone( + pluginConfig.gitRepositoryURL, + gitPath, + [ '--depth=1', '--no-single-branch' ] + ); log( ` >> successfully clone into: ${ gitPath }` ); } ); diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index bb6dd29f972cb..e153f482137e5 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -14,11 +14,13 @@ const { readJSONFile, askForConfirmation, getRandomTemporaryPath, + getFilesFromDir, } = require( '../lib/utils' ); const config = require( '../config' ); const ARTIFACTS_PATH = process.env.WP_ARTIFACTS_PATH || path.join( process.cwd(), 'artifacts' ); +const RESULTS_FILE_SUFFIX = '.performance-results.json'; /** * @typedef WPPerformanceCommandOptions @@ -29,62 +31,6 @@ const ARTIFACTS_PATH = * @property {string=} wpVersion The WordPress version to be used as the base install for testing. */ -/** - * @typedef WPRawPerformanceResults - * - * @property {number[]} timeToFirstByte Represents the time since the browser started the request until it received a response. - * @property {number[]} largestContentfulPaint Represents the time when the main content of the page has likely loaded. - * @property {number[]} lcpMinusTtfb Represents the difference between LCP and TTFB. - * @property {number[]} serverResponse Represents the time the server takes to respond. - * @property {number[]} firstPaint Represents the time when the user agent first rendered after navigation. - * @property {number[]} domContentLoaded Represents the time immediately after the document's DOMContentLoaded event completes. - * @property {number[]} loaded Represents the time when the load event of the current document is completed. - * @property {number[]} firstContentfulPaint Represents the time when the browser first renders any text or media. - * @property {number[]} firstBlock Represents the time when Puppeteer first sees a block selector in the DOM. - * @property {number[]} type Average type time. - * @property {number[]} typeContainer Average type time within a container. - * @property {number[]} focus Average block selection time. - * @property {number[]} inserterOpen Average time to open global inserter. - * @property {number[]} inserterSearch Average time to search the inserter. - * @property {number[]} inserterHover Average time to move mouse between two block item in the inserter. - * @property {number[]} listViewOpen Average time to open listView - */ - -/** - * @typedef WPPerformanceResults - * - * @property {number=} timeToFirstByte Represents the time since the browser started the request until it received a response. - * @property {number=} largestContentfulPaint Represents the time when the main content of the page has likely loaded. - * @property {number=} lcpMinusTtfb Represents the difference between LCP and TTFB. - * @property {number=} serverResponse Represents the time the server takes to respond. - * @property {number=} firstPaint Represents the time when the user agent first rendered after navigation. - * @property {number=} domContentLoaded Represents the time immediately after the document's DOMContentLoaded event completes. - * @property {number=} loaded Represents the time when the load event of the current document is completed. - * @property {number=} firstContentfulPaint Represents the time when the browser first renders any text or media. - * @property {number=} firstBlock Represents the time when Puppeteer first sees a block selector in the DOM. - * @property {number=} type Average type time. - * @property {number=} minType Minimum type time. - * @property {number=} maxType Maximum type time. - * @property {number=} typeContainer Average type time within a container. - * @property {number=} minTypeContainer Minimum type time within a container. - * @property {number=} maxTypeContainer Maximum type time within a container. - * @property {number=} focus Average block selection time. - * @property {number=} minFocus Min block selection time. - * @property {number=} maxFocus Max block selection time. - * @property {number=} inserterOpen Average time to open global inserter. - * @property {number=} minInserterOpen Min time to open global inserter. - * @property {number=} maxInserterOpen Max time to open global inserter. - * @property {number=} inserterSearch Average time to open global inserter. - * @property {number=} minInserterSearch Min time to open global inserter. - * @property {number=} maxInserterSearch Max time to open global inserter. - * @property {number=} inserterHover Average time to move mouse between two block item in the inserter. - * @property {number=} minInserterHover Min time to move mouse between two block item in the inserter. - * @property {number=} maxInserterHover Max time to move mouse between two block item in the inserter. - * @property {number=} listViewOpen Average time to open list view. - * @property {number=} minListViewOpen Min time to open list view. - * @property {number=} maxListViewOpen Max time to open list view. - */ - /** * Sanitizes branch name to be used in a path or a filename. * @@ -96,93 +42,23 @@ function sanitizeBranchName( branch ) { return branch.replace( /[^a-zA-Z0-9-]/g, '-' ); } -/** - * Computes the average number from an array numbers. - * - * @param {number[]} array - * - * @return {number} Average. - */ -function average( array ) { - return array.reduce( ( a, b ) => a + b, 0 ) / array.length; -} - /** * Computes the median number from an array numbers. * * @param {number[]} array * - * @return {number} Median. + * @return {number|undefined} Median value or undefined if array empty. */ function median( array ) { - const mid = Math.floor( array.length / 2 ), - numbers = [ ...array ].sort( ( a, b ) => a - b ); - return array.length % 2 !== 0 - ? numbers[ mid ] - : ( numbers[ mid - 1 ] + numbers[ mid ] ) / 2; -} + if ( ! array || ! array.length ) return undefined; -/** - * Rounds and format a time passed in milliseconds. - * - * @param {number} number - * - * @return {number} Formatted time. - */ -function formatTime( number ) { - const factor = Math.pow( 10, 2 ); - return Math.round( number * factor ) / factor; -} + const numbers = [ ...array ].sort( ( a, b ) => a - b ); + const middleIndex = Math.floor( numbers.length / 2 ); -/** - * Curate the raw performance results. - * - * @param {string} testSuite - * @param {WPRawPerformanceResults} results - * - * @return {WPPerformanceResults} Curated Performance results. - */ -function curateResults( testSuite, results ) { - if ( - testSuite === 'front-end-classic-theme' || - testSuite === 'front-end-block-theme' - ) { - return { - timeToFirstByte: median( results.timeToFirstByte ), - largestContentfulPaint: median( results.largestContentfulPaint ), - lcpMinusTtfb: median( results.lcpMinusTtfb ), - }; + if ( numbers.length % 2 === 0 ) { + return ( numbers[ middleIndex - 1 ] + numbers[ middleIndex ] ) / 2; } - - return { - serverResponse: average( results.serverResponse ), - firstPaint: average( results.firstPaint ), - domContentLoaded: average( results.domContentLoaded ), - loaded: average( results.loaded ), - firstContentfulPaint: average( results.firstContentfulPaint ), - firstBlock: average( results.firstBlock ), - type: average( results.type ), - minType: Math.min( ...results.type ), - maxType: Math.max( ...results.type ), - typeContainer: average( results.typeContainer ), - minTypeContainer: Math.min( ...results.typeContainer ), - maxTypeContainer: Math.max( ...results.typeContainer ), - focus: average( results.focus ), - minFocus: Math.min( ...results.focus ), - maxFocus: Math.max( ...results.focus ), - inserterOpen: average( results.inserterOpen ), - minInserterOpen: Math.min( ...results.inserterOpen ), - maxInserterOpen: Math.max( ...results.inserterOpen ), - inserterSearch: average( results.inserterSearch ), - minInserterSearch: Math.min( ...results.inserterSearch ), - maxInserterSearch: Math.max( ...results.inserterSearch ), - inserterHover: average( results.inserterHover ), - minInserterHover: Math.min( ...results.inserterHover ), - maxInserterHover: Math.max( ...results.inserterHover ), - listViewOpen: average( results.listViewOpen ), - minListViewOpen: Math.min( ...results.listViewOpen ), - maxListViewOpen: Math.max( ...results.listViewOpen ), - }; + return numbers[ middleIndex ]; } /** @@ -190,27 +66,18 @@ function curateResults( testSuite, results ) { * * @param {string} testSuite Name of the tests set. * @param {string} performanceTestDirectory Path to the performance tests' clone. - * @param {string} runKey Unique identifier for the test run, e.g. `branch-name_post-editor_run-3`. - * - * @return {Promise} Performance results for the branch. + * @param {string} runKey Unique identifier for the test run. */ async function runTestSuite( testSuite, performanceTestDirectory, runKey ) { - const resultsFilename = `${ runKey }.performance-results.json`; - await runShellScript( `npm run test:performance -- ${ testSuite }`, performanceTestDirectory, { ...process.env, WP_ARTIFACTS_PATH: ARTIFACTS_PATH, - RESULTS_FILENAME: resultsFilename, + RESULTS_ID: runKey, } ); - - return curateResults( - testSuite, - await readJSONFile( path.join( ARTIFACTS_PATH, resultsFilename ) ) - ); } /** @@ -239,7 +106,10 @@ async function runPerformanceTests( branches, options ) { await askForConfirmation( 'Ready to go? ' ); } - // 1- Preparing the tests directory. + /* + * 1- Preparing the tests directory. + */ + log( '\n>> Preparing the tests directories' ); log( ' >> Cloning the repository' ); @@ -285,13 +155,22 @@ async function runPerformanceTests( branches, options ) { log( ' >> Installing dependencies and building packages' ); await runShellScript( - 'bash -c "source $HOME/.nvm/nvm.sh && nvm install && npm ci && node ./bin/packages/build.js"', + `bash -c "${ [ + 'source $HOME/.nvm/nvm.sh', + 'nvm install', + 'npm ci', + 'npx playwright install chromium --with-deps', + 'npm run build:packages', + ].join( ' && ' ) }"`, performanceTestDirectory ); log( ' >> Creating the environment folders' ); await runShellScript( 'mkdir -p ' + rootDirectory + '/envs' ); - // 2- Preparing the environment directories per branch. + /* + * 2- Preparing the environment directories per branch. + */ + log( '\n>> Preparing an environment directory per branch' ); const branchDirectories = {}; for ( const branch of branches ) { @@ -404,7 +283,7 @@ async function runPerformanceTests( branches, options ) { } } - // 3- Printing the used folders. + // Printing the used folders. log( '\n>> Perf Tests Directory : ' + formats.success( performanceTestDirectory ) @@ -415,34 +294,28 @@ async function runPerformanceTests( branches, options ) { log( `>> Environment Directory (${ branch }) : ${ envPath }` ); } - // 4- Running the tests. + /* + * 3- Running the tests. + */ + log( '\n>> Running the tests' ); - const testSuites = [ - 'post-editor', - 'site-editor', - 'front-end-classic-theme', - 'front-end-block-theme', - ]; + const testSuites = getFilesFromDir( + path.join( performanceTestDirectory, 'test/performance/specs' ) + ).map( ( file ) => path.basename( file, '.spec.js' ) ); - /** @type {Record>} */ - const results = {}; const wpEnvPath = path.join( performanceTestDirectory, 'node_modules/.bin/wp-env' ); for ( const testSuite of testSuites ) { - results[ testSuite ] = {}; - /** @type {Array>} */ - const rawResults = []; - for ( let i = 0; i < TEST_ROUNDS; i++ ) { - const roundInfo = `round ${ i + 1 } of ${ TEST_ROUNDS }`; + for ( let i = 1; i <= TEST_ROUNDS; i++ ) { + const roundInfo = `round ${ i } of ${ TEST_ROUNDS }`; log( ` >> Suite: ${ testSuite } (${ roundInfo })` ); - rawResults[ i ] = {}; for ( const branch of branches ) { const sanitizedBranch = sanitizeBranchName( branch ); - const runKey = `${ testSuite }_${ sanitizedBranch }_run-${ i }`; + const runKey = `${ testSuite }_${ sanitizedBranch }_round-${ i }`; // @ts-ignore const environmentDirectory = branchDirectories[ branch ]; log( ` >> Branch: ${ branch }` ); @@ -452,7 +325,7 @@ async function runPerformanceTests( branches, options ) { environmentDirectory ); log( ' >> Running the test.' ); - rawResults[ i ][ branch ] = await runTestSuite( + await runTestSuite( testSuite, performanceTestDirectory, runKey @@ -464,53 +337,60 @@ async function runPerformanceTests( branches, options ) { ); } } + } - // Computing medians. - for ( const branch of branches ) { - /** - * @type {string[]} - */ - let dataPointsForTestSuite = []; - if ( rawResults.length > 0 ) { - dataPointsForTestSuite = Object.keys( - rawResults[ 0 ][ branch ] - ); - } + /* + * 4- Formatting and saving the results. + */ - const resultsByDataPoint = {}; - dataPointsForTestSuite.forEach( ( dataPoint ) => { - // @ts-ignore - resultsByDataPoint[ dataPoint ] = rawResults.map( - // @ts-ignore - ( r ) => r[ branch ][ dataPoint ] - ); - } ); - // @ts-ignore - const medians = Object.fromEntries( - Object.entries( resultsByDataPoint ).map( - ( [ dataPoint, dataPointResults ] ) => [ - dataPoint, - median( dataPointResults ), - ] - ) - ); + // Load curated results from each round. + const resultFiles = getFilesFromDir( ARTIFACTS_PATH ).filter( ( file ) => + file.endsWith( RESULTS_FILE_SUFFIX ) + ); + /** @type {Record>>} */ + const results = {}; - // Format results as times. - // @ts-ignore - results[ testSuite ][ branch ] = Object.fromEntries( - Object.entries( medians ).map( - ( [ dataPoint, dataPointMedian ] ) => [ - dataPoint, - formatTime( dataPointMedian ), - ] + // Calculate medians from all rounds. + for ( const testSuite of testSuites ) { + results[ testSuite ] = {}; + + for ( const branch of branches ) { + const sanitizedBranch = sanitizeBranchName( branch ); + const resultsRounds = resultFiles + .filter( ( file ) => + file.includes( + `${ testSuite }_${ sanitizedBranch }_round-` + ) ) - ); + .map( ( file ) => readJSONFile( file ) ); + + const metrics = Object.keys( resultsRounds[ 0 ] ); + results[ testSuite ][ branch ] = {}; + + for ( const metric of metrics ) { + const values = resultsRounds + .map( ( round ) => round[ metric ] ) + .filter( ( value ) => typeof value === 'number' ); + + const value = median( values ); + if ( value !== undefined ) { + results[ testSuite ][ branch ][ metric ] = value; + } + } } + + // Save calculated results to file. + fs.writeFileSync( + path.join( ARTIFACTS_PATH, testSuite + RESULTS_FILE_SUFFIX ), + JSON.stringify( results[ testSuite ], null, 2 ) + ); } - // 5- Formatting the results. - log( '\n>> 🎉 Results.\n' ); + /* + * 5- Displaying the results. + */ + log( '\n>> 🎉 Results.\n' ); log( '\nPlease note that client side metrics EXCLUDE the server response time.\n' ); @@ -518,31 +398,20 @@ async function runPerformanceTests( branches, options ) { for ( const testSuite of testSuites ) { log( `\n>> ${ testSuite }\n` ); + // Invert the results so we can display them in a table. /** @type {Record>} */ const invertedResult = {}; - Object.entries( results[ testSuite ] ).reduce( - ( acc, [ key, val ] ) => { - for ( const entry of Object.keys( val ) ) { - // @ts-ignore - if ( ! acc[ entry ] && isFinite( val[ entry ] ) ) - acc[ entry ] = {}; - // @ts-ignore - if ( isFinite( val[ entry ] ) ) { - // @ts-ignore - acc[ entry ][ key ] = val[ entry ] + ' ms'; - } - } - return acc; - }, - invertedResult - ); - console.table( invertedResult ); + for ( const [ branch, metrics ] of Object.entries( + results[ testSuite ] + ) ) { + for ( const [ metric, value ] of Object.entries( metrics ) ) { + invertedResult[ metric ] = invertedResult[ metric ] || {}; + invertedResult[ metric ][ branch ] = `${ value } ms`; + } + } - const resultsFilename = testSuite + '.performance-results.json'; - fs.writeFileSync( - path.join( ARTIFACTS_PATH, resultsFilename ), - JSON.stringify( results[ testSuite ], null, 2 ) - ); + // Print the results. + console.table( invertedResult ); } } diff --git a/bin/plugin/lib/utils.js b/bin/plugin/lib/utils.js index c50094321710c..4f57269d60c77 100644 --- a/bin/plugin/lib/utils.js +++ b/bin/plugin/lib/utils.js @@ -37,8 +37,9 @@ function runShellScript( script, cwd, env = {} ) { ...env, }, }, - function ( error, _, stderr ) { + function ( error, stdout, stderr ) { if ( error ) { + console.log( stdout ); // Sometimes the error message is thrown via stdout. console.log( stderr ); reject( error ); } else { @@ -120,10 +121,30 @@ function getRandomTemporaryPath() { return path.join( os.tmpdir(), uuid() ); } +/** + * Scans the given directory and returns an array of file paths. + * + * @param {string} dir The path to the directory to scan. + * + * @return {string[]} An array of file paths. + */ +function getFilesFromDir( dir ) { + if ( ! fs.existsSync( dir ) ) { + console.log( 'Directory does not exist: ', dir ); + return []; + } + + return fs + .readdirSync( dir, { withFileTypes: true } ) + .filter( ( dirent ) => dirent.isFile() ) + .map( ( dirent ) => path.join( dir, dirent.name ) ); +} + module.exports = { askForConfirmation, runStep, readJSONFile, runShellScript, getRandomTemporaryPath, + getFilesFromDir, }; diff --git a/changelog.txt b/changelog.txt index 89b0b707c106a..0149b4c2b7bf5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,394 @@ == Changelog == += 16.5.0-rc.1 = + + + +## Changelog + +### Features + +#### Interactivity API +- Allow passing optional `afterLoad` callbacks to `store` calls. ([53363](https://github.com/WordPress/gutenberg/pull/53363)) + +### Enhancements + +#### Post Editor +- Command Palette: + - Add new block commands. ([52509](https://github.com/WordPress/gutenberg/pull/52509)) + - Add support for registering commands without icons. ([53647](https://github.com/WordPress/gutenberg/pull/53647)) + - Update the `Preview in a new tab` command to reuse the preview target tab when available. ([53242](https://github.com/WordPress/gutenberg/pull/53242)) + - Update command palette styling. ([53117](https://github.com/WordPress/gutenberg/pull/53117)) + - Improve command palette rendering on smaller viewports. ([53661](https://github.com/WordPress/gutenberg/pull/53661)) +- Replace `withPluginContext` in `PluginPostPublishPanel` ([53302](https://github.com/WordPress/gutenberg/pull/53302)) and `PluginPrePublishPanel` ([53304](https://github.com/WordPress/gutenberg/pull/53304)). +- Replace HoCs with hooks in: + - `PluginDocumentSettingPanel` ([53290](https://github.com/WordPress/gutenberg/pull/53290)) + - `PostPendingStatusCheck` ([53389](https://github.com/WordPress/gutenberg/pull/53389)) + - `PostPendingStatus` ([53387](https://github.com/WordPress/gutenberg/pull/53387)) +- Top-align Publish row in the post panel. ([53573](https://github.com/WordPress/gutenberg/pull/53573)) +- Dependencies: Bump `remove-accents` to 0.5.0. ([53420](https://github.com/WordPress/gutenberg/pull/53420)) + +#### Components +- Button: Remove default border from the destructive button. ([53607](https://github.com/WordPress/gutenberg/pull/53607)) +- LineHeightControl: Allow for more granular control of decimal places. ([52902](https://github.com/WordPress/gutenberg/pull/52902)) +- Snackbar: Design and motion improvements. ([53248](https://github.com/WordPress/gutenberg/pull/53248)) +- Modal: + - Add `headerActions` prop to render buttons in the header. ([53328](https://github.com/WordPress/gutenberg/pull/53328)) + - Nuance outside interactions. ([52994](https://github.com/WordPress/gutenberg/pull/52994)) +- ProgressBar: + - Use gray 300 for track color. ([53349](https://github.com/WordPress/gutenberg/pull/53349)) + - Use the theme system accent for indicator color. ([53347](https://github.com/WordPress/gutenberg/pull/53347)) + - Use theme accent color variable. ([53632](https://github.com/WordPress/gutenberg/pull/53632)) +- Expose `Theme` via private APIs. ([53262](https://github.com/WordPress/gutenberg/pull/53262)) +- Move accent colors to theme context. ([53631](https://github.com/WordPress/gutenberg/pull/53631)) + +#### Block Library +- Details block: Add accordion and toggle keywords. ([53501](https://github.com/WordPress/gutenberg/pull/53501)) +- Column block: + - Add stretch alignment. ([53325](https://github.com/WordPress/gutenberg/pull/53325)) + - Exit on enter. ([53311](https://github.com/WordPress/gutenberg/pull/53311)) +- Classic block: Increase dimensions of modal and allow toggling fullscreen. ([53449](https://github.com/WordPress/gutenberg/pull/53449)) +- File block: Add spacing support. ([45107](https://github.com/WordPress/gutenberg/pull/45107)) +- Footnotes block: Add typography, dimensions, and border block supports. ([53044](https://github.com/WordPress/gutenberg/pull/53044)) +- Image block: Add aspect ratio support to lightbox. ([52765](https://github.com/WordPress/gutenberg/pull/52765)) +- Remove "post" from block titles. ([53492](https://github.com/WordPress/gutenberg/pull/53492)) + +#### Patterns +- Open detail view when duplicating pattern. ([53214](https://github.com/WordPress/gutenberg/pull/53214)) +- Prevent convert modal closing block options menu. ([53707](https://github.com/WordPress/gutenberg/pull/53707)) +- Skip migration logs in the Patterns screen. ([53626](https://github.com/WordPress/gutenberg/pull/53626)) + +#### Global Styles +- Global styles revisions: Add a reset to default revision. ([52965](https://github.com/WordPress/gutenberg/pull/52965)) +- Global styles revisions: Reduce visibility check from 2 to 1 revision. ([53281](https://github.com/WordPress/gutenberg/pull/53281)) +- Post Content: Add color controls. ([51326](https://github.com/WordPress/gutenberg/pull/51326)) + +#### Media +- Adjust size of image previews in list view. ([53649](https://github.com/WordPress/gutenberg/pull/53649)) +- List View: Add media previews to list view for gallery and image blocks. ([53381](https://github.com/WordPress/gutenberg/pull/53381)) + +#### Site Editor +- Command Palette: Order template results in Site Editor. ([53286](https://github.com/WordPress/gutenberg/pull/53286)) +- Edit Site: Use progress bar for loading screen. ([53032](https://github.com/WordPress/gutenberg/pull/53032)) + +#### Data Layer +- Data: Warn if the 'useSelect' hook returns different values when called with the same state and parameters. ([53666](https://github.com/WordPress/gutenberg/pull/53666)) + +#### Block Editor +- Add `Opens in new Tab` control into Link Preview. ([53566](https://github.com/WordPress/gutenberg/pull/53566)) + +#### Interactivity API +- Update deepsignal version. ([53549](https://github.com/WordPress/gutenberg/pull/53549)) + +#### Code Editor +- Tweak, and add, more consistent commands. ([53496](https://github.com/WordPress/gutenberg/pull/53496)) + +#### Themes +- Allow layout controls to be disabled per block from theme.json. ([53378](https://github.com/WordPress/gutenberg/pull/53378)) + +#### Plugins API +- Plugins: Introduce the 'usePluginContext' hook. ([53291](https://github.com/WordPress/gutenberg/pull/53291)) + +#### Layout +- Add layout and block spacing to details block. ([53282](https://github.com/WordPress/gutenberg/pull/53282)) + +#### Typography +- Fluid typography: Add min and max viewport width configurable options. ([53081](https://github.com/WordPress/gutenberg/pull/53081)) + + +### New APIs + +#### Extensibility +- Make useBlockEditingMode() public. ([52094](https://github.com/WordPress/gutenberg/pull/52094)) + + +### Bug Fixes + +- Command palette: Fix metrics for resting and no results view. ([53497](https://github.com/WordPress/gutenberg/pull/53497)) +- Fix top toolbar in the post editor with custom fields in Safari. ([53688](https://github.com/WordPress/gutenberg/pull/53688)) +- Improve metrics on post publish view buttons. ([53245](https://github.com/WordPress/gutenberg/pull/53245)) +- Set top toolbar size dynamically. ([53526](https://github.com/WordPress/gutenberg/pull/53526)) +- Support container queries in editor CSS. ([49915](https://github.com/WordPress/gutenberg/pull/49915)) + +#### Block Library +- Button block: Memoize link value passed to the LinkControl. ([53507](https://github.com/WordPress/gutenberg/pull/53507)) +- Cover block: Fix flickering when inserted in templates and also fix isDark calculation bugs. ([53253](https://github.com/WordPress/gutenberg/pull/53253)) +- Footnotes: + - Autosave is not slashing JSON. ([53664](https://github.com/WordPress/gutenberg/pull/53664)) + - Fix accidental override. ([53663](https://github.com/WordPress/gutenberg/pull/53663)) + - Fix recursion into updating attributes when attributes is not an object. ([53257](https://github.com/WordPress/gutenberg/pull/53257)) +- Image block: + - Fix image stretching with only height. ([53443](https://github.com/WordPress/gutenberg/pull/53443)) + - Don't render `DimensionsTool` if it is not resizable. ([53181](https://github.com/WordPress/gutenberg/pull/53181)) + - Fix stretched images constrained by max-width. ([53274](https://github.com/WordPress/gutenberg/pull/53274)) + - Clear aspect ratio when wide aligned. ([53439](https://github.com/WordPress/gutenberg/pull/53439)) + - Dimensions Tool: Change the conditions underwhich we display the scale control. ([53334](https://github.com/WordPress/gutenberg/pull/53334)) + - Aspect Ratio: Reset height when selecting the original aspect ratio. ([53339](https://github.com/WordPress/gutenberg/pull/53339)) +- Latest Posts block: Make latest-posts ssr categories handling more defensive. ([53659](https://github.com/WordPress/gutenberg/pull/53659)) +- Inject theme stylesheet value as template part theme attribute. ([53423](https://github.com/WordPress/gutenberg/pull/53423)) +- Patterns: Add `delete_posts` to the wp_block (patterns) capabilities. ([53405](https://github.com/WordPress/gutenberg/pull/53405)) +- Block serialization: Correctly compare default attribute values. ([53521](https://github.com/WordPress/gutenberg/pull/53521)) + +#### Block Editor +- Fix Synced Patterns' color in quick inserter. ([53327](https://github.com/WordPress/gutenberg/pull/53327)) +- Hide pattern previews on hover in inserter. ([53331](https://github.com/WordPress/gutenberg/pull/53331)) +- LinkControl: Prevent overflow when the title is a URL. ([53356](https://github.com/WordPress/gutenberg/pull/53356)) +- Safari: Fix ArrowUp on empty paragraph. ([53341](https://github.com/WordPress/gutenberg/pull/53341)) +- Safari: Fix Shift+Click multi select. ([53440](https://github.com/WordPress/gutenberg/pull/53440)) +- Selection: Restore focus after dragging out of the block repeatedly. ([53429](https://github.com/WordPress/gutenberg/pull/53429)) +- Writing flow: Avoid merging paragraph into Columns. ([53508](https://github.com/WordPress/gutenberg/pull/53508)) +- Writing flow: Fix vertical arrow keys not moving. ([53454](https://github.com/WordPress/gutenberg/pull/53454)) + +#### Site Editor +- Add missing i18n in `HomeTemplateDetails`. ([53543](https://github.com/WordPress/gutenberg/pull/53543)) +- Adds site editor mobile block settings and styles. ([53412](https://github.com/WordPress/gutenberg/pull/53412)) +- Edit Site: Fix site editor canvas edit mode button. ([53730](https://github.com/WordPress/gutenberg/pull/53730)) +- Fix document actions label helper method. ([52974](https://github.com/WordPress/gutenberg/pull/52974)) +- Fix document title alignment in command palette button. ([53224](https://github.com/WordPress/gutenberg/pull/53224)) + +#### Post Editor +- Fix crash by moving editor style logic into a hook with useMemo. ([53596](https://github.com/WordPress/gutenberg/pull/53596)) +- Fix support of sticky position in non-iframed post editor. ([53540](https://github.com/WordPress/gutenberg/pull/53540)) +- Fix the typo when setting the preview device type to 'Desktop'. ([53409](https://github.com/WordPress/gutenberg/pull/53409)) +- getInsertionPoint: Avoid returning a different object on every call. ([53722](https://github.com/WordPress/gutenberg/pull/53722)) + +#### Page Content Focus +- Fix missing Replace button in content-locked Image blocks. ([53410](https://github.com/WordPress/gutenberg/pull/53410)) +- Site Editor: Fix BlockPreview in Template panel when editing a page. ([53550](https://github.com/WordPress/gutenberg/pull/53550)) +- Use template.blocks in BlockPreview if it exists. ([53611](https://github.com/WordPress/gutenberg/pull/53611)) + +#### Navigation Menus +- Fix: #52886 Make all the 'Loading' strings consistent. ([52901](https://github.com/WordPress/gutenberg/pull/52901)) +- Fix: Title is not copied correctly when duplicating navigation. ([53610](https://github.com/WordPress/gutenberg/pull/53610)) +- Revert Fix entity cache misses for single posts due to string as recordKey. ([53419](https://github.com/WordPress/gutenberg/pull/53419)) + +#### Typography +- Fallback to default max viewport if layout wide size is fluid. ([53551](https://github.com/WordPress/gutenberg/pull/53551)) +- Fix typo and add tests for fonts install endpoint. ([53644](https://github.com/WordPress/gutenberg/pull/53644)) + +#### Patterns +- Fix pattern creation button in list view dropdown menu. ([53562](https://github.com/WordPress/gutenberg/pull/53562)) +- Fix: Sync status overlaps for some languages in Patterns post type page. ([53243](https://github.com/WordPress/gutenberg/pull/53243)) + +#### Rich Text +- Copy tag name on internal paste. ([48254](https://github.com/WordPress/gutenberg/pull/48254)) +- RichText: Remove 'Footnotes' when interactive formatting is disabled. ([53474](https://github.com/WordPress/gutenberg/pull/53474)) + +#### Global Styles +- Fix push-to-global-styles clearing of attributes, border fallbacks, link hover colors, and behaviors. ([51621](https://github.com/WordPress/gutenberg/pull/51621)) +- Preserve block style variations when securing theme json. ([53466](https://github.com/WordPress/gutenberg/pull/53466)) + +#### Layout +- Don't add root padding to children of flex and grid layout blocks. ([53259](https://github.com/WordPress/gutenberg/pull/53259)) +- Include namespace in layout classname for non-core blocks. ([53404](https://github.com/WordPress/gutenberg/pull/53404)) + +#### Interactivity API +- Add short-cirtuit to `useSignalEffect`. ([53358](https://github.com/WordPress/gutenberg/pull/53358)) +- Add support for underscores and leading dashes in the suffix part of the directive. ([53337](https://github.com/WordPress/gutenberg/pull/53337)) + +#### Components +- Button: add `:Disabled` selector to reset hover color for disabled buttons. ([53411](https://github.com/WordPress/gutenberg/pull/53411)) + +#### Template Editor +- Remove "go to" for terms and posts. ([53408](https://github.com/WordPress/gutenberg/pull/53408)) + +#### Custom Fields +- Insert path and query args to form before submitting. ([53324](https://github.com/WordPress/gutenberg/pull/53324)) + +#### Themes +- Don't allow access to Styles-related pages via the command palette in the hybrid theme. ([53123](https://github.com/WordPress/gutenberg/pull/53123)) + +#### Block Validation/Deprecation +- Media & Text Block: Fix deprecation with `isStackOnMobile` default value changed. ([49538](https://github.com/WordPress/gutenberg/pull/49538)) + +#### npm Packages +- Add some missing package dependencies. ([41486](https://github.com/WordPress/gutenberg/pull/41486)) + + +### Accessibility + +- Type labels GH Action: Fix accessibility issues in error message. ([53371](https://github.com/WordPress/gutenberg/pull/53371)) + +#### Block Library +- Add accessible description of current Navigation block state. ([53469](https://github.com/WordPress/gutenberg/pull/53469)) +- Implement accessible version of Navigation overlay preview toggle control. ([53462](https://github.com/WordPress/gutenberg/pull/53462)) +- Search Block: Fix unintended wrapping of button text in "Button only" style. ([53373](https://github.com/WordPress/gutenberg/pull/53373)) + + +### Performance + +- Compute presets from `theme.json`: Skip those without classes or variables. ([53574](https://github.com/WordPress/gutenberg/pull/53574)) +- Switch performance tests to Playwright. ([52022](https://github.com/WordPress/gutenberg/pull/52022)) + +#### Block Editor +- Fix memory leaks in `