Skip to content

feat(bricks): inject all framework classes and variables into Bricks UI - #81

Merged
jackgranatowski merged 2 commits into
mainfrom
feat/bricks-dynamic-inventory
May 24, 2026
Merged

feat(bricks): inject all framework classes and variables into Bricks UI#81
jackgranatowski merged 2 commits into
mainfrom
feat/bricks-dynamic-inventory

Conversation

@kiro-agent

@kiro-agent kiro-agent Bot commented May 24, 2026

Copy link
Copy Markdown

This pull request was created by @kiro-agent on behalf of @jackgranatowski 👻

Comment with /kiro fix to address specific feedback or /kiro all to address everything.
Learn about Kiro autonomous agent


Why

The Bricks integration was supposed to populate the Bricks Builder UI with every SLASHED token and class so users get a complete picker / autocomplete experience. In practice it was hand-curating those lists in PHP and missing roughly half of them:

Surface Before After (optimal bundle)
--sf-* variables registered 332 603
.sf-* classes registered 100 (some stale) 123
.is-* classes registered 39 40
--sf-color-* swatches in palette ~76 275

Entire token families were absent — animations, blur, body/headings, borders, focus rings, gradients, line, opacity, optical, perspective, ratio, scroll, scrollbar, stroke, the whole alpha color scale (-a5-a95), etc. Every framework release silently broke parity with the Bricks UI.

What changed

Replace the hardcoded enumeration with runtime parsing of the loaded CSS bundle, so registrations always match the framework exactly.

New files

  • integrations/bricks/includes/class-css-parser.php — pure parser. Strips comments, returns declared --sf-* properties and .sf-* / .is-* selectors.
  • integrations/bricks/includes/class-inventory.php — resolves the active bundle (local file → CDN URL with transient cache → built-in JSON fallback) and categorizes variables by prefix family.
  • integrations/bricks/data/inventory.json — fallback inventory (603 vars / 123 .sf- / 40 .is-) generated from dist/slashed.optimal.css. Keeps the plugin functional on hosts that block outbound HTTP.
  • scripts/gen-bricks-inventory.js — regenerates the fallback at release time. Wired into npm run build.

Refactored

  • class-variables.php, class-classes.php, class-colors.php — all now delegate to the inventory. Removed several hundred lines of stale hardcoded lists.

Preserved

  • All existing public APIs and filter hooks (slashed_bricks/registered_variables, slashed_bricks/registered_classes, slashed_bricks/registered_colors, slashed_bricks/color_categories).

New filters

  • slashed_bricks/inventory — replace the resolved inventory wholesale.
  • slashed_bricks/inventory_local_path — authoritative local-path override (string use that path · false skip local · null use defaults).

How it works at runtime

The inventory tries sources in order:

  1. Local file (dist/slashed.optimal.css next to the plugin). Cached as a transient keyed by mtime, so edits invalidate automatically.
  2. CDN URL from slashed_bricks_get_css_url(). Fetched via wp_remote_get, cached for one day.
  3. Built-in fallback (data/inventory.json).

The first source that succeeds wins. All three registration classes share one process-local cache so a page load incurs at most one parse.

Validation

A harness mocking the WP functions the integration uses was run against the real dist/slashed.optimal.css. All 27 assertions pass:

  • Inventory parses bundle 1:1 (603 / 123 / 40)
  • Variables registry covers every declared --sf-* (no misses)
  • Categorization assigns 602/603 vars to a typed bucket; only --sf-truncate-suffix lands in "Misc" (correct — it's a content fragment, not a typed token)
  • Classes registry exactly matches the bundle, all entries locked: true
  • Color palette emits 275 swatches, all raw: var(--sf-color-…) references; brand categories appear in canonical order; labels render correctly (e.g. "SF Primary 50", "SF Primary A20")
  • Strict fallback test (local disabled + CDN returning WP_Error) confirms data/inventory.json is loaded successfully

Tested

  • php -l clean across all six PHP files
  • Pre-commit hook ran the full bundle build cleanly (10 distributions)
  • commitlint passes (conventional commit format)

Known limitations

  • The runtime parser only sees what's loaded. If a site uses the slashed_bricks/css_bundle_url filter to point at a non-SLASHED stylesheet, the inventory will reflect that file (not the framework). This is the correct behavior — the Bricks UI should match what's actually applied — but worth flagging for users with unusual setups.
  • The fallback JSON is generated from slashed.optimal.css. Sites running slashed.essential.css will still see the optimal-superset inventory if both local and remote resolution fail. In practice this only affects offline-only installs that are also using a non-default bundle.

Summary by CodeRabbit

  • New Features

    • Bricks Builder integration now dynamically sources and registers layout classes, state classes, color palettes, and variables from the active CSS bundle at runtime
    • Added new WordPress filter hooks for customizing token inventory and local CSS path resolution
  • Documentation

    • Expanded Bricks integration documentation with architecture overview and filter hook references

Review Change Stack

The integration previously hand-curated Variables, Classes, and Colors via
hardcoded lists in PHP, leaving roughly half the framework's tokens
(animations, blur, body/headings, borders, focus rings, gradients, line,
opacity, optical, perspective, ratio, scroll, scrollbar, stroke, the alpha
color scale a5-a95, etc.) and several class selectors absent from the
Bricks UI. The lists also drifted out of sync with each release.

Replace the hardcoded enumeration with runtime parsing of the loaded CSS
bundle, so registrations always match the framework exactly:

- class-css-parser.php   pure parser: declared --sf-* properties + .sf-/
                         .is- selectors from any CSS string
- class-inventory.php    resolves the active bundle (local file > CDN URL
                         with transient cache > built-in JSON fallback),
                         categorizes variables by prefix family
- data/inventory.json    fallback inventory (regenerated at release time
                         by scripts/gen-bricks-inventory.js, hooked into
                         npm run build)

Refactor class-variables/classes/colors to delegate to the inventory.
Public APIs and existing filters (slashed_bricks/registered_*,
slashed_bricks/color_categories) are preserved; new filters
slashed_bricks/inventory and slashed_bricks/inventory_local_path let
sites override resolution.

Validation against dist/slashed.optimal.css confirms 100% coverage:
603 variables (was 332), 123 .sf-* classes (was 141 with several stale
entries), 40 .is-* classes, and 275 color swatches in the global palette
(was ~76).

Co-authored-by: Jack Granatowski <contact@codeslash.net>
@jackgranatowski

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cbdedf56-6ffd-401a-b9c2-4131923a4763

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
📝 Walkthrough

Walkthrough

This PR refactors the Bricks integration to register classes, colors, and variables dynamically from a centralized CSS bundle inventory rather than hardcoded lists. A new Slashed_Bricks_Inventory resolves CSS from local, remote, or fallback sources with caching; a Slashed_Bricks_CSS_Parser extracts variables and classes; and build tooling generates fallback inventory JSON. Registration classes now iterate inventory data, replacing static enumerations.

Changes

Bricks Integration Inventory-Driven Registration

Layer / File(s) Summary
Architecture & Filter Documentation
integrations/bricks/README.md
Features, Architecture, and "How It Works" sections updated to describe dynamic CSS parsing, new filter hooks (slashed_bricks/inventory, slashed_bricks/inventory_local_path), inventory resolution order (local → remote → fallback), and npm build commands.
CSS Parser Utility
integrations/bricks/includes/class-css-parser.php
New Slashed_Bricks_CSS_Parser extracts --sf-* variables and .sf-*/.is-* class selectors from CSS strings via regex, with comment stripping and sorted unique lists.
Inventory Resolution & Categorization
integrations/bricks/includes/class-inventory.php
New Slashed_Bricks_Inventory resolves CSS bundles from local files (with filter override), remote URLs (with transient caching), or fallback JSON; provides categorized getters and per-request caching with slashed_bricks/inventory filter support.
Fallback Inventory Build Tooling
package.json, scripts/gen-bricks-inventory.js
Node.js script gen-bricks-inventory.js parses the built CSS bundle and generates fallback inventory JSON; package.json chains inventory generation into the build step and adds explicit bricks:inventory npm command.
Fallback Inventory Data
integrations/bricks/data/inventory.json
Generated data file containing metadata and enumerated lists of CSS variables, .sf-* layout classes, and .is-* state classes.
Plugin Initialization Wiring
integrations/bricks/slashed-bricks.php
Loads CSS parser and inventory resolver classes early in slashed_bricks_init() before other registration components.
Dynamic Class Registration
integrations/bricks/includes/class-classes.php
get_classes() now iterates Slashed_Bricks_Inventory::get_sf_classes() and get_is_classes() instead of hardcoded enumerations; register_global_classes defensively initializes globalClassesLocked array.
Dynamic Color Swatch Generation
integrations/bricks/includes/class-colors.php
get_colors() builds category mappings from inventory's --sf-color-* variables; new helpers parse variable names, assign to brand/status/semantic categories, and generate deterministic swatch IDs; register_global_colors defensively initializes globalColors.
Dynamic Variable Categorization & Registration
integrations/bricks/includes/class-variables.php
get_variables() calls Slashed_Bricks_Inventory::get_variables_by_category() instead of returning hardcoded maps; register_variable_groups() and register_code_signatures() defensively coerce input arrays and populate autocomplete/i18n from inventory categories.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • codeslash-dev/SLASHED#71: Introduced the original hardcoded class/color/variable registration logic that this PR refactors to use the new inventory source of truth.
  • codeslash-dev/SLASHED#74: Modifies slashed_bricks_get_css_url() to default to jsDelivr CDN, directly affecting what CSS bundle the new inventory resolver will fetch and parse.
  • codeslash-dev/SLASHED#77: Changes the same slashed_bricks_get_css_url() CDN/reference behavior that the inventory resolver depends on for remote CSS resolution.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: injecting dynamic framework classes and variables into the Bricks UI via runtime inventory resolution, replacing hardcoded lists.
Docstring Coverage ✅ Passed Docstring coverage is 94.29% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bricks-dynamic-inventory

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
integrations/bricks/includes/class-colors.php (1)

28-40: ⚡ Quick win

Derive color family buckets from the inventory instead of static prefix lists.

build_categories() still depends on the fixed $brands / $statuses arrays, so a new --sf-color-<family>-* palette will be registered but silently grouped under SLASHED Semantic until this file is updated. That reintroduces the drift this PR is trying to remove. Consider deriving first-segment families from the inventory itself, or at least making the recognized family lists filterable.

Also applies to: 111-139, 176-188

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integrations/bricks/includes/class-colors.php` around lines 28 - 40, The
current static lists private static $brands and private static $statuses cause
build_categories() to misgroup newly-registered palettes; update
class-colors.php so family buckets are derived from the actual inventory rather
than hard-coded prefixes (or at minimum make these lists filterable).
Specifically, modify the logic in build_categories() (and any helper that
references $brands/$statuses) to iterate the inventory keys (first segment of
the palette slug) to compute canonical families dynamically, falling back to the
existing arrays only if no inventory-derived families exist; alternatively
expose $brands and $statuses via apply_filters so consumers can add families.
Ensure any references at the other noted regions (lines around 111-139 and
176-188) use the new dynamic/filerable family source rather than the static
arrays.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@integrations/bricks/includes/class-inventory.php`:
- Around line 329-349: The resolve() logic currently prefers a local bundle
(find_local_bundle_path() -> parse_path_with_cache()) even when the enqueued
bundle URL (slashed_bricks_get_css_url() or the slashed_bricks/css_bundle_url
override) points at a different remote bundle; update resolve() so it first
determines the final bundle URL (use slashed_bricks_get_css_url() and apply the
slashed_bricks/css_bundle_url override) and then choose the inventory source
that matches that URL — if the final URL is a remote URL call
parse_url_with_cache($url), otherwise if it points to a local path or the local
bundle name matches use parse_path_with_cache($local_path); ensure both
resolve() and the same logic area around lines 369-392 are aligned to avoid
reading variables from the wrong bundle.
- Around line 66-76: The filter runs before the cache is primed which allows
re-entrant calls to Slashed_Bricks_Inventory::get_*() to recurse; to fix, prime
the cache with the resolved inventory before calling apply_filters so recursive
get() calls read the primed value: set self::$cache (or an "in-progress"
sentinel) to $inventory returned by self::resolve() first, then call
apply_filters( 'slashed_bricks/inventory', self::$cache ) and assign its result
back to self::$cache; update the get()/resolve() logic accordingly to respect
the primed cache and avoid infinite recursion.

---

Nitpick comments:
In `@integrations/bricks/includes/class-colors.php`:
- Around line 28-40: The current static lists private static $brands and private
static $statuses cause build_categories() to misgroup newly-registered palettes;
update class-colors.php so family buckets are derived from the actual inventory
rather than hard-coded prefixes (or at minimum make these lists filterable).
Specifically, modify the logic in build_categories() (and any helper that
references $brands/$statuses) to iterate the inventory keys (first segment of
the palette slug) to compute canonical families dynamically, falling back to the
existing arrays only if no inventory-derived families exist; alternatively
expose $brands and $statuses via apply_filters so consumers can add families.
Ensure any references at the other noted regions (lines around 111-139 and
176-188) use the new dynamic/filerable family source rather than the static
arrays.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d96c044-6a29-47e0-b452-97ed21fe9996

📥 Commits

Reviewing files that changed from the base of the PR and between 175f26c and c989206.

📒 Files selected for processing (10)
  • integrations/bricks/README.md
  • integrations/bricks/data/inventory.json
  • integrations/bricks/includes/class-classes.php
  • integrations/bricks/includes/class-colors.php
  • integrations/bricks/includes/class-css-parser.php
  • integrations/bricks/includes/class-inventory.php
  • integrations/bricks/includes/class-variables.php
  • integrations/bricks/slashed-bricks.php
  • package.json
  • scripts/gen-bricks-inventory.js

Comment on lines +66 to +76
$inventory = self::resolve();

/**
* Filter the resolved inventory before it's used to register
* variables, classes, and colors with Bricks.
*
* @param array $inventory ['variables', 'sf_classes', 'is_classes'].
*/
self::$cache = apply_filters( 'slashed_bricks/inventory', $inventory );

return self::$cache;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent re-entrant recursion during inventory filtering.

At Line 74, apply_filters( 'slashed_bricks/inventory', ...) runs before self::$cache is primed. If a filter callback calls Slashed_Bricks_Inventory::get_*(), get() re-enters and can recurse indefinitely.

Suggested fix
 public static function get() {
 	if ( null !== self::$cache ) {
 		return self::$cache;
 	}

-	$inventory = self::resolve();
+	$inventory = self::resolve();
+	// Prime cache before filters to prevent re-entrant recursion.
+	self::$cache = self::sanitize_inventory( $inventory );

 	/**
 	 * Filter the resolved inventory before it's used to register
 	 * variables, classes, and colors with Bricks.
 	 *
 	 * `@param` array $inventory ['variables', 'sf_classes', 'is_classes'].
 	 */
-	self::$cache = apply_filters( 'slashed_bricks/inventory', $inventory );
+	$filtered   = apply_filters( 'slashed_bricks/inventory', self::$cache );
+	self::$cache = self::sanitize_inventory( $filtered );

 	return self::$cache;
 }
+
+private static function sanitize_inventory( $inventory ) {
+	$base = Slashed_Bricks_CSS_Parser::empty_inventory();
+	if ( ! is_array( $inventory ) ) {
+		return $base;
+	}
+	foreach ( array( 'variables', 'sf_classes', 'is_classes' ) as $key ) {
+		$base[ $key ] = isset( $inventory[ $key ] ) && is_array( $inventory[ $key ] )
+			? array_values( array_filter( $inventory[ $key ], 'is_string' ) )
+			: array();
+		$base[ $key ] = array_values( array_unique( $base[ $key ] ) );
+		sort( $base[ $key ] );
+	}
+	return $base;
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integrations/bricks/includes/class-inventory.php` around lines 66 - 76, The
filter runs before the cache is primed which allows re-entrant calls to
Slashed_Bricks_Inventory::get_*() to recurse; to fix, prime the cache with the
resolved inventory before calling apply_filters so recursive get() calls read
the primed value: set self::$cache (or an "in-progress" sentinel) to $inventory
returned by self::resolve() first, then call apply_filters(
'slashed_bricks/inventory', self::$cache ) and assign its result back to
self::$cache; update the get()/resolve() logic accordingly to respect the primed
cache and avoid infinite recursion.

Comment on lines +329 to +349
private static function resolve() {
// 1. Local file - cheapest, also handles offline development.
$local_path = self::find_local_bundle_path();
if ( '' !== $local_path ) {
$inventory = self::parse_path_with_cache( $local_path );
if ( ! empty( $inventory['variables'] ) ) {
return $inventory;
}
}

// 2. Remote URL (CDN). Cached as a transient.
$url = function_exists( 'slashed_bricks_get_css_url' )
? slashed_bricks_get_css_url()
: '';

if ( '' !== $url && self::is_remote_url( $url ) ) {
$inventory = self::parse_url_with_cache( $url );
if ( ! empty( $inventory['variables'] ) ) {
return $inventory;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Keep inventory source aligned with the final CSS bundle URL.

Lines 331-337 always prefer local dist/slashed.optimal.css, but the actual enqueued bundle can be overridden via slashed_bricks/css_bundle_url (integrations/bricks/slashed-bricks.php Line 76). In environments where local dist/ exists, this can register tokens from optimal while the site loads essential, full, or a custom URL.

Direction for fix
 private static function find_local_bundle_path() {
 	$override = apply_filters( 'slashed_bricks/inventory_local_path', null );
@@
 	if ( is_string( $override ) ) {
 		return ( '' !== $override && file_exists( $override ) ) ? $override : '';
 	}
+
+	// If the final CSS URL was overridden away from default local bundle URLs,
+	// skip implicit local candidates to avoid inventory/CSS drift.
+	$resolved_url = function_exists( 'slashed_bricks_get_css_url' )
+		? slashed_bricks_get_css_url()
+		: '';
+	$default_local_urls = array(
+		SLASHED_BRICKS_URL . '../../dist/slashed.optimal.css',
+		SLASHED_BRICKS_URL . 'dist/slashed.optimal.css',
+	);
+	if ( '' !== $resolved_url && ! in_array( $resolved_url, $default_local_urls, true ) ) {
+		return '';
+	}

Also applies to: 369-392

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integrations/bricks/includes/class-inventory.php` around lines 329 - 349, The
resolve() logic currently prefers a local bundle (find_local_bundle_path() ->
parse_path_with_cache()) even when the enqueued bundle URL
(slashed_bricks_get_css_url() or the slashed_bricks/css_bundle_url override)
points at a different remote bundle; update resolve() so it first determines the
final bundle URL (use slashed_bricks_get_css_url() and apply the
slashed_bricks/css_bundle_url override) and then choose the inventory source
that matches that URL — if the final URL is a remote URL call
parse_url_with_cache($url), otherwise if it points to a local path or the local
bundle name matches use parse_path_with_cache($local_path); ensure both
resolve() and the same logic area around lines 369-392 are aligned to avoid
reading variables from the wrong bundle.

Two bugs surfaced by CodeRabbit review on PR #81, both Major:

1. Re-entrant recursion in Slashed_Bricks_Inventory::get()
   The 'slashed_bricks/inventory' filter ran before self::$cache was
   primed. A filter callback calling Slashed_Bricks_Inventory::get_*()
   from inside itself (a legitimate use case for plugins extending the
   token list) would re-enter get(), call resolve() again, and recurse
   indefinitely.

   Fix: prime the cache with the resolved+sanitised inventory FIRST,
   then apply the filter, then re-sanitise and re-cache the filter's
   return. Recursive get() calls now short-circuit on the cache check
   in the first three lines.

2. Inventory drifted from the active CSS bundle URL
   find_local_bundle_path() always probed dist/slashed.optimal.css,
   ignoring whatever URL slashed_bricks/css_bundle_url resolved to.
   Sites that filtered the URL to load 'essential' or 'full' got
   tokens registered from 'optimal' - so Bricks UI showed tokens that
   didn't exist in the loaded CSS.

   Fix: derive the local path from slashed_bricks_get_css_url(). Map
   the URL back to a filesystem path when it lives under the plugin's
   URL space (covers both copy-install and symlink-in-repo modes);
   fall through to remote fetch otherwise. inventory_local_path filter
   still wins over both, and returning false still skips local
   resolution entirely.

Also adds defensive sanitize_inventory() that normalises arbitrary
filter outputs (null, partial arrays, non-string entries) into the
canonical {variables, sf_classes, is_classes} shape so downstream
registration code never sees malformed data.

Validated end-to-end via mock-WP harness covering both fixes plus
PR #81 coverage baseline: 22/22 checks pass.
- Recursion test: filter callback calls get_variables() inside itself,
  returns 603 vars, callback runs exactly once, resolves in <2ms.
- URL drift test: switching slashed_bricks/css_bundle_url between
  essential/optimal/full and CDN paths makes the inventory follow.
- Sanitiser tests: dedup + sort + drop-non-strings + handle-null.
- Coverage sanity: 603 vars, 123 sf, 40 is, 17 categories, all
  unchanged from PR #81 baseline.
@jackgranatowski
jackgranatowski merged commit 730d583 into main May 24, 2026
5 checks passed
jackgranatowski pushed a commit that referenced this pull request May 25, 2026
Two bugs surfaced by CodeRabbit review on PR #81, both Major:

1. Re-entrant recursion in Slashed_Bricks_Inventory::get()
   The 'slashed_bricks/inventory' filter ran before self::$cache was
   primed. A filter callback calling Slashed_Bricks_Inventory::get_*()
   from inside itself (a legitimate use case for plugins extending the
   token list) would re-enter get(), call resolve() again, and recurse
   indefinitely.

   Fix: prime the cache with the resolved+sanitised inventory FIRST,
   then apply the filter, then re-sanitise and re-cache the filter's
   return. Recursive get() calls now short-circuit on the cache check
   in the first three lines.

2. Inventory drifted from the active CSS bundle URL
   find_local_bundle_path() always probed dist/slashed.optimal.css,
   ignoring whatever URL slashed_bricks/css_bundle_url resolved to.
   Sites that filtered the URL to load 'essential' or 'full' got
   tokens registered from 'optimal' - so Bricks UI showed tokens that
   didn't exist in the loaded CSS.

   Fix: derive the local path from slashed_bricks_get_css_url(). Map
   the URL back to a filesystem path when it lives under the plugin's
   URL space (covers both copy-install and symlink-in-repo modes);
   fall through to remote fetch otherwise. inventory_local_path filter
   still wins over both, and returning false still skips local
   resolution entirely.

Also adds defensive sanitize_inventory() that normalises arbitrary
filter outputs (null, partial arrays, non-string entries) into the
canonical {variables, sf_classes, is_classes} shape so downstream
registration code never sees malformed data.

Validated end-to-end via mock-WP harness covering both fixes plus
PR #81 coverage baseline: 22/22 checks pass.
- Recursion test: filter callback calls get_variables() inside itself,
  returns 603 vars, callback runs exactly once, resolves in <2ms.
- URL drift test: switching slashed_bricks/css_bundle_url between
  essential/optimal/full and CDN paths makes the inventory follow.
- Sanitiser tests: dedup + sort + drop-non-strings + handle-null.
- Coverage sanity: 603 vars, 123 sf, 40 is, 17 categories, all
  unchanged from PR #81 baseline.
jackgranatowski pushed a commit that referenced this pull request May 25, 2026
Brings main (post PR #86 chevron change, PR #81-85 bricks integration
work, PR #87-90 Dependabot bumps) into the v0.4 branch so PR #91 can
be merged cleanly. The 16 *.min.css and *.min.css.map files conflicted
on git merge because they're single-line bundles where every change
collides; resolved by rebuilding from the merged source via npm run
build. The unminified .css and .flat.css bundles auto-merged cleanly
because the changes are in non-overlapping line ranges (chevron region
in optional/forms.css from main vs new tokens/macros/themes/layout
from v0.4).

Regenerated artefacts:
- dist/* via scripts/bundle.js
- docs/tokens.md via scripts/gen-token-reference.js (608 tokens)
- integrations/bricks/data/inventory.json via scripts/gen-bricks-inventory.js

Validation: npm run lint:css clean, 149/149 chromium playwright tests
pass (token-API snapshot lock, bundle-size budget, link-contrast suite,
macros, tokens resolution, layers ordering, behavior, demo coverage,
demo visual).
@jackgranatowski
jackgranatowski deleted the feat/bricks-dynamic-inventory branch May 31, 2026 18:00
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.

2 participants