From 982c197fee71a5625f733afa5417c934fdfa68fc Mon Sep 17 00:00:00 2001 From: Jan-CoreBunch Date: Thu, 8 Oct 2026 16:13:06 +0200 Subject: [PATCH 1/3] fix(bricks): avoid multiplying postmeta joins during class cleanup --- .../wp/Tests/BricksSynchronizationTest.php | 43 ++++++++++++++++++- packages/wp/wp/App/Bricks/Functions.php | 16 ++++--- scripts/test-wp-e2e.sh | 27 ++++++++++++ 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/packages/wp/Tests/BricksSynchronizationTest.php b/packages/wp/Tests/BricksSynchronizationTest.php index 8c31885..ce44aee 100644 --- a/packages/wp/Tests/BricksSynchronizationTest.php +++ b/packages/wp/Tests/BricksSynchronizationTest.php @@ -68,8 +68,11 @@ function get_post_types( $args = array(), $output = 'names' ) { if ( ! function_exists( 'get_posts' ) ) { function get_posts( $args = array() ) { - $meta_keys = array_column( array_filter( $args['meta_query'] ?? array(), 'is_array' ), 'key' ); - $post_ids = array(); + $meta_keys = array(); + foreach ( array_filter( $args['meta_query'] ?? array(), 'is_array' ) as $clause ) { + $meta_keys = array_merge( $meta_keys, (array) $clause['key'] ); + } + $post_ids = array(); foreach ( $GLOBALS['cf_test_post_meta'] as $post_id => $post_meta ) { if ( array_intersect( $meta_keys, array_keys( $post_meta ) ) ) { @@ -447,6 +450,42 @@ public function testSelectorsThatSanitizeToTheSameIdProduceOneClass(): void { ); } + public function testReferenceSweepFindsEveryBricksMetaKeyAndSkipsUnrelatedPosts(): void { + $meta_keys = array( + '_bricks_page_header_2', + '_bricks_page_content_2', + '_bricks_page_footer_2', + '_bricks_page_settings', + '_bricks_template_settings', + ); + + foreach ( $meta_keys as $index => $meta_key ) { + $GLOBALS['cf_test_post_meta'][ $index + 1 ] = array( + $meta_key => array( '_cssGlobalClasses' => array( 'removed_c', 'keep-me' ) ), + ); + } + $GLOBALS['cf_test_post_meta'][6] = array( '_bricks_page_content_2' => '' ); + $GLOBALS['cf_test_post_meta'][7] = array( '_bricks_page_settings' => array() ); + $GLOBALS['cf_test_post_meta'][8] = array( + 'unrelated_meta' => array( '_cssGlobalClasses' => array( 'removed_c' ) ), + ); + + $method = new ReflectionMethod( BricksFunctions::class, 'remove_class_references' ); + $method->setAccessible( true ); + $method->invoke( $this->createBricksFunctions(), array( 'removed_c' ) ); + + $this->assertSame( array( range( 1, 7 ) ), $GLOBALS['cf_test_meta_primes'] ); + foreach ( $meta_keys as $index => $meta_key ) { + $this->assertSame( + array( 'keep-me' ), + $GLOBALS['cf_test_post_meta'][ $index + 1 ][ $meta_key ]['_cssGlobalClasses'] + ); + } + $this->assertSame( '', $GLOBALS['cf_test_post_meta'][6]['_bricks_page_content_2'] ); + $this->assertSame( array(), $GLOBALS['cf_test_post_meta'][7]['_bricks_page_settings'] ); + $this->assertSame( array( 'removed_c' ), $GLOBALS['cf_test_post_meta'][8]['unrelated_meta']['_cssGlobalClasses'] ); + } + public function testReferenceSweepIsBatchedAndRunsAfterClassesArePersisted(): void { $GLOBALS['cf_test_options'][ BricksFunctions::BRICKS_CLASSES_OPTION ] = array( array( diff --git a/packages/wp/wp/App/Bricks/Functions.php b/packages/wp/wp/App/Bricks/Functions.php index f872a6f..1edcdc9 100644 --- a/packages/wp/wp/App/Bricks/Functions.php +++ b/packages/wp/wp/App/Bricks/Functions.php @@ -254,13 +254,15 @@ private function remove_class_references( array $class_ids ): void { defined( 'BRICKS_DB_TEMPLATE_SETTINGS' ) ? constant( 'BRICKS_DB_TEMPLATE_SETTINGS' ) : '_bricks_template_settings', ); - $meta_query = array( 'relation' => 'OR' ); - foreach ( $meta_keys as $meta_key ) { - $meta_query[] = array( - 'key' => $meta_key, - 'compare' => 'EXISTS', - ); - } + // Separate OR/EXISTS clauses create one postmeta join per key, multiplying + // rows on posts with lots of metadata. Match all keys through a single join. + $meta_query = array( + array( + 'key' => $meta_keys, + 'compare_key' => 'IN', + 'compare' => 'EXISTS', + ), + ); $post_ids = get_posts( array( diff --git a/scripts/test-wp-e2e.sh b/scripts/test-wp-e2e.sh index 11030f8..4fd4819 100755 --- a/scripts/test-wp-e2e.sh +++ b/scripts/test-wp-e2e.sh @@ -141,6 +141,33 @@ if (!is_file($path)) { WP_CLI::success("Generated stylesheet exists in the uploads directory."); ' +# Exercise the real WP_Query SQL builder: unit tests use a get_posts() stub. +# Short-circuit execution so this regression check never runs a Cartesian join. +wp_cli eval ' +$queries = array(); +$capture = static function ($posts, $query) use (&$queries) { + $queries[] = $query->request; + return array(); +}; +add_filter("posts_pre_query", $capture, 10, 2); +try { + $class = new ReflectionClass(\CoreFramework\App\Bricks\Functions::class); + $method = $class->getMethod("remove_class_references"); + $method->setAccessible(true); + $method->invoke($class->newInstanceWithoutConstructor(), array("cf-e2e-removed_c")); +} finally { + remove_filter("posts_pre_query", $capture, 10); +} +global $wpdb; +if (count($queries) !== 1 || substr_count($queries[0], "JOIN $wpdb->postmeta") !== 1) { + WP_CLI::error("Bricks reference discovery must use exactly one postmeta join."); +} +if (strpos($queries[0], "$wpdb->postmeta.meta_key IN (") === false) { + WP_CLI::error("Bricks reference discovery must filter metadata keys together."); +} +WP_CLI::success("Bricks reference discovery uses one postmeta join."); +' + curl --fail --silent --show-error --cookie-jar "$COOKIE_FILE" "$SITE_URL/wp-login.php" >/dev/null curl --fail --silent --show-error --location \ --cookie "$COOKIE_FILE" \ From 1d4a33ba1d4ebd02b3e5b75d4037bee18eaa9417 Mon Sep 17 00:00:00 2001 From: Jan-CoreBunch Date: Thu, 8 Oct 2026 14:29:04 +0200 Subject: [PATCH 2/3] chore(deps): patch critical build dependency advisories --- bun.lock | 14 ++++++++------ package.json | 2 ++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bun.lock b/bun.lock index 232f475..7d63639 100644 --- a/bun.lock +++ b/bun.lock @@ -75,7 +75,7 @@ }, "packages/figma": { "name": "core-framework-figma", - "version": "2.0.0", + "version": "2.0.2", "dependencies": { "@fontsource-variable/inter": "^5.3.0", "colord": "^2.9.3", @@ -102,7 +102,7 @@ }, "packages/gutenberg": { "name": "coreframework-gutenberg-integration", - "version": "2.0.0", + "version": "2.0.2", "devDependencies": { "@wordpress/block-editor": "^16.1.0", "@wordpress/browserslist-config": "^6.53.0", @@ -119,7 +119,7 @@ }, "packages/wp": { "name": "coreframework", - "version": "2.0.0", + "version": "2.0.2", "dependencies": { "@adobe/react-spectrum": "^3.47.3", "@core-framework/core": "workspace:*", @@ -167,7 +167,7 @@ }, "packages/www": { "name": "core-framework", - "version": "2.0.0", + "version": "2.0.2", "dependencies": { "@adobe/react-spectrum": "^3.47.3", "@core-framework/core": "workspace:*", @@ -229,6 +229,8 @@ "micromatch": "4.0.8", "nanoid": "3.3.18", "postcss": "8.5.26", + "proxy-addr": "2.0.8", + "shell-quote": "1.11.0", "svgo": "3.3.4", "websocket-driver": "0.7.5", "yaml": "1.10.3", @@ -3178,7 +3180,7 @@ "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], - "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "proxy-addr": ["proxy-addr@2.0.8", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ=="], "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], @@ -3386,7 +3388,7 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - "shell-quote": ["shell-quote@1.9.0", "", {}, "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA=="], + "shell-quote": ["shell-quote@1.11.0", "", {}, "sha512-JdxDPD0DBTyu08pq0kPC0xSNet/qsU07qT6IsX1AS8oO2ICNRY4ldNa8OAI6PuwAH8tG3lxEhbqmyp4Dw4036g=="], "shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="], diff --git a/package.json b/package.json index a9902c5..a6993d8 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "micromatch": "4.0.8", "nanoid": "3.3.18", "postcss": "8.5.26", + "proxy-addr": "2.0.8", + "shell-quote": "1.11.0", "svgo": "3.3.4", "websocket-driver": "0.7.5", "yaml": "1.10.3" From 7a4c21fbefd72b6105d4629cd87782b9a8ee86a2 Mon Sep 17 00:00:00 2001 From: DavidBabinec Date: Tue, 27 Oct 2026 10:12:42 +0100 Subject: [PATCH 3/3] docs(changelog): note Bricks class cleanup fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f665b8f..b83a7aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed +- Fixed Bricks sites hanging when removed Core Framework classes were cleaned up. The initial lookup joined post metadata five times, multiplying rows on sites with many metadata entries. It now checks all five Bricks metadata keys through one join while retaining the existing batched cleanup. - Fixed the block editor going blank with the Core Framework Gutenberg integration enabled. The editor script was loaded with an outdated, hard-coded dependency list that left out WordPress's `react-jsx-runtime`, so rendering the Core Framework class panel threw on an undefined `ReactJSXRuntime` and took the whole editor down. The script now loads the dependencies its build declares. - Fixed saving from the Figma plugin to a connected WordPress site, which waited 15 seconds and then reported "Failed to sync css with Figma" and "Failed to update project" even though the changes had reached the site. The plugin handed WordPress's reply back under the wrong message type, so the editor never recognised it. Builder synchronization now also waits for the stylesheet and project to finish saving, so Bricks global variables, color swatches and the Style Manager show the values you just saved, including on projects that define variables but no classes. An empty class list no longer creates a blank Oxygen selector. - Stopped disabled shades and tints from appearing as variables in the Oxygen and Bricks builders. The generated CSS already left out a color's shades or tints once you switched them off, but the builder variable dropdowns and Oxygen Classic's Alt-click variable panel still listed them, so picking one pointed at a custom property that did not exist.