-
Notifications
You must be signed in to change notification settings - Fork 0
fix: gate built assets and guard generators against silent drift #176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jackgranatowski
merged 2 commits into
main
from
claude/codebase-audit-vendoring-security-i9592s
Jul 8, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 22 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| <?php | ||
| /** | ||
| * Golden-value regression tests for Slashed_Color_Math — the dependency-free | ||
| * OKLCH ⇄ hex color-space math that powers PHP-side swatch previews. | ||
| * | ||
| * The same conversion also exists in JS (the vendored OklchColorDesk / | ||
| * WcagPanel components drive the interactive picker). These are hand-maintained | ||
| * parallel implementations with no build-time link, so they can silently | ||
| * diverge — a preview swatch computed in PHP would then stop matching the | ||
| * colour the picker shows. These golden values lock the PHP output so any | ||
| * future change to the conversion math is caught here rather than shipping as a | ||
| * silent visual drift. If the framework's canonical algorithm intentionally | ||
| * changes, update these constants deliberately (and mirror the change in JS). | ||
| * | ||
| * Pure math, no WordPress runtime — see class doc comment. | ||
| * | ||
| * @package SLASHED | ||
| */ | ||
|
|
||
| use PHPUnit\Framework\TestCase; | ||
|
|
||
| final class ColorMathTest extends TestCase { | ||
|
|
||
| /** | ||
| * @return array<string, array{0: float, 1: float, 2: float, 3: string}> | ||
| */ | ||
| public function oklch_to_hex_cases() { | ||
| return array( | ||
| // [ L, C, H, expected hex ] | ||
| 'blue (primary source)' => array( 0.45, 0.20, 264.0, '#1745c2' ), | ||
| 'green' => array( 0.70, 0.15, 145.0, '#5bb661' ), | ||
| 'warm red' => array( 0.60, 0.12, 29.0, '#bd6255' ), | ||
| 'achromatic grey' => array( 0.50, 0.0, 0.0, '#636363' ), | ||
| 'pale yellow' => array( 0.90, 0.05, 90.0, '#ebddb9' ), | ||
| 'deep violet' => array( 0.20, 0.10, 300.0, '#1e023b' ), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @dataProvider oklch_to_hex_cases | ||
| */ | ||
| public function test_oklch_to_hex_matches_golden( $l, $c, $h, $expected ) { | ||
| $this->assertSame( $expected, Slashed_Color_Math::oklch_to_hex( $l, $c, $h ) ); | ||
| } | ||
|
|
||
| public function test_parse_oklch_reads_components() { | ||
| $this->assertSame( array( 0.45, 0.2, 264.0 ), Slashed_Color_Math::parse_oklch( 'oklch(0.45 0.2 264)' ) ); | ||
| $this->assertSame( array( 0.45, 0.2, 264.0 ), Slashed_Color_Math::parse_oklch( 'oklch(0.45 0.2 264deg)' ) ); | ||
| } | ||
|
|
||
| public function test_parse_oklch_rejects_garbage() { | ||
| $this->assertNull( Slashed_Color_Math::parse_oklch( 'rgb(1,2,3)' ) ); | ||
| $this->assertNull( Slashed_Color_Math::parse_oklch( 'not a color' ) ); | ||
| } | ||
|
|
||
| public function test_hex_to_oklch_matches_golden() { | ||
| $oklch = Slashed_Color_Math::hex_to_oklch( '#3b82f6' ); | ||
| $this->assertNotNull( $oklch ); | ||
| // Tolerance keeps the assertion stable against last-decimal float noise | ||
| // while still pinning the conversion to the current algorithm. | ||
| $this->assertEqualsWithDelta( 0.6231, $oklch[0], 0.0005 ); | ||
| $this->assertEqualsWithDelta( 0.1880, $oklch[1], 0.0005 ); | ||
| $this->assertEqualsWithDelta( 259.8145, $oklch[2], 0.001 ); | ||
| } | ||
|
|
||
| public function test_hex_to_oklch_rejects_non_hex() { | ||
| $this->assertNull( Slashed_Color_Math::hex_to_oklch( 'oklch(0.5 0.1 200)' ) ); | ||
| $this->assertNull( Slashed_Color_Math::hex_to_oklch( 'nope' ) ); | ||
| } | ||
|
|
||
| /** | ||
| * hex → oklch → hex must return the original colour (the two directions are | ||
| * inverse transforms). Guards against a matrix/gamma change breaking one | ||
| * direction without the other. | ||
| * | ||
| * @dataProvider round_trip_cases | ||
| */ | ||
| public function test_hex_oklch_round_trip( $hex ) { | ||
| $oklch = Slashed_Color_Math::hex_to_oklch( $hex ); | ||
| $this->assertNotNull( $oklch ); | ||
| $this->assertSame( $hex, Slashed_Color_Math::oklch_to_hex( $oklch[0], $oklch[1], $oklch[2] ) ); | ||
| } | ||
|
|
||
| /** | ||
| * @return array<string, array{0: string}> | ||
| */ | ||
| public function round_trip_cases() { | ||
| return array( | ||
| 'brand blue' => array( '#3b82f6' ), | ||
| 'black' => array( '#000000' ), | ||
| 'white' => array( '#ffffff' ), | ||
| 'mid grey' => array( '#808080' ), | ||
| 'orange' => array( '#ff8800' ), | ||
| ); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| /** | ||
| * color-model.js is maintained as two byte-identical copies — one for the | ||
| * Bricks editor-app, one for the Gutenberg editor bundle — because the two | ||
| * builders ship separate bundles with no shared module between them. The | ||
| * cross-impl test (color-model-cross-impl.test.js) only pins the *behaviour* | ||
| * of classifyVar(); every other export (buildColorModel, swatchHex, | ||
| * filterModel, FAMILY_INFO, …) is unguarded and could silently diverge if | ||
| * someone edits one copy and forgets the other. | ||
| * | ||
| * This test asserts the two files are identical byte-for-byte, so any edit to | ||
| * one that isn't mirrored in the other fails CI. When you change one copy, | ||
| * copy it verbatim to the other (they are intentionally the same file). | ||
| * | ||
| * Run: node --test tests/color-model-copies-identical.test.js | ||
| */ | ||
| import { test } from 'node:test'; | ||
| import assert from 'node:assert/strict'; | ||
| import { readFileSync } from 'node:fs'; | ||
| import path from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
|
|
||
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
| const ROOT = path.resolve(__dirname, '..'); | ||
|
|
||
| const BRICKS_COPY = path.join( | ||
| ROOT, | ||
| 'SLASHED-for-WP/integrations/bricks/editor-app/src/lib/color-model.js', | ||
| ); | ||
| const GUTENBERG_COPY = path.join( | ||
| ROOT, | ||
| 'SLASHED-for-WP/integrations/gutenberg/assets/editor/color-model.js', | ||
| ); | ||
|
|
||
| test('Bricks and Gutenberg color-model.js copies are byte-identical', () => { | ||
| const bricks = readFileSync(BRICKS_COPY, 'utf8'); | ||
| const gutenberg = readFileSync(GUTENBERG_COPY, 'utf8'); | ||
| assert.equal( | ||
| bricks, | ||
| gutenberg, | ||
| 'color-model.js has diverged between the Bricks and Gutenberg copies — ' + | ||
| 'they are intentionally identical. Copy your change to both files.', | ||
| ); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /** | ||
| * ELEMENT_TYPE_LABEL_MAP (JS, editor-app) and Slashed_Bricks_Settings_Page:: | ||
| * BUILTIN_DEFAULTS (PHP) are two hand-maintained copies of the same element- | ||
| * type → default-BEM-name table. The editor is the source of truth at | ||
| * pre-fill time; the PHP copy only feeds the settings-page placeholders. The | ||
| * PHP class carries a "KEEP IN SYNC" comment but nothing enforced it, so the | ||
| * two could drift and show users different defaults than they actually get. | ||
| * | ||
| * This test parses the PHP const array and asserts it equals the JS map | ||
| * exactly. If you change one, change the other. | ||
| * | ||
| * Run: node --test tests/element-types-mirror.test.js | ||
| */ | ||
| import { test } from 'node:test'; | ||
| import assert from 'node:assert/strict'; | ||
| import { readFileSync } from 'node:fs'; | ||
| import path from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| import { ELEMENT_TYPE_LABEL_MAP } from '../SLASHED-for-WP/integrations/bricks/editor-app/src/lib/element-types.js'; | ||
|
|
||
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
| const PHP_FILE = path.resolve( | ||
| __dirname, | ||
| '..', | ||
| 'SLASHED-for-WP/includes/class-bricks-settings-page.php', | ||
| ); | ||
|
|
||
| /** | ||
| * Extract the `const BUILTIN_DEFAULTS = array( ... );` block from the PHP file | ||
| * and parse its `'key' => 'value'` pairs into a plain object. | ||
| */ | ||
| function parsePhpBuiltinDefaults(src) { | ||
| const start = src.indexOf('const BUILTIN_DEFAULTS = array('); | ||
| assert.notEqual(start, -1, 'BUILTIN_DEFAULTS array not found in PHP source'); | ||
| const body = src.slice(start); | ||
| const end = body.indexOf(');'); | ||
| assert.notEqual(end, -1, 'end of BUILTIN_DEFAULTS array not found'); | ||
| const inner = body.slice(0, end); | ||
|
|
||
| const pairRe = /'([^']+)'\s*=>\s*'([^']+)'/g; | ||
| const map = {}; | ||
| let m; | ||
| while ((m = pairRe.exec(inner)) !== null) { | ||
| map[m[1]] = m[2]; | ||
| } | ||
| return map; | ||
| } | ||
|
|
||
| test('PHP BUILTIN_DEFAULTS mirrors JS ELEMENT_TYPE_LABEL_MAP exactly', () => { | ||
| const phpMap = parsePhpBuiltinDefaults(readFileSync(PHP_FILE, 'utf8')); | ||
| const jsMap = { ...ELEMENT_TYPE_LABEL_MAP }; | ||
|
|
||
| assert.ok(Object.keys(phpMap).length > 0, 'parsed PHP map is unexpectedly empty'); | ||
| assert.deepEqual( | ||
| phpMap, | ||
| jsMap, | ||
| 'PHP BUILTIN_DEFAULTS has drifted from JS ELEMENT_TYPE_LABEL_MAP — ' + | ||
| 'update both (class-bricks-settings-page.php and element-types.js).', | ||
| ); | ||
| }); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.