Skip to content

Fix/natural sort color palette - #131

Merged
jackgranatowski merged 5 commits into
mainfrom
fix/natural-sort-color-palette
May 28, 2026
Merged

Fix/natural sort color palette#131
jackgranatowski merged 5 commits into
mainfrom
fix/natural-sort-color-palette

Conversation

@jackgranatowski

@jackgranatowski jackgranatowski commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

Release Notes

  • New Features

    • Added "Migrate ID Styles" mode to reBEMer panel with class migration preview and summary
    • Added API endpoint to detect and report unused global classes
    • Enhanced reBEMer editor with improved visual feedback and mode-specific UI elements
  • Documentation

    • Updated implementation status with progress tracking and feature references
  • Style

    • Refined editor styling including badge redesign and migrate mode indicators

Review Change Stack

kiro-agent and others added 4 commits May 27, 2026 23:20
Implements approved design refinements from PR #129 review. The design
doc moves from 'pre-implementation' to a per-item status table (§0).

#1 Element-aware row pre-fill
  - New lib/element-types.js maps Bricks element.name → BEM label
    (heading → 'heading', image → 'image', layout containers fall
    through to 'item').
  - BemPanel onMount uses suggestElementName() when the structure-
    panel label slugifies to nothing, so opening the panel on a
    fresh subtree doesn't require typing every name.
  - Provenance tracked on each row via suggestedFrom; the panel
    renders 'suggested' rows with italic muted text and a chip
    so the user knows what to override.

#2 Sibling auto-numbering
  - New applyAutoNumbering() pre-pass in apply.js groups ops by
    finalClassName and appends -1, -2, ... in document order.
  - User-typed names (suggestedFrom: 'user') are authoritative and
    never auto-numbered. Two user-typed collisions hard-error
    rather than silently mangling.
  - Skipped rows (include: false) are excluded from the tally.
  - Modifier-mode collisions error out (block__title--lg-2 reads
    poorly; better to ask the user to disambiguate).

#3 Per-row skip toggle
  - Already present in skeleton as the include checkbox; now formally
    aligned with §6.2 'skip' semantics — checkbox unchecks but the
    row stays visible for transparency, contributes nothing to
    apply or to collision detection. Documented in Row.svelte.

#4 'Use existing' recommendation (client-side half of §11.3)
  - Row.svelte derives candidateClassName + checks against the
    globalClasses snapshot captured at panel open. When a class
    with the same name already exists, the row shows a green
    'will attach existing class' hint.
  - This is the §11.3 recommendedAction: 'attach' path executed
    purely from in-memory state. The 'rename' / 'replace' paths
    need a server preflight (cross-page reference count) and ship
    in a follow-up PR. The §0 status table makes the split explicit.

#5 Migrate ID styles mode
  - New 'migrate' value in the operation dropdown.
  - lib/migrate-keys.js holds the allowlist (padding, color, typography,
    border, layout, position, ~80 keys). Defense-in-depth: apply.js
    re-checks the allowlist before lifting any key, so a tampered
    row.migrateKeys can't smuggle non-style settings into a class.
  - Per-row chip strip (§6.3 preview) lists the keys that will lift,
    plus a 'N skipped' badge for keys present-but-not-allowlisted
    (which stay on the element by design).
  - Panel-level summary strip totals migrated/skipped across the
    included rows so the user can sanity-check before pressing Apply.
  - Lifted keys are deep-cloned into the new class' settings, then
    deleted from the element. Other classes preserved (additive).

#10 Unused-class read-only report
  - New GET /wp-json/slashed-bricks/v1/rebemer/unused endpoint in
    new class-rebemer-rest.php. Capability gate matches the editor
    enqueue gate (bricks_full_access || manage_options).
  - count_class_references() helper walks postmeta for Bricks page
    content keys (_bricks_page_content_2 + legacy + header/footer),
    capped at REFERENCE_SCAN_CAP=5000 rows. Iterative-with-stack
    walk avoids deep recursion. Response includes truncated:true
    when the cap is hit so the UI can warn.
  - Returns naturally-sorted unused class list, total counts, and
    scan stats. Read-only — never touches bricks_global_classes.
    Goal #6 (no destructive writes) stays intact; deletion remains
    Bricks' Global Class Manager job.
  - Helper signature designed for reuse by the future preflight
    endpoint.

Dev harness updates
  - index.html now seeds a mock __vue_app__.$_state and
    bricks_global_classes so the badge injector + recommendation
    hint + migrate chips are exercised in standalone dev.
  - Mock element types match real Bricks (heading, image, button,
    block, text-basic) so element-types.js is hit.

Build artifacts
  - assets/editor-app/app.js + app.css regenerated. Bundle grew
    from skeleton size to ~60kB JS (+22.8kB gzip), ~7kB CSS (+1.7kB
    gzip). Filemtime cache-busting in class-rebemer-enqueue.php
    handles the version bump automatically.

What does NOT ship in this PR (still spec-only, see §0 table)
  - Cross-page reference count preflight (§11)
  - Snapshot/rollback transactional apply (§10)
  - In-panel undo ring buffer (§15)
  - i18n string table (§16)
  - SLASHED-utility reserved-name guard wired from inventory (§13)

These are on the roadmap; each is its own PR.
… and actions

Badge was being injected at the start of .actions (when found) or at
the start of the <li> as a fallback — neither matches Bricks' actual
structure-panel DOM, which is:

    <li data-id="…">
      <div class="structure-item">
        <div class="title">…label…</div>
        <ul class="actions">…icon buttons…</ul>
      </div>
    </li>

The fallback path was firing for users on current Bricks builds and
producing a chip overlaying the row. Fix:

  - Selector: prefer :scope > .structure-item, locate ul.actions
    inside it, insert the badge BEFORE actions (so it lands between
    .title and .actions per design §6.1). Defensive fallbacks
    preserve behavior for legacy/forked Bricks variants.
  - Visual: drop the yellow pill in favor of small inline 'reBEM'
    text — muted by default, accent-colored on hover, with a subtle
    background tint. Reads as a discreet inline word, not a chip.
  - .rebemer-badge-host: inline-flex, flex: 0 0 auto, 6px horizontal
    margin so it slots cleanly into Bricks' row flex layout without
    competing with the title for space.
  - Badge text 'BEM' → 'reBEM' to match the product name.

Dev harness updated to reflect the real Bricks DOM (div.structure-item
> div.title + ul.actions), so the new selector logic is exercised
under npm run dev.
Resolves the 10 concerns surfaced in the design-level review of PR #130
(stored at semantic-review/2026-05-27-233854-pr-130.md, gitignored).

#1 Migrate-mode silent data loss on existing-class collision
  - Added validateMigrate() pre-validation pass that runs BEFORE any
    mutation. Detects three cases:
      a) Target class doesn't exist → safe (creates with seed).
      b) Target class exists, no key overlap → safe (additive merge).
      c) Target class exists, same key with different value → CONFLICT.
         Hard-errors before any mutation, with a message naming the
         conflicting keys and suggesting Add mode or a different name.
  - Per-op merge logic: when target exists with no conflicts, missing
    seed keys are added to its settings before upsert. upsertGlobalClass
    still never overwrites — we only ADD keys it didn't have. The
    'never overwrite' policy is preserved (overwrite = replacing
    existing values; this is purely additive).
  - End result: migrate is now safe in all three cases. removeMigratedKeys
    is only called after every key is guaranteed to be provided by the
    (new or existing) class with the same value.

#2 Modifier-mode auto-numbering rejected legitimate multi-row applies
  - Two siblings each producing card__image--lg is the canonical
    'attach this modifier to all of them' case, NOT a collision.
    Replaced the blanket name.includes('--') reject with a per-mode
    check: in modifier mode, intra-plan duplicates are intentional,
    no error and no numbering. upsertGlobalClass dedupes by name so
    all rows share the single class.

#3 + #10 'label' provenance not honored as authoritative
  - Added AUTHORITATIVE_PROVENANCE = new Set(['user', 'label']) and
    use it consistently in applyAutoNumbering. Both user-typed and
    structure-panel-label-derived names are treated as authoritative
    and never auto-renumbered. Two authoritative rows colliding is
    a hard error (mentions row count, not provenance details, since
    the user shouldn't need to know about reBEMer's internal vocabulary).
  - Updated apply.js JSDoc typedef to enumerate all five provenance
    values: 'user' | 'label' | 'element-type' | 'fallback' | 'auto-number'.
  - §0 status table no longer overstates this row.

#4 Auto-numbering didn't re-check post-numbered names
  - Added a final post-numbering integrity pass: in non-modifier
    modes, every op must have a unique finalClass after numbering.
    Catches the case where a user-typed card__image-1 collides with
    an auto-numbered card__image-1 from a different group. Modifier
    mode is exempt (duplicates are by design).

#5 Badge dataset flag never cleared on host disconnect
  - Removed the ATTACHED_FLAG dataset entirely. refreshBadges() now
    uses badgeInstances map state as the source of truth, with two
    explicit passes: reap stale (host disconnected), then mount any
    <li> without a live host inside it. The li.contains(existing.host)
    check handles the common Bricks pattern of rerendering inner
    .structure-item without removing the outer <li>.
  - Eliminates the permanent-skip bug where a row that was rendered
    once and then had its inner subtree rebuilt by Bricks would lose
    its badge forever.

#6 'Use existing' hint not migrate-aware
  - Row.svelte recommendation copy now branches on mode. Migrate-mode
    copy reflects the new validateMigrate semantics: 'On Apply,
    missing style keys will be merged into it. Conflicting values
    block the migration — pick a different name or use Add.'
  - Non-migrate modes keep the original 'attach the existing class
    instead of creating a duplicate' copy.

#7 Auto-numbered names didn't refresh the 'use existing' hint
  - Refactored apply.js to expose a pure buildPlan({ rootId, rows,
    mode }) → { ok, ops, error }. Returns the ops with their
    POST-numbering finalClass values.
  - BemPanel.svelte computes a  previewClassNames Map that
    runs buildPlan and indexes the result by row id. Reactive — re-runs
    when rows or mode change.
  - Row.svelte now accepts finalClassName as a prop (from the panel's
    preview map) instead of computing candidateClassName locally. The
    'use existing' hint matches what apply.js will actually produce,
    even after auto-numbering.
  - Same buildPlan is used by applyToSubtree, so preview and apply
    can never disagree.

#8 Unused-classes scan was non-deterministic past the cap
  - Added ORDER BY meta_id ASC to the postmeta query in
    class-rebemer-rest.php. The cap+1 truncation detection now pairs
    with reproducible content: two truncated runs on the same site
    return the same first 5000 rows.

#9 Apply has no mid-apply rollback (acknowledged spec-only)
  - Snapshot/rollback transactional apply (§10) remains spec-only,
    but the partial-failure path now (a) console.warn-logs the
    failure with the count of completed mutations, and (b) surfaces
    a partial-success toast: 'Applied to N elements before halting:
    <error>. State is partially applied — undo via Bricks (Cmd-Z)
    before retrying.' The user has actionable info instead of a
    bare error.

Also addresses cleanup
  - Added semantic-review/ to .gitignore so future review artifacts
    from the sub-agent don't accidentally land in commits.

Build artifacts regenerated. Bundle grew from 60.20 kB to 62.25 kB
JS (+0.6 kB gzip) — the new buildPlan refactor + validateMigrate
pre-pass + post-numbering check. Acceptable for the correctness gain.

What's still spec-only after this PR
  Items #4 and #6 in §0 status table are now ✅ shipped where they
  were partial; the remaining ❌ rows are unchanged (cross-page
  preflight, snapshot/rollback, undo ring buffer, i18n, reserved-name
  guard wired from inventory).
…tions

feat(rebemer): implement element-aware suggestions, auto-numbering, migrate mode, unused-class endpoint
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

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 48 minutes and 45 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: ebfdbb88-0d3b-4b12-89e6-da52455371ae

📥 Commits

Reviewing files that changed from the base of the PR and between 5563467 and a377f11.

📒 Files selected for processing (3)
  • integrations/bricks/editor-app/index.html
  • integrations/bricks/editor-app/src/lib/apply.js
  • integrations/bricks/includes/class-rebemer-rest.php
📝 Walkthrough

Walkthrough

This PR adds a "Migrate ID styles" operation mode to reBEMer, allowing designers to lift element-specific styling into reusable global classes with validated conflict detection, preview tables, and warning summaries. It also introduces a WordPress REST API endpoint to scan and report unused global classes across Bricks pages.

Changes

Migrate Mode Feature and Class Detection

Layer / File(s) Summary
Migrate Allowlist and Element Type Utilities
integrations/bricks/editor-app/src/lib/element-types.js, integrations/bricks/editor-app/src/lib/migrate-keys.js
Defines ELEMENT_TYPE_LABEL_MAP and LAYOUT_CONTAINER_TYPES for suggesting BEM element names from Bricks element types, and MIGRATE_ALLOWLIST with pickMigratableKeys/pickSkippedKeys helpers to identify safe style keys for lifting.
Plan-Based Class Application and Subtree API
integrations/bricks/editor-app/src/lib/apply.js, integrations/bricks/editor-app/src/lib/bricks-api.js
Introduces two-step workflow: buildPlan() validates mode, resolves classes, auto-numbers duplicates; refactored applyToSubtree() pre-validates migrate conflicts, seeds allowlisted settings, merges without overwriting, and removes lifted keys. Extended getSubtree() to include element name field for label suggestion.
Svelte Components for Migrate Mode UI
integrations/bricks/editor-app/src/components/BemPanel.svelte, integrations/bricks/editor-app/src/components/Row.svelte, integrations/bricks/editor-app/src/components/BemBadge.svelte
BemPanel snapshots global classes at open, computes migrate previews, seeds rich row metadata (suggestedFrom, migrateKeys, skippedKeys), and renders migrate selector with warning summary. Row tracks user-typed state, displays existence-based recommendations, and renders chip strips of migrated/skipped keys. BemBadge text updated to "reBEM".
Badge Map-Based Lifecycle Refactoring
integrations/bricks/editor-app/src/main.js
Refactors badge injection from dataset-flag gating to badgeInstances map source-of-truth with two-pass refresh (unmount detached, inject missing), improved DOM targeting for .structure-item layout.
CSS Styling for Migrate Mode Affordances
integrations/bricks/editor-app/src/styles/panel.css, integrations/bricks/assets/editor-app/app.css
Adds .rebemer-badge-host wrapper and muted inline badge styling, introduces .rebemer-panel__notice with --warn variant, adds .rebemer-row__hint and .rebemer-row__recommend hints, and introduces .rebemer-row__chips strip with label/empty/skip subcomponents and .rebemer-chip styling.
WordPress REST Endpoint for Unused Class Detection
integrations/bricks/includes/class-rebemer-rest.php, integrations/bricks/slashed-bricks.php
New Slashed_Bricks_ReBEMer_REST controller exposes GET /slashed-bricks/v1/rebemer/unused to scan postmeta for global-class references and report unused classes with scan metadata and truncation flag. Registered in plugin init.
Dev Harness and Implementation Status
integrations/bricks/editor-app/index.html, docs/rebemer.md
Updated HTML harness with .structure-item layout and inline script to inject mocked Bricks $_state (elements tree + seeded globalClasses). Updated rebemer.md with implementation status table linking shipped features to source locations.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • codeslash-dev/SLASHED#125: Provides the MVP reBEMer foundation; this PR extends the same core modules (apply.js, bricks-api.js, Svelte components) to add the migrate mode and preview/detection features.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'Fix/natural sort color palette' does not match the changeset, which focuses on reBEMer panel enhancements, migrate mode functionality, and REST API additions—not color palette sorting. Update the PR title to accurately describe the main changes, such as 'Add reBEMer migrate mode and REST endpoint for unused global classes' or 'Enhance reBEMer panel with migrate operation and class analysis'.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 fix/natural-sort-color-palette

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/editor-app/src/lib/apply.js (1)

140-145: ⚡ Quick win

Default missing provenance to a non-authoritative value.

Line 144 currently treats absent suggestedFrom as 'user', which can trigger false duplicate errors instead of auto-numbering for callers that omit this optional field.

Proposed patch
     ops.push({
       row,
       isRoot,
       finalClass,
-      suggestedFrom: row.suggestedFrom || 'user',
+      suggestedFrom: row.suggestedFrom || 'fallback',
     });
🤖 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 140 - 145, The
code defaults missing provenance to 'user' when building ops (see ops.push and
suggestedFrom), which makes absent provenance appear non-authoritative and
breaks auto-numbering; change the behavior to not default suggestedFrom to
'user' — either omit the suggestedFrom property when row.suggestedFrom is
undefined/null or explicitly set it to undefined/null (rather than 'user') so
callers that omit provenance are treated as authoritative and will trigger
auto-numbering instead of duplicate errors.
🤖 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/index.html`:
- Line 53: Update the harness copy that currently reads "Click the
<code>BEM</code> badge on any structure-panel row." to match the UI label by
changing it to "Click the <code>reBEM</code> badge on any structure-panel row."
so the instruction string in integrations/bricks/editor-app/index.html matches
the rendered badge label.

In `@integrations/bricks/includes/class-rebemer-rest.php`:
- Around line 121-124: The reported total (`$totalGlobalClasses` / response
totals) currently uses count($classes) even though malformed entries are
skipped, causing drift; update the loop that iterates over `$classes` (the
foreach handling `$cls`) to maintain an explicit processed counter (e.g.,
`$processedClasses++`) only for valid records (or decrement a running total when
you `continue`) and use that processed counter when building the response
instead of `count($classes)`; apply the same change to the other loop/location
referenced (the block around lines handling `$unused` and the similar code at
the later 153-154 area) so totals reflect only actually processed/accepted class
records.

---

Nitpick comments:
In `@integrations/bricks/editor-app/src/lib/apply.js`:
- Around line 140-145: The code defaults missing provenance to 'user' when
building ops (see ops.push and suggestedFrom), which makes absent provenance
appear non-authoritative and breaks auto-numbering; change the behavior to not
default suggestedFrom to 'user' — either omit the suggestedFrom property when
row.suggestedFrom is undefined/null or explicitly set it to undefined/null
(rather than 'user') so callers that omit provenance are treated as
authoritative and will trigger auto-numbering instead of duplicate errors.
🪄 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: 70369ba1-2e66-4ce4-9b5c-5cf508dc8363

📥 Commits

Reviewing files that changed from the base of the PR and between 692ea16 and 5563467.

📒 Files selected for processing (16)
  • .gitignore
  • docs/rebemer.md
  • integrations/bricks/assets/editor-app/app.css
  • integrations/bricks/assets/editor-app/app.js
  • integrations/bricks/editor-app/index.html
  • 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/lib/apply.js
  • integrations/bricks/editor-app/src/lib/bricks-api.js
  • integrations/bricks/editor-app/src/lib/element-types.js
  • integrations/bricks/editor-app/src/lib/migrate-keys.js
  • integrations/bricks/editor-app/src/main.js
  • integrations/bricks/editor-app/src/styles/panel.css
  • integrations/bricks/includes/class-rebemer-rest.php
  • integrations/bricks/slashed-bricks.php

Comment thread integrations/bricks/editor-app/index.html Outdated
Comment thread integrations/bricks/includes/class-rebemer-rest.php Outdated
- apply.js: default suggestedFrom to 'fallback' instead of 'user' so
  callers that omit provenance trigger auto-numbering rather than false
  duplicate errors
- class-rebemer-rest.php: use an explicit processed-classes counter so
  totalGlobalClasses reflects only valid entries, not malformed ones
- index.html: update dev-harness copy from 'BEM' to 'reBEM' to match
  the rendered badge label
@jackgranatowski
jackgranatowski merged commit d41ea4b into main May 28, 2026
5 checks passed
@jackgranatowski
jackgranatowski deleted the fix/natural-sort-color-palette 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.

3 participants