Skip to content

Replace modifier mode with multi-modifier support in all modes - #209

Merged
jackgranatowski merged 2 commits into
mainfrom
claude/laughing-noether-0V06F
Jun 3, 2026
Merged

Replace modifier mode with multi-modifier support in all modes#209
jackgranatowski merged 2 commits into
mainfrom
claude/laughing-noether-0V06F

Conversation

@jackgranatowski

@jackgranatowski jackgranatowski commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR removes the dedicated "modifier" mode and instead adds support for multiple modifiers across all non-migrate modes (add, rename, replace). Modifiers are now specified as an array on each row and produce additional --modifier classes alongside the primary class. Additionally, introduces support for sub-block roots, allowing non-root elements to be promoted to their own BEM block scope.

Key Changes

  • Removed modifier mode: The 'modifier' mode is no longer a valid option. Modifier functionality is now available in add/rename/replace modes via row.modifiers: string[].

  • Multi-modifier support: Each row can now carry multiple modifiers in an array. Each non-empty modifier slug produces an additional finalClass--modSlug class attached alongside the primary class. Modifier slugs are resolved after auto-numbering to ensure they reference the correct post-numbered base class name.

  • Sub-block roots: Non-root rows can now be promoted to block roots via row.isBlockRoot: true. A sub-block root's finalClass becomes its own block name (not parentBlock__elemName), and all its descendants adopt it as their owning block. Implemented via new computeBlockAssignment() function that walks rows with a depth-based stack.

  • Updated Row component:

    • Modifier input changed from single text field to array of fields with add/remove buttons
    • Row type badge (ELEM/BLOCK) is now a clickable toggle on non-root rows to promote/demote sub-block roots
    • Preview now shows all class names (base + modifiers) as an array
  • Updated BemPanel component:

    • Removed "Add modifier" mode option from mode selector
    • Removed syncLabels disable for modifier mode (now always available)
    • Uses computeBlockAssignment() to derive per-row block names for prefix display
    • Validates sub-block root names before applying
  • Updated apply.js logic:

    • Modifier classes are appended after the primary class operation completes
    • Auto-numbering no longer has special handling for modifier-mode duplicates
    • Block assignment is computed upfront via stack walk, enabling support for multiple blocks per subtree
    • Removed the 'modifier' case from the mode switch in applyToSubtree()

Implementation Details

  • Modifier slug resolution happens in step 3 of buildPlan(), after auto-numbering, ensuring modifiers reference the final (post-numbered) class names.
  • The computeBlockAssignment() function is exported so BemPanel can reuse it for live prefix display without re-running the full plan.
  • Skipped sub-block roots are not pushed onto the block stack, so their children fall back to the nearest active ancestor block.
  • Sub-block root names are validated upfront in buildPlan() with a combined error message listing all invalid names.

https://claude.ai/code/session_01MR5eh7i7GRVpvudPrCjm2w

Summary by CodeRabbit

  • New Features

    • Added sub-block-root toggle controls for non-root rows to support hierarchical block designation
    • Redesigned modifier inputs to support multiple modifiers per row with add/remove controls
  • UI/UX Changes

    • Removed "modifier" operation mode from the mode configuration
    • Improved per-row class name preview display
    • Added validation feedback for sub-block names with error notifications
    • Enhanced styling for type toggles and modifier controls

Any non-root row can now be promoted to a sub-block root by clicking
its type badge (ELEM → BLOCK). The row and all its descendants adopt a
new BEM block scope, so a whole section can be reBEMed with distinct
block prefixes (hero__*, card__*, etc.) in one Apply.

Each row also gains a row.modifiers string[] field (replaces the old
single modifier string). In add/rename/replace mode a --modifier
placeholder input is shown per row; typing fills it in and an
'+ Add modifier' button appears to attach additional modifiers. All
non-empty modifiers are attached alongside the primary class.

The standalone 'Add modifier' mode is removed — modifier attachment is
now an inline feature of the three class-mutation modes.

Implementation notes:
- computeBlockAssignment(): new exported pure helper; stack-based depth
  walk that resolves each row to its owning block name. Shared by
  buildPlan() and BemPanel's rowBlockNames derived.
- buildPlan(): uses computeBlockAssignment, validates all block roots,
  populates op.modifierSlugs after auto-numbering so modifier class
  names always reference the final (post-numbered) base name.
- applyToSubtree(): appends modifier classes from op.modifierSlugs to
  nextIds before setElementClasses; uses per-op op.blockName for
  labelFromClass instead of a single root blockName.
- BemPanel: rowBlockNames derived feeds per-row blockName to Row;
  previewClassNames now Map<id, string[]> covering base + modifiers.
- Row: type badge is a clickable toggle for non-root rows; modifier
  section supports dynamic list with add/remove.

https://claude.ai/code/session_01MR5eh7i7GRVpvudPrCjm2w
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jackgranatowski, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 53 minutes and 21 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f4a3678-5ee1-438e-a164-d8e3104bceaa

📥 Commits

Reviewing files that changed from the base of the PR and between fa4945e and 70a0516.

📒 Files selected for processing (2)
  • plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/components/Row.svelte
  • plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/apply.js
📝 Walkthrough

Walkthrough

The PR refactors the reBEMer BEM-class mutation system by removing the "modifier" operational mode and introducing a row.modifiers array model with multi-block subtree ownership. The core logic flow evolves from mode-specific class generation to a unified plan-building phase that computes block ownership, validates roots, auto-numbers, and defers modifier-class resolution until after numbering. The Row component and panel layer are updated to reflect and wire the new data model, including sub-block-root toggling and multi-modifier editing.

Changes

BEM Plan Model and Application Refactor

Layer / File(s) Summary
Data model contract and block ownership
plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/apply.js
Mode union narrowed to remove 'modifier'; Row typedef adds modifiers?: string[] and clarifies isBlockRoot semantics; Op typedef expanded to track blockName and modifierSlugs; computeBlockAssignment uses depth-based block stack respecting isBlockRoot and excluding include: false rows from assignment.
Plan building, numbering, and collision detection
plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/apply.js
buildPlan validates root and sub-block names up-front, derives block ownership via computeBlockAssignment, runs auto-numbering without mode parameter, resolves row.modifiers to modifierSlugs post-numbering (except during migrate), and enforces post-numbering collision detection to reject duplicate finalClass values.
Apply-time class mutation and label syncing
plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/apply.js
Removes modifier case from mode switch; appends modifier classes post-primary-class resolution for each modifierSlugs entry; simplifies label syncing to depend only on syncLabels flag (no mode-based skip).
Row component props and multi-modifier UI
plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/components/Row.svelte
Component receives finalClassNames array and rootId instead of single finalClassName; hides modifier input only in migrate mode; renders sub-block-root toggle button (non-root rows); implements multi-modifier stack UI with per-item remove and add-modifier controls.
Panel layer state and Row component wiring
plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/components/BemPanel.svelte
Imports computeBlockAssignment and derives rowBlockNames map; updates row preview to generate per-row class-name arrays from modifierSlugs instead of single class string; seeds rows with modifiers: [''] array and isBlockRoot: false; validates included sub-block roots before apply; wires Row component with per-row blockName, expanded root detection, and finalClassNames array.
Type toggle button and multi-modifier UI styling
plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/styles/panel.css
Adds .rebemer-row__type--toggle button variant (hover/disabled states) and .rebemer-row__type--block accent highlight; introduces multi-modifier layout (.rebemer-row__modifiers, .rebemer-row__modifier-row) with remove/add button controls.

Sequence Diagram

sequenceDiagram
  participant User as User / Panel
  participant buildPlan as buildPlan()
  participant BlockAssign as computeBlockAssignment()
  participant Validate as validateName()
  participant Number as applyAutoNumbering()
  participant applyTo as applyToSubtree()
  participant GlobalClasses as Global Class IDs
  User->>buildPlan: rows, rootId, mode
  buildPlan->>Validate: validate root + sub-block names
  buildPlan->>BlockAssign: compute per-row owning block
  BlockAssign-->>buildPlan: rowBlockNames map
  buildPlan->>Number: auto-number ops
  Number-->>buildPlan: ops with numbered finalClass
  buildPlan->>buildPlan: resolve modifiers to modifierSlugs
  buildPlan-->>User: ops array
  User->>applyTo: ops, mode, syncLabels
  applyTo->>applyTo: by mode, compute primary class IDs
  applyTo->>GlobalClasses: upser primary classes
  applyTo->>GlobalClasses: append modifier classes per modifierSlugs
  applyTo->>applyTo: sync labels if enabled
  applyTo-->>User: done
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • codeslash-dev/SLASHED#125: Introduces the initial reBEMer MVP class-application logic; this PR refactors the same functions (BemPanel.svelte, Row.svelte, apply.js) to replace modifier mode with a modifiers array and multi-block subtree support.
🚥 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 summarizes the main change: removal of the dedicated 'modifier' mode and introduction of multi-modifier support across all remaining modes, which aligns with the substantial changes across multiple components.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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 claude/laughing-noether-0V06F

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

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

110-121: 💤 Low value

Minor JSDoc clarification: include handling is asymmetric.

The docstring says the function determines ownership "for each included row," but the implementation assigns block ownership to all non-block element rows regardless of include status (lines 138-141). This is intentional for UI preview purposes (so excluded rows still show their would-be prefix), but the documentation could be clearer about this nuance.

Consider updating:

- * Walk rows in document order to determine the owning BEM block for each
- * included row.
+ * Walk rows in document order to determine the owning BEM block for each row.
+ * Block/sub-block roots are only pushed onto the stack when included;
+ * element rows are assigned regardless of include status (for UI preview).
🤖 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/apply.js`
around lines 110 - 121, Update the JSDoc for the exported function that walks
rows (parameters rows and rootId, returning Map row.id → owning block's
slugified name) to explicitly state that ownership is assigned to all non-block
element rows regardless of their include flag (this asymmetry is intentional to
support UI preview/prefix display used by BemPanel), i.e., clarify the function
determines the "would-be" owning BEM block for excluded rows as well rather than
limiting results to included rows.
plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/components/Row.svelte (1)

134-134: 💤 Low value

Avoid keying the modifier list by array index.

{#each row.modifiers as _mod, mi (mi)} keys by position while the inputs use bind:value={row.modifiers[mi]}. Removing a middle entry reuses DOM nodes by index, so the surviving inputs rebind to shifted values and can carry over stale focus/IME/selection state. Consider keying by a stable per-modifier id instead.

🤖 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/components/Row.svelte`
at line 134, The each block in Row.svelte currently keys modifiers by index
({`#each` row.modifiers as _mod, mi (mi)}) which causes DOM reuse and input state
bleeding when items are removed; change the data model so each modifier is an
object with a stable id (e.g., {id, value, ...}), update the each to key by that
id (use _mod.id instead of mi) and update the input bindings to use the loop
variable (bind:value={_mod.value} or equivalent) rather than indexing into
row.modifiers by mi so DOM nodes stay tied to stable modifier identities.
🤖 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
`@plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/components/Row.svelte`:
- Around line 136-145: The modifier input currently calls markAsUserTyped
(oninput={markAsUserTyped}), which flips row.suggestedFrom to 'user' and
incorrectly locks the base name; remove that side-effect by not invoking
markAsUserTyped from the modifier field (bind:value={row.modifiers[mi]} on the
input). Instead, either remove the oninput from the modifier input entirely or
replace it with a no-op/modifier-only handler that updates the modifier value
without touching row.suggestedFrom; keep markAsUserTyped only on the base name
input so that only edits to the base class set row.suggestedFrom and block
auto-numbering.

---

Nitpick comments:
In
`@plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/components/Row.svelte`:
- Line 134: The each block in Row.svelte currently keys modifiers by index
({`#each` row.modifiers as _mod, mi (mi)}) which causes DOM reuse and input state
bleeding when items are removed; change the data model so each modifier is an
object with a stable id (e.g., {id, value, ...}), update the each to key by that
id (use _mod.id instead of mi) and update the input bindings to use the loop
variable (bind:value={_mod.value} or equivalent) rather than indexing into
row.modifiers by mi so DOM nodes stay tied to stable modifier identities.

In `@plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/apply.js`:
- Around line 110-121: Update the JSDoc for the exported function that walks
rows (parameters rows and rootId, returning Map row.id → owning block's
slugified name) to explicitly state that ownership is assigned to all non-block
element rows regardless of their include flag (this asymmetry is intentional to
support UI preview/prefix display used by BemPanel), i.e., clarify the function
determines the "would-be" owning BEM block for excluded rows as well rather than
limiting results to included rows.
🪄 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: 83cc4c73-c2d2-4696-b042-f3799ac5c12c

📥 Commits

Reviewing files that changed from the base of the PR and between 2cbd586 and fa4945e.

📒 Files selected for processing (4)
  • plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/components/BemPanel.svelte
  • plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/components/Row.svelte
  • plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/lib/apply.js
  • plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/styles/panel.css

… JSDoc

Typing into a modifier input was calling markAsUserTyped(), which flipped
row.suggestedFrom to 'user' and silently locked the base name out of
sibling auto-numbering — even though the user never touched the base
name input. Modifier slugs are resolved after numbering and never affect
the base class identity, so the oninput handler is removed from the
modifier inputs.

Also clarifies the computeBlockAssignment() JSDoc: element rows are
assigned an owner regardless of their include status (intentional, for
UI prefix preview), which was previously undocumented.

https://claude.ai/code/session_01MR5eh7i7GRVpvudPrCjm2w
@jackgranatowski
jackgranatowski merged commit a048647 into main Jun 3, 2026
9 checks passed
jackgranatowski pushed a commit that referenced this pull request Jun 3, 2026
Compiles the Svelte source changes from PR #209 into the deployed
assets — sub-block root promotion, inline multi-modifier inputs,
and removal of the standalone modifier mode.

https://claude.ai/code/session_01MR5eh7i7GRVpvudPrCjm2w
jackgranatowski pushed a commit that referenced this pull request Jun 3, 2026
Compiles the Svelte source changes from PR #209 into the deployed
assets — sub-block root promotion, inline multi-modifier inputs,
and removal of the standalone modifier mode.

https://claude.ai/code/session_01MR5eh7i7GRVpvudPrCjm2w
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