Skip to content

feat(bricks): add color swatches to variable-picker dropdown - #184

Merged
jackgranatowski merged 2 commits into
mainfrom
devin/1780306701-bricks-variable-picker-swatches
Jun 1, 2026
Merged

feat(bricks): add color swatches to variable-picker dropdown#184
jackgranatowski merged 2 commits into
mainfrom
devin/1780306701-bricks-variable-picker-swatches

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Restores colour swatches in the Bricks builder without re-introducing the dark-mode bug that #182 fixed. Closes the follow-up tracked in #183.

Since #182 disabled Color-Palette injection on Bricks 2.2+ (the Color Manager bakes palette colours into :root as static hex, freezing light-dark()), --sf-color-* tokens are now reached via the Variable Manager. Those variables are deliberately empty-valued so nothing hits :root — which also means Bricks has no value to draw a swatch from, so the picker shows plain text.

This paints the swatch back in, builder-side only, the way CORE Framework does it: a small colour square is prepended to each --sf-color-* row in the variable-picker dropdown. The colour comes from the existing server-resolved hex map (Slashed_Bricks_Inventory::get_color_hex_map()), localised alongside the existing class-hints config — the builder panel doesn't load the SLASHED stylesheet, so var() can't resolve there. Nothing is written to :root; dark/light stays 100% framework-driven.

How it hooks the picker

The dropdown markup (captured from a live 2.2 install) is stable and semantic:

<li class="variable-picker-item">
  <span title="sf-color-primary">sf-color-primary</span>  <!-- name (no leading --) -->
  <span class="option-value"></span>                      <!-- empty for our vars -->
</li>

A debounced MutationObserver reconciles each row on every pass rather than stamping once: Bricks (Vue) reuses <li> nodes when the search box filters, so a row that was sf-color-action can become sf-color-primary — or a non-colour variable — while keeping the same node. Each pass recomputes the desired colour and adds / updates / removes the swatch accordingly, which is reuse-safe where a one-time marker would go stale.

// color-swatches.js — pure, DOM-free, unit-testable
resolveSwatchColor('sf-color-primary', hexMap) // -> '#…' (only for known --sf-color-* keys)
resolveSwatchColor('sf-spacing-m',     hexMap) // -> null  (harmless in spacing/typography pickers)

Design guarantees

  • Additive & fail-silent. Bricks renders the list, names and canvas hover-preview natively; we only prepend a square. If the markup ever changes or anything throws, you lose the squares — never the picker. (Confirmed with the user as a hard requirement.)
  • Reuses the proven class-hints.js pattern (scoped, idempotent, AbortSignal teardown) in the same editor-app bundle.
  • Opaque vs alpha tokens: the colour is layered on top of a checkerboard, so translucent -a* tokens read as transparent at a glance.
  • Toggle: add_filter('slashed_bricks/show_color_swatches', '__return_false') — also skips localising the hex map when off.

Changes

  • editor-app/src/lib/color-swatches.js (new) — observer + resolveSwatchColor() reconciler.
  • editor-app/src/main.js — init in start(), teardown in beforeunload.
  • editor-app/src/styles/panel.css.slashed-var-swatch styling (checkerboard underlay).
  • includes/class-rebemer-enqueue.php — localise showColorSwatches + colorHexMap; new filter.
  • assets/editor-app/app.{js,css} — rebuilt bundle.
  • README.md — feature + filter docs.

Testing

  • php -l on the modified PHP — clean.
  • npm run lint:css (repo root) — clean.
  • npm run build in editor-app/ — builds; swatch code present in the committed bundle.
  • I can't run a live Bricks 2.2 builder here. User verification on a live install: open a colour field → Variables dropdown → squares appear next to each --sf-color-* entry; selecting one and toggling dark/light still recolours the canvas; if the script is disabled/broken the dropdown still lists and selects every variable.

Refs #183

Link to Devin session: https://app.devin.ai/sessions/113c6a747fc6406c9510e0e88892441f
Requested by: @jackgranatowski

Summary by CodeRabbit

  • New Features

    • Added optional color swatches in the Bricks builder variable picker for SLASHED color tokens, displaying the actual color alongside variable names for easier identification and selection.
  • Documentation

    • Updated integration documentation to describe the new color swatches feature and its configuration options.

Paint a colour square next to each --sf-color-* entry in the Bricks
variable-picker dropdown, builder-side only. Variables stay empty-valued
so nothing hits :root and dark/light remains framework-driven; the swatch
is rendered from the server-resolved hex map (get_color_hex_map),
localised alongside the existing class-hints config.

Additive and fail-silent, reusing the class-hints decoration pattern:
a debounced MutationObserver reconciles each row every pass (Bricks/Vue
reuses <li> nodes when the search filter runs, so a one-time marker would
go stale). resolveSwatchColor() is pure/DOM-free and only matches known
--sf-color-* tokens, so the same observer is harmless in the spacing /
typography pickers.

Restores the swatch affordance lost when the Color Palette injection was
disabled on Bricks 2.2+ (#182). Toggle via slashed_bricks/show_color_swatches.

Refs #183

Co-Authored-By: Jack Granatowski <jack.granatowski@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@jackgranatowski

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 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 Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

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: 1b6eb4d5-63a5-4f32-88a4-abdbd355ae0e

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 introduces an optional Bricks builder feature that displays color swatches next to --sf-color-* variable picker entries. The feature is controlled by a server-side filter, receives a hex color map via script localization, and runs a debounced mutation observer to inject/remove swatch elements as the picker re-renders.

Changes

Bricks Builder Color Swatches

Layer / File(s) Summary
Backend data localization
plugins/SLASHED-for-WP/integrations/bricks/includes/class-rebemer-enqueue.php
Introduces a slashed_bricks/show_color_swatches filter to gate swatch rendering and conditionally retrieves a hex color map from Slashed_Bricks_Inventory, then passes both into the slashedBricksEditor wp_localize_script payload.
Color swatch DOM module
plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/color-swatches.js
New module exports resolveSwatchColor() to normalize variable names and map to validated hex strings, init() to set up a debounced mutation observer and inject swatch spans, and destroy() to clean up the observer and remove injected elements. Idempotently reconciles swatch presence on each pass.
Styling and editor lifecycle wiring
plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/styles/panel.css, plugins/SLASHED-for-WP/integrations/bricks/assets/editor-app/app.css, plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/main.js
Panel and app CSS define .slashed-var-swatch styling (12×12 inline block, checkerboard background, --slashed-swatch-color overlay). Main entry-point imports the module and calls init() with localized flags and hex map during setup, and destroy() during beforeunload cleanup.
Feature documentation
plugins/SLASHED-for-WP/integrations/bricks/README.md
Documents the swatch feature, filter default behavior, and example showing how builders can disable swatches via filter hook.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • codeslash-dev/SLASHED#183: This PR directly implements the builder-side color-swatch feature described in the issue, adding the JS module, wiring it into the editor lifecycle, and localizing the hex map data.

Possibly related PRs

  • codeslash-dev/SLASHED#97: The color swatch module depends on the Slashed_Bricks_Inventory::get_color_hex_map() function added in this PR to resolve token hex values for display.
  • codeslash-dev/SLASHED#103: This PR updates hex map generation to include admin-saved overrides and correct hex handling, which directly affects the swatch color output rendered by the new feature.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main feature being added: color swatches in the Bricks variable-picker dropdown, matching the primary objective of the changeset.
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 devin/1780306701-bricks-variable-picker-swatches

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.

🧹 Nitpick comments (1)
plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/color-swatches.js (1)

186-187: 💤 Low value

Consider the performance impact of observing document.body.

Observing document.body with { childList: true, subtree: true } will trigger on every DOM mutation in the page, not just the variable picker. While the debounced schedule() mitigates excessive reconciliation passes, you might consider narrowing the observation scope if the picker is mounted in a known container.

However, the comment on lines 181-185 explains that the picker is "created, destroyed, and re-rendered" by Bricks dynamically, which may justify the broad scope. If the picker's container is predictable, you could observe that instead of body.

🤖 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
`@plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/color-swatches.js`
around lines 186 - 187, The code currently creates a MutationObserver named
_observer and calls _observer.observe(document.body, { childList: true, subtree:
true }), which can be expensive; change the observer target to a narrower
container when possible by finding the picker's mount element (e.g., the Bricks
picker container or its parent) instead of document.body, and fall back to
document.body only if the picker container cannot be determined. Update the
logic around _observer and the existing schedule() usage so observe() is invoked
on that container element (or document.body fallback) and ensure
disconnect/reconnect behavior still uses _observer to avoid leaking observers
when the picker is destroyed and recreated.
🤖 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.

Nitpick comments:
In
`@plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/color-swatches.js`:
- Around line 186-187: The code currently creates a MutationObserver named
_observer and calls _observer.observe(document.body, { childList: true, subtree:
true }), which can be expensive; change the observer target to a narrower
container when possible by finding the picker's mount element (e.g., the Bricks
picker container or its parent) instead of document.body, and fall back to
document.body only if the picker container cannot be determined. Update the
logic around _observer and the existing schedule() usage so observe() is invoked
on that container element (or document.body fallback) and ensure
disconnect/reconnect behavior still uses _observer to avoid leaking observers
when the picker is destroyed and recreated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 926845a3-c3f4-4fee-aaaa-42b5657f2242

📥 Commits

Reviewing files that changed from the base of the PR and between c3eab68 and e51c5f9.

📒 Files selected for processing (7)
  • plugins/SLASHED-for-WP/integrations/bricks/README.md
  • plugins/SLASHED-for-WP/integrations/bricks/assets/editor-app/app.css
  • plugins/SLASHED-for-WP/integrations/bricks/assets/editor-app/app.js
  • plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/color-swatches.js
  • plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/main.js
  • plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/styles/panel.css
  • plugins/SLASHED-for-WP/integrations/bricks/includes/class-rebemer-enqueue.php

Filter the body-level MutationObserver with touchesPicker() so only
mutations inside the variable picker (a row patch or the dropdown
subtree being added) schedule a reconcile pass. Unrelated builder churn
(canvas edits, structure-panel rebuilds) no longer wakes the reconciler.
Keeps the robust body scope (the picker has no container that's stable
across its whole lifecycle) while removing the wasted passes CodeRabbit
flagged.

Co-Authored-By: Jack Granatowski <jack.granatowski@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@coderabbitai Addressed the observer-scope nitpick in 6a49975. I kept the document.body scope (the picker dropdown is created/destroyed/re-rendered by Bricks with no container that stays mounted across its whole lifecycle), but added a cheap touchesPicker() pre-filter so the reconciler only wakes when a mutation actually touches a .variable-picker-item (a reused-row text patch) or adds a subtree containing one (dropdown open). Canvas edits and structure-panel rebuilds no longer trigger any passes.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the updated color-swatches.js after commit 6a49975
git show 6a49975 -- plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/color-swatches.js 2>/dev/null || \
  cat plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/color-swatches.js 2>/dev/null || \
  fd "color-swatches.js" --exec cat {}

Length of output: 4181



@devin-ai-integration[bot] The touchesPicker() implementation looks correct and well-reasoned. A few notes from reviewing the diff:

What's working well:

  • Target check (t.nodeType === 1 && t.closest(ITEM_SELECTOR)) correctly catches childList mutations whose target is an existing row or a child element within one — exactly the Bricks node-reuse pattern during search filtering.
  • Added-nodes check (node.matches(ITEM_SELECTOR) || node.querySelector(ITEM_SELECTOR)) correctly catches the dropdown subtree being mounted on open.
  • Since the observer is configured with only { childList: true, subtree: true } (no characterData: true), the nodeType === 1 gate on the target is complete — text-node character data mutations are never reported, so nothing slips through.
  • The defensive guards (t.closest &&, node.matches &&, node.querySelector &&) are a nice touch for environments where these methods might not exist on every node type.
  • The inline JSDoc clearly explains the two mutation patterns and why document.body scope is retained — useful context for future maintainers.

One minor edge case to be aware of (non-blocking): If Bricks ever replaces the entire picker container (rather than adding rows into it), the addedNode would be the container element, not the rows themselves. The node.querySelector(ITEM_SELECTOR) branch handles this correctly since it searches the subtree — so that case is already covered.

The fix cleanly addresses the concern without sacrificing robustness.

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.

1 participant