Skip to content

feat(bricks): reBEMer MVP — subtree BEM class manager - #125

Merged
jackgranatowski merged 3 commits into
mainfrom
feat/rebemer-mvp
May 27, 2026
Merged

feat(bricks): reBEMer MVP — subtree BEM class manager#125
jackgranatowski merged 3 commits into
mainfrom
feat/rebemer-mvp

Conversation

@kiro-agent

@kiro-agent kiro-agent Bot commented May 27, 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


reBEMer MVP — subtree BEM class manager

Stripped-down MVP of reBEMer: add/rename/replace BEM classes for an element and its children via the Bricks Builder structure panel.

What it does

  • BEM badge on every structure-panel item
  • Click → opens a panel listing the element + all descendants
  • Name the block, each child gets block__element automatically
  • Four modes: Add, Rename, Replace, Add Modifier
  • Optional "Sync labels" updates structure-panel labels to match

What's NOT in this MVP (intentionally deferred)

  • No REST preflight / cross-post reference counting
  • No configurable naming policy (hardcoded two-dash ASCII kebab)
  • No custom undo (relies on Bricks' native Cmd-Z)
  • No i18n (English only)
  • No settings persistence (defaults every open)
  • No reserved-name guard beyond CSS keywords
  • No tests (manual smoke-testing in real Bricks install)

File counts

  • ~880 LOC total (down from ~2,650 in the over-engineered v1)
  • 10 source files in editor-app + 1 PHP class
  • Build output: app.js 19.5 kB gzip + app.css 1.3 kB gzip

To test

  1. Build: cd integrations/bricks/editor-app && npm install && npm run build
  2. Activate the SLASHED for Bricks plugin on a Bricks site
  3. Open any page in Bricks Builder
  4. Click the gold "BEM" badge on any element in the structure panel

Summary by CodeRabbit

Release Notes

  • New Features

    • Added reBEMer, a BEM class manager for Bricks Builder. Manage CSS classes from the structure panel with add, rename, replace, and modifier operations. Features include name validation, operation safety checks, and keyboard undo support.
  • Documentation

    • Added comprehensive design documentation and updated README with reBEMer feature details.
  • Chores

    • Updated build configuration and changelog.

Review Change Stack

reBEMer Bot added 2 commits May 27, 2026 13:01
Adds reBEMer to the SLASHED for Bricks integration: a subtree-scoped BEM
class tool in the Bricks Builder structure panel. Click the 'BEM' badge on
any element, name the block + every descendant element, optionally add a
modifier, and apply as global classes in one operation.

Four modes: Add, Rename, Replace, Add Modifier.

Client side (Svelte 5 + Vite, ~880 LOC total):
  - bricks-api.js: single seam to Bricks Vue internals (probe, subtree
    traversal, class upsert, element mutation)
  - apply.js: one-pass loop over rows, 4 modes inline
  - slugify.js + validate.js: ASCII kebab-case + CSS-keyword guard
  - BemPanel, Row, BemBadge, Toast Svelte components
  - Imperative bootstrap (main.js): MutationObserver on structure panel,
    badge injection, panel mount/unmount, AbortController cleanup
  - Builds to app.js (19.5 kB gzip) + app.css (1.3 kB gzip)

Server side (PHP, 54 LOC):
  - class-rebemer-enqueue.php: builder-only enqueue with cap check +
    type=module script tag

Design doc: docs/rebemer.md
…HP type attr

- Wrap apply loop in try/catch so a mid-loop throw surfaces an error
  toast instead of leaving partial mutations silently
- Treat count=0 as an error (subtree may have changed)
- Clear the 800ms auto-close timer on unmount to prevent race on
  rapid re-clicks
- Move CSS custom properties from :root to #slashed-rebemer-host
  to avoid collision with other plugins
- Fix mark_as_module: strip existing type attr before inserting
  type=module so WP < 6.3 does not get a duplicate attribute
@jackgranatowski

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 27, 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 27, 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: d37e0f91-acd4-41db-847b-5a25dd913c5a

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 reBEMer, a subtree-scoped BEM class manager for Bricks Builder. It adds a Vue-aware JavaScript frontend (Svelte components compiled via Vite) that injects "BEM" badges into the structure panel, opens a dialog to edit block/element names and modifiers in batch, applies changes back to Bricks via a dedicated Vue-state seam, and enqueues the bundle in WordPress with module-script handling.

Changes

reBEMer MVP Integration

Layer / File(s) Summary
Design & Documentation
docs/rebemer.md, CHANGELOG.md, integrations/bricks/README.md, semantic-review/2025-05-27-131939-pr-rebemer-mvp.md
Complete design specification covering UX flow, architecture, BEM policy model, Plan/apply semantics, REST preflight contract, reserved-name guards, ID generation, undo buffer, i18n, testing strategy, and known implementation issues.
Editor App Project Setup
integrations/bricks/editor-app/package.json, svelte.config.js, vite.config.js, .gitignore, index.html
Vite + Svelte ESM app configuration, dev harness with mocked Bricks structure, and build output targeting assets/editor-app with source maps and stable filenames.
Bricks Vue State Interface
integrations/bricks/editor-app/src/lib/bricks-api.js
Seam module that detects/caches Vue app, locates elements by ID, extracts subtree hierarchy, accesses global class registry, and provides reactive mutations (upsert classes, update element class lists, sync labels) with collision-safe ID generation.
Validation & Slugification
integrations/bricks/editor-app/src/lib/validate.js, src/lib/slugify.js
Input validation (kebab-case regex, CSS keyword disallow-list) and Unicode-to-ASCII normalization for BEM class names.
Subtree Apply Logic
integrations/bricks/editor-app/src/lib/apply.js
One-pass BEM class applier that validates root block names, computes target classes per mode (add/rename/replace/modifier with suffix handling), mutates class lists with dedup, optionally syncs derived labels, and returns success/error responses.
UI Components & Styling
integrations/bricks/editor-app/src/components/BemBadge.svelte, BemPanel.svelte, Row.svelte, Toast.svelte, src/styles/panel.css
Svelte components for structure-panel badge button, dialog-based operation panel with mode selector, editable row grid per element, and auto-dismiss toast notifications; CSS defines theme variables, fixed-position panel layout, row indentation, and button/toast variants.
Integration Runtime
integrations/bricks/editor-app/src/main.js
Imperative shell that probes Vue app, mounts MutationObserver on structure panel, injects BemBadge instances on structure items, manages single BemPanel dialog on badge activation, and unmounts all components on page unload.
Built Assets
integrations/bricks/assets/editor-app/app.js, app.css
Minified Vite outputs (Svelte runtime + bundled app logic and styles) ready for WordPress enqueue.
PHP Integration
integrations/bricks/includes/class-rebemer-enqueue.php, integrations/bricks/slashed-bricks.php
Enqueue class registers app.js as ES module (via script tag type rewriting) and app.css in Bricks builder only for users with sufficient capabilities, with filemtime cache-busting. Plugin hook initializes on after_setup_theme when Bricks is active.
Project Configuration
.gitignore, .stylelintrc.json
Root .gitignore ignores editor-app source maps; .stylelintrc ignores editor-app asset and source directories.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.63% 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 title 'feat(bricks): reBEMer MVP — subtree BEM class manager' directly and clearly summarizes the main change: the addition of reBEMer, a BEM class manager for Bricks Builder's structure panel.
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/rebemer-mvp

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: 8

🤖 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/editor-app/src/components/BemPanel.svelte`:
- Around line 32-38: The Enter key handler currently in handleKeydown (and the
similar handlers at the other spots) applies when any INPUT/SELECT anywhere on
the page is focused; restrict it to only inputs inside this component by
checking that the event target is contained within the panel's DOM root before
calling apply() (e.g., create a bound ref like panelEl via bind:this and use
panelEl.contains(e.target) in handleKeydown and the other handlers). Keep the
existing tagName checks and preventDefault/apply() logic but only run them when
the target is inside the component; leave the Escape/onClose behavior unchanged.
- Line 72: The close button currently renders only the "×" character which lacks
an accessible name; update the button with an explicit accessible label by
adding an aria-label (e.g., aria-label="Close") or include visually-hidden text
inside the button, while keeping the existing class "rebemer-panel__close" and
preserving the onclick handler that calls onClose. Ensure the label is localized
if needed and that the onClose callback remains unchanged.

In `@integrations/bricks/editor-app/src/lib/apply.js`:
- Around line 31-35: The call to api.getGlobalClasses() occurs outside the try
block and can throw, breaking the function's { ok: false } contract; move that
call (and any similar API reads around lines 102-104) inside the existing try so
all runtime errors are caught, and replace usages of err.message with a hardened
stringification (e.g., String(err) or JSON-safe fallback) when building the
error response so non-Error throwables are handled predictably; target the
api.getGlobalClasses() invocation and the surrounding try/catch around the rows
loop and the other API reads referenced.
- Line 24: The code in applyToSubtree silently treats unknown mode values as a
replace-like operation (writing [newClassId]) which can drop existing classes;
update applyToSubtree to validate the mode argument up front (e.g., allow only
explicit modes like "replace" and "merge"/"append" used elsewhere) and throw a
clear Error (or return early with an error) for unrecognized modes instead of
falling through to the default branch. Locate the switch/conditional that writes
[newClassId] (and the similar logic around the other block referenced near lines
77-90) and replace the default case with an explicit validation failure that
includes the invalid mode value and the rootId to aid debugging.
- Around line 54-58: When mode === 'modifier' you must skip rows that lack a
valid modifier instead of falling through and treating them like base-class
rows; update the logic in apply.js (the block using mode, row.modifier, slugify,
baseClass, finalClass) so that if mode === 'modifier' and either row.modifier is
falsy or slugify(row.modifier) returns an empty string you immediately continue
to the next row, otherwise set finalClass = `${baseClass}--${modSlug}`.

In `@integrations/bricks/editor-app/src/main.js`:
- Around line 156-163: When mounting a new BemBadge you currently overwrite
badgeInstances[elementId] without cleaning up an existing Svelte instance, which
can leak/orphan the prior component; before calling mount(BemBadge, ...) check
badgeInstances.get(elementId) and if present call its instance.$destroy() (and
remove its host node from the DOM if you previously inserted one), then replace
the entry with the new { instance, host } and set li.dataset[ATTACHED_FLAG] as
you do now to avoid orphaned instances.

In `@integrations/bricks/editor-app/src/styles/panel.css`:
- Around line 6-19: Badge styles rely on CSS variables defined on
`#slashed-rebemer-host` which don’t inherit into injected badge nodes inside the
structure-panel list items, causing missing colors/typography; fix by updating
the badge-related rules (the structure-panel list item / badge selectors
referenced in the diff) to use fallback values for each variable (e.g. use
var(--rebemer-bg, `#161a1d`), var(--rebemer-fg, `#e1e1e1`), var(--rebemer-font,
"Inter", -apple-system, BlinkMacSystemFont, sans-serif), etc.), or alternatively
duplicate the same --rebemer-* declarations at a global scope (e.g. :root or the
structure-panel container) so injected nodes resolve those variables even when
outside `#slashed-rebemer-host`. Ensure every use of --rebemer-* in the badge
rules has a sensible fallback.

In `@integrations/bricks/editor-app/vite.config.js`:
- Around line 24-30: The Vite config uses __dirname (e.g., in outDir and
rollupOptions.input) but the package is ESM so __dirname is unavailable; update
vite.config.js to derive the directory from import.meta.url (use
fileURLToPath(import.meta.url) and path.dirname or new URL('./',
import.meta.url)) and replace occurrences of resolve(__dirname, ...) with
path.resolve(projectDir, ...) or new URL('./relative/path', import.meta.url)
equivalents so outDir, rollupOptions.input and any other path calls (e.g.,
resolve(...)) work in ESM.
🪄 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: 9dbc07a4-7b37-453a-99ee-d2c421dbd989

📥 Commits

Reviewing files that changed from the base of the PR and between 005e715 and 5956854.

⛔ Files ignored due to path filters (1)
  • integrations/bricks/editor-app/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (25)
  • .gitignore
  • .stylelintrc.json
  • CHANGELOG.md
  • docs/rebemer.md
  • integrations/bricks/README.md
  • integrations/bricks/assets/editor-app/app.css
  • integrations/bricks/assets/editor-app/app.js
  • integrations/bricks/editor-app/.gitignore
  • integrations/bricks/editor-app/index.html
  • integrations/bricks/editor-app/package.json
  • integrations/bricks/editor-app/src/components/BemBadge.svelte
  • integrations/bricks/editor-app/src/components/BemPanel.svelte
  • integrations/bricks/editor-app/src/components/Row.svelte
  • integrations/bricks/editor-app/src/components/Toast.svelte
  • integrations/bricks/editor-app/src/lib/apply.js
  • integrations/bricks/editor-app/src/lib/bricks-api.js
  • integrations/bricks/editor-app/src/lib/slugify.js
  • integrations/bricks/editor-app/src/lib/validate.js
  • integrations/bricks/editor-app/src/main.js
  • integrations/bricks/editor-app/src/styles/panel.css
  • integrations/bricks/editor-app/svelte.config.js
  • integrations/bricks/editor-app/vite.config.js
  • integrations/bricks/includes/class-rebemer-enqueue.php
  • integrations/bricks/slashed-bricks.php
  • semantic-review/2025-05-27-131939-pr-rebemer-mvp.md

Comment on lines +32 to +38
function handleKeydown(e) {
if (e.key === 'Escape') { onClose?.(); return; }
if (e.key === 'Enter' && (e.target?.tagName === 'INPUT' || e.target?.tagName === 'SELECT')) {
e.preventDefault();
apply();
}
}

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

Scope Enter-to-Apply to this dialog only.

Line 34 currently allows Enter from any focused INPUT/SELECT in the window to trigger apply(), which can mutate classes unintentionally outside panel interactions.

Suggested fix
   let { rootId, onClose } = $props();
+  let panelEl;

   function handleKeydown(e) {
     if (e.key === 'Escape') { onClose?.(); return; }
-    if (e.key === 'Enter' && (e.target?.tagName === 'INPUT' || e.target?.tagName === 'SELECT')) {
+    const inPanel = panelEl && e.target instanceof Node && panelEl.contains(e.target);
+    if (inPanel && e.key === 'Enter' && (e.target?.tagName === 'INPUT' || e.target?.tagName === 'SELECT')) {
       e.preventDefault();
       apply();
     }
   }
...
-<div class="rebemer-panel" role="dialog" aria-modal="true" tabindex="-1">
+<div bind:this={panelEl} class="rebemer-panel" role="dialog" aria-modal="true" tabindex="-1">

Also applies to: 67-67, 69-69

🤖 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/editor-app/src/components/BemPanel.svelte` around lines
32 - 38, The Enter key handler currently in handleKeydown (and the similar
handlers at the other spots) applies when any INPUT/SELECT anywhere on the page
is focused; restrict it to only inputs inside this component by checking that
the event target is contained within the panel's DOM root before calling apply()
(e.g., create a bound ref like panelEl via bind:this and use
panelEl.contains(e.target) in handleKeydown and the other handlers). Keep the
existing tagName checks and preventDefault/apply() logic but only run them when
the target is inside the component; leave the Escape/onClose behavior unchanged.

<div class="rebemer-panel" role="dialog" aria-modal="true" tabindex="-1">
<header class="rebemer-panel__header">
<h2 class="rebemer-panel__title">reBEMer · <span class="rebemer-panel__subject">{rootLabel}</span></h2>
<button type="button" class="rebemer-panel__close" onclick={() => onClose?.()}>×</button>

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 | 🟡 Minor | ⚡ Quick win

Add an accessible label to the close button.

Line 72 renders only ×, which is not a reliable accessible name.

Suggested fix
-    <button type="button" class="rebemer-panel__close" onclick={() => onClose?.()}>×</button>
+    <button
+      type="button"
+      class="rebemer-panel__close"
+      aria-label="Close reBEMer panel"
+      onclick={() => onClose?.()}
+    >×</button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button type="button" class="rebemer-panel__close" onclick={() => onClose?.()}>×</button>
<button
type="button"
class="rebemer-panel__close"
aria-label="Close reBEMer panel"
onclick={() => onClose?.()}
>×</button>
🤖 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/editor-app/src/components/BemPanel.svelte` at line 72,
The close button currently renders only the "×" character which lacks an
accessible name; update the button with an explicit accessible label by adding
an aria-label (e.g., aria-label="Close") or include visually-hidden text inside
the button, while keeping the existing class "rebemer-panel__close" and
preserving the onclick handler that calls onClose. Ensure the label is localized
if needed and that the onClose callback remains unchanged.

* @param {boolean} opts.syncLabels
* @returns {{ok:true, count:number} | {ok:false, error:string}}
*/
export function applyToSubtree({ rootId, rows, mode, syncLabels }) {

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

Reject unknown mode values instead of silently applying replace-like behavior.

The default branch currently writes [newClassId], which can remove existing classes if an invalid mode slips through.

Suggested fix
 export function applyToSubtree({ rootId, rows, mode, syncLabels }) {
+  const VALID_MODES = new Set(['add', 'rename', 'replace', 'modifier']);
+  if (!VALID_MODES.has(mode)) {
+    return { ok: false, error: `Invalid mode: ${mode}` };
+  }
+
   const blockRow = rows.find(r => r.id === rootId);
@@
-      default:
-        nextIds = [newClassId];
+      default:
+        // Unreachable due to upfront validation.
+        continue;

Also applies to: 77-90

🤖 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/editor-app/src/lib/apply.js` at line 24, The code in
applyToSubtree silently treats unknown mode values as a replace-like operation
(writing [newClassId]) which can drop existing classes; update applyToSubtree to
validate the mode argument up front (e.g., allow only explicit modes like
"replace" and "merge"/"append" used elsewhere) and throw a clear Error (or
return early with an error) for unrecognized modes instead of falling through to
the default branch. Locate the switch/conditional that writes [newClassId] (and
the similar logic around the other block referenced near lines 77-90) and
replace the default case with an explicit validation failure that includes the
invalid mode value and the rootId to aid debugging.

Comment on lines +31 to +35
const globalClasses = api.getGlobalClasses();
let count = 0;

try {
for (const row of rows) {

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

Keep API reads inside the guarded block and harden error stringification.

Line 31 can throw outside the try, breaking the function’s { ok: false } contract. Also, err.message assumes an Error object.

Suggested fix
-  const globalClasses = api.getGlobalClasses();
   let count = 0;
 
   try {
+  const globalClasses = api.getGlobalClasses();
   for (const row of rows) {
@@
-  } catch (err) {
-    return { ok: false, error: `Operation failed mid-apply: ${err.message}` };
+  } catch (err) {
+    const message = err instanceof Error ? err.message : String(err);
+    return { ok: false, error: `Operation failed mid-apply: ${message}` };
   }

Also applies to: 102-104

🤖 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/editor-app/src/lib/apply.js` around lines 31 - 35, The
call to api.getGlobalClasses() occurs outside the try block and can throw,
breaking the function's { ok: false } contract; move that call (and any similar
API reads around lines 102-104) inside the existing try so all runtime errors
are caught, and replace usages of err.message with a hardened stringification
(e.g., String(err) or JSON-safe fallback) when building the error response so
non-Error throwables are handled predictably; target the api.getGlobalClasses()
invocation and the surrounding try/catch around the rows loop and the other API
reads referenced.

Comment on lines +54 to +58
if (mode === 'modifier' && row.modifier) {
const modSlug = slugify(row.modifier);
if (!modSlug) continue;
finalClass = `${baseClass}--${modSlug}`;
}

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

Modifier mode should skip rows without a valid modifier.

Line 54 currently turns modifier mode into base-class add when row.modifier is empty. That contradicts the mode contract and can mutate classes unexpectedly.

Suggested fix
-    let finalClass = baseClass;
-    if (mode === 'modifier' && row.modifier) {
-      const modSlug = slugify(row.modifier);
-      if (!modSlug) continue;
-      finalClass = `${baseClass}--${modSlug}`;
-    }
+    let finalClass = baseClass;
+    if (mode === 'modifier') {
+      const modSlug = slugify(row.modifier);
+      if (!modSlug) continue;
+      finalClass = `${baseClass}--${modSlug}`;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (mode === 'modifier' && row.modifier) {
const modSlug = slugify(row.modifier);
if (!modSlug) continue;
finalClass = `${baseClass}--${modSlug}`;
}
if (mode === 'modifier') {
const modSlug = slugify(row.modifier);
if (!modSlug) continue;
finalClass = `${baseClass}--${modSlug}`;
}
🤖 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/editor-app/src/lib/apply.js` around lines 54 - 58, When
mode === 'modifier' you must skip rows that lack a valid modifier instead of
falling through and treating them like base-class rows; update the logic in
apply.js (the block using mode, row.modifier, slugify, baseClass, finalClass) so
that if mode === 'modifier' and either row.modifier is falsy or
slugify(row.modifier) returns an empty string you immediately continue to the
next row, otherwise set finalClass = `${baseClass}--${modSlug}`.

Comment on lines +156 to +163
const instance = mount(BemBadge, {
target: host,
props: { elementId, label, onActivate: openPanel },
});

badgeInstances.set(elementId, { instance, host });
li.dataset[ATTACHED_FLAG] = '1';
}

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

Avoid leaking old badge instances when remounting the same data-id.

Line 161 overwrites badgeInstances by key without first unmounting any existing instance for that id. During rapid tree rebuilds this can orphan the previous Svelte instance.

Suggested fix
   const instance = mount(BemBadge, {
     target: host,
     props: { elementId, label, onActivate: openPanel },
   });

+  const existing = badgeInstances.get(elementId);
+  if (existing) {
+    try { unmount(existing.instance); } catch (err) { log('warn', 'badge remount cleanup failed', err); }
+    if (existing.host.isConnected) existing.host.remove();
+  }
   badgeInstances.set(elementId, { instance, host });
   li.dataset[ATTACHED_FLAG] = '1';
 }
🤖 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/editor-app/src/main.js` around lines 156 - 163, When
mounting a new BemBadge you currently overwrite badgeInstances[elementId]
without cleaning up an existing Svelte instance, which can leak/orphan the prior
component; before calling mount(BemBadge, ...) check
badgeInstances.get(elementId) and if present call its instance.$destroy() (and
remove its host node from the DOM if you previously inserted one), then replace
the entry with the new { instance, host } and set li.dataset[ATTACHED_FLAG] as
you do now to avoid orphaned instances.

Comment on lines +6 to +19
#slashed-rebemer-host {
--rebemer-bg: #161a1d;
--rebemer-fg: #e1e1e1;
--rebemer-fg-muted: #8a8d95;
--rebemer-border: #3d4752;
--rebemer-accent: #e2b93b;
--rebemer-accent-hover: #ffd042;
--rebemer-input-bg: #293038;
--rebemer-input-border: #3d4752;
--rebemer-error: #ff4c4c;
--rebemer-success: #4ade80;
--rebemer-radius: 4px;
--rebemer-font: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
}

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

Badge styles depend on CSS vars that are out of scope for injected badge nodes.

#slashed-rebemer-host vars do not inherit into structure-panel list items, but Line 22+ badge rules consume them. This can break badge colors/typography.

Suggested fix (fallbacks for out-of-host badge rendering)
 .rebemer-badge {
-  font: 600 9px/1 var(--rebemer-font);
+  font: 600 9px/1 var(--rebemer-font, "Inter", -apple-system, BlinkMacSystemFont, sans-serif);
   letter-spacing: .5px;
   text-transform: uppercase;
-  color: var(--rebemer-bg);
-  background: var(--rebemer-accent);
+  color: var(--rebemer-bg, `#161a1d`);
+  background: var(--rebemer-accent, `#e2b93b`);
   border: 0;
   border-radius: 2px;
   padding: 2px 4px;
   cursor: pointer;
 }
-.rebemer-badge:hover { background: var(--rebemer-accent-hover); }
-.rebemer-badge:focus-visible { outline: 2px solid var(--rebemer-accent-hover); outline-offset: 1px; }
+.rebemer-badge:hover { background: var(--rebemer-accent-hover, `#ffd042`); }
+.rebemer-badge:focus-visible { outline: 2px solid var(--rebemer-accent-hover, `#ffd042`); outline-offset: 1px; }

Also applies to: 22-35

🤖 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/editor-app/src/styles/panel.css` around lines 6 - 19,
Badge styles rely on CSS variables defined on `#slashed-rebemer-host` which don’t
inherit into injected badge nodes inside the structure-panel list items, causing
missing colors/typography; fix by updating the badge-related rules (the
structure-panel list item / badge selectors referenced in the diff) to use
fallback values for each variable (e.g. use var(--rebemer-bg, `#161a1d`),
var(--rebemer-fg, `#e1e1e1`), var(--rebemer-font, "Inter", -apple-system,
BlinkMacSystemFont, sans-serif), etc.), or alternatively duplicate the same
--rebemer-* declarations at a global scope (e.g. :root or the structure-panel
container) so injected nodes resolve those variables even when outside
`#slashed-rebemer-host`. Ensure every use of --rebemer-* in the badge rules has a
sensible fallback.

Comment on lines +24 to +30
outDir: resolve(__dirname, '../assets/editor-app'),
emptyOutDir: true,
sourcemap: true,
target: 'es2020',
cssCodeSplit: false,
rollupOptions: {
input: resolve(__dirname, 'src/main.js'),

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

🧩 Analysis chain

🌐 Web query:

In Vite 6 projects with package.json "type": "module", is __dirnameguaranteed insidevite.config.js, or should paths be resolved via import.meta.urlandfileURLToPath?

💡 Result:

In Vite projects where package.json contains "type": "module", dirname is not guaranteed to be available [1]. When a project is configured as an ES module, Node.js does not provide dirname or filename in the module scope [1]. While Vite historically performed transformations to inject these variables in some contexts, relying on this is not recommended in modern Vite projects [1]. Instead, you should explicitly resolve paths using import.meta.url and node:url utilities [2][1]. The standard, robust approach to define dirname in an ESM Vite configuration file is: import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; const dirname = dirname(fileURLToPath(import.meta.url)); Additionally, Node.js (and modern environments) now support import.meta.dirname, which is the preferred, built-in alternative for accessing the directory path of the current module without requiring manual resolution [3]. Vite's internal codebase has transitioned to using import.meta.dirname for this purpose [3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the vite config around the reported lines
echo "---- vite.config.js (relevant excerpt) ----"
sed -n '1,120p' integrations/bricks/editor-app/vite.config.js | nl -ba | sed -n '15,80p'

# Find package.json that may control module type (root + nearby)
echo "---- package.json files near integrations/bricks/editor-app ----"
# List likely package.json files
find . -maxdepth 4 -name package.json | sed 's|^\./||' | sort

# Show root package.json "type" if present
if [ -f package.json ]; then
  echo "---- root package.json ----"
  rg -n "\"type\"\\s*:\\s*\"module\"" package.json || true
  cat package.json | head -n 40
fi

# Show package.json under integrations/bricks/editor-app or integrations/bricks if present
for p in integrations/package.json integrations/bricks/package.json integrations/bricks/editor-app/package.json; do
  if [ -f "$p" ]; then
    echo "---- $p ----"
    rg -n "\"type\"\\s*:\\s*\"module\"" "$p" || true
    cat "$p" | head -n 60
  fi
done

Repository: codeslash-dev/SLASHED

Length of output: 170


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- vite.config.js (lines 1-120) ----"
cat -n integrations/bricks/editor-app/vite.config.js | sed -n '1,120p'

echo "---- package.json files (maxdepth 4) ----"
find . -maxdepth 4 -name package.json | sed 's|^\./||' | sort

echo "---- root package.json: type module? ----"
if [ -f package.json ]; then
  rg -n "\"type\"\\s*:\\s*\"module\"" package.json || true
  cat package.json | head -n 60
fi

for p in integrations/package.json integrations/bricks/package.json integrations/bricks/editor-app/package.json; do
  if [ -f "$p" ]; then
    echo "---- $p: type module? ----"
    rg -n "\"type\"\\s*:\\s*\"module\"" "$p" || true
    cat "$p" | head -n 80
  fi
done

Repository: codeslash-dev/SLASHED

Length of output: 5278


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- vite.config.js (lines 1-140) ----"
cat -n integrations/bricks/editor-app/vite.config.js | sed -n '1,140p'

echo "---- package.json files (maxdepth 4) ----"
find . -maxdepth 4 -name package.json | sed 's|^\./||' | sort

echo "---- root package.json: type module? ----"
if [ -f package.json ]; then
  rg -n "\"type\"\\s*:\\s*\"module\"" package.json || true
  cat package.json | head -n 80
fi

for p in integrations/package.json integrations/bricks/package.json integrations/bricks/editor-app/package.json; do
  if [ -f "$p" ]; then
    echo "---- $p: type module? ----"
    rg -n "\"type\"\\s*:\\s*\"module\"" "$p" || true
    cat "$p" | head -n 120
  fi
done

Repository: codeslash-dev/SLASHED

Length of output: 5278


Replace __dirname with import.meta.url paths in the ESM Vite config
integrations/bricks/editor-app/vite.config.js is under integrations/bricks/editor-app/package.json with "type": "module", so __dirname isn’t reliably available. Resolve paths from import.meta.url instead.

♻️ Proposed fix
 import { defineConfig } from 'vite';
 import { svelte } from '`@sveltejs/vite-plugin-svelte`';
-import { resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
 
 export default defineConfig({
   plugins: [svelte()],
 
   build: {
-    outDir: resolve(__dirname, '../assets/editor-app'),
+    outDir: fileURLToPath(new URL('../assets/editor-app/', import.meta.url)),
     emptyOutDir: true,
     sourcemap: true,
     target: 'es2020',
     cssCodeSplit: false,
     rollupOptions: {
-      input: resolve(__dirname, 'src/main.js'),
+      input: fileURLToPath(new URL('./src/main.js', import.meta.url)),
       output: {
         entryFileNames: 'app.js',
         chunkFileNames: 'app-[name].js',
         assetFileNames: (info) => {
           if (info.name && info.name.endsWith('.css')) return 'app.css';
           return 'app-[name][extname]';
         },
       },
     },
   },
   // Standalone dev server for component-level work outside the builder.
   // The real integration runs inside the Bricks builder DOM, but this
   // lets us iterate on the panel UI quickly with a mock Bricks state.
   server: {
     port: 5174,
   },
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
outDir: resolve(__dirname, '../assets/editor-app'),
emptyOutDir: true,
sourcemap: true,
target: 'es2020',
cssCodeSplit: false,
rollupOptions: {
input: resolve(__dirname, 'src/main.js'),
import { defineConfig } from 'vite';
import { svelte } from '`@sveltejs/vite-plugin-svelte`';
import { fileURLToPath } from 'node:url';
export default defineConfig({
plugins: [svelte()],
build: {
outDir: fileURLToPath(new URL('../assets/editor-app/', import.meta.url)),
emptyOutDir: true,
sourcemap: true,
target: 'es2020',
cssCodeSplit: false,
rollupOptions: {
input: fileURLToPath(new URL('./src/main.js', import.meta.url)),
output: {
entryFileNames: 'app.js',
chunkFileNames: 'app-[name].js',
assetFileNames: (info) => {
if (info.name && info.name.endsWith('.css')) return 'app.css';
return 'app-[name][extname]';
},
},
},
},
// Standalone dev server for component-level work outside the builder.
// The real integration runs inside the Bricks builder DOM, but this
// lets us iterate on the panel UI quickly with a mock Bricks state.
server: {
port: 5174,
},
});
🤖 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/editor-app/vite.config.js` around lines 24 - 30, The Vite
config uses __dirname (e.g., in outDir and rollupOptions.input) but the package
is ESM so __dirname is unavailable; update vite.config.js to derive the
directory from import.meta.url (use fileURLToPath(import.meta.url) and
path.dirname or new URL('./', import.meta.url)) and replace occurrences of
resolve(__dirname, ...) with path.resolve(projectDir, ...) or new
URL('./relative/path', import.meta.url) equivalents so outDir,
rollupOptions.input and any other path calls (e.g., resolve(...)) work in ESM.

1. Scope Enter-to-Apply to panel only (panelEl.contains check)
2. Add aria-label to close button
3. Reject unknown mode values upfront, remove default branch
4. Move api.getGlobalClasses() inside try, harden error stringify
5. Modifier mode always requires valid slug (skip row otherwise)
6. Unmount existing badge before overwriting badgeInstances entry
7. Add CSS var fallbacks for badges (outside host scope) + replace
   __dirname with import.meta.url in vite.config.js (ESM compat)
@jackgranatowski
jackgranatowski merged commit 7f5c3ef into main May 27, 2026
5 checks passed
jackgranatowski pushed a commit that referenced this pull request May 27, 2026
PR #127 wired zip-plugin.js into the build chain but didn't commit the
generated dist/slashed-bricks.zip artifact (required by release.yml).
Also refreshes the ten .min.css.map files whose sources changed in
PRs #125#126 but whose built output was never re-committed.

https://claude.ai/code/session_01CkF1n21vhFAfscxSarXuCD
@jackgranatowski
jackgranatowski deleted the feat/rebemer-mvp branch May 31, 2026 18:01
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