Block Refactoring — Evolution of Existing Blocks
Analyze and improve an existing block after its first implementation. Four axes of evolution: design drift detection, CSS coverage analysis, variation expansion, and gap/migration detection.
Announce at start: "I'm using the block-refactoring skill to evolve the block."
When to use
- Block shipped in a previous PR and design has evolved since
- CSS bundle includes rules/tokens that are no longer used
- New design tokens exist in
app.css/design-tokens.mdthat the block could leverage - Legacy block still uses v1 pattern (
.b-{slug}class, dual selector, no custom element) - You suspect implementation diverged from design reference during initial build
When NOT to use
- Creating a new block from scratch → use
/block-scaffoldinginstead - Project-wide convention audit → use
/reviewing - Non-block UI component → Blade components are not ACF blocks
Input
$ARGUMENTS
Resolve to {ClassName} and {slug}. If not provided, ask.
See references/evolution-axes.md for the 4 evolution axes.
Refactoring NEVER rebuilds from scratch. For a full re-scaffold, delegate to /block-scaffolding as a fallback.
Procedure
Phase 0 — Resolve block identity and locate files
From the argument, identify:
{ClassName}— e.g.HeroSection{slug}— e.g.hero-section- 5 target files (one may be absent on legacy blocks):
app/Blocks/{ClassName}.phpresources/views/blocks/{slug}.blade.phpresources/css/blocks/{slug}.cssresources/js/blocks/{slug}.js(may not exist — legacy)resources/css/editor.css(check for@import './blocks/{slug}.css')
Read all present files.
Phase 0b — Shared component inventory
Glob resources/views/components/*.blade.php in the target project (if a path is known, otherwise skip this step).
Build a component inventory table:
| Component slug | File | Likely use |
|---|---|---|
section-header |
section-header.blade.php |
<x-eyebrow> + <h2> pairing |
button |
button.blade.php |
<a> or <button> with utility classes |
card |
card.blade.php |
Repeated card structure |
Keep this table in context — G9 in Axis 4 will reference it to report concrete component names instead of generic suggestions.
If resources/views/components/ does not exist or is empty, note "No shared components found" and proceed.
Phase 1 — Classify current pattern version
Inspect the view file and the CSS file:
| Signal | Version |
|---|---|
View has <block-{slug}> custom element |
v2 |
View uses $attributes->merge() on a <section class="b-{slug}"> |
v1 |
CSS scoped to block-{slug} tag selector |
v2 |
CSS scoped to .b-{slug} class selector |
v1 |
CSS has dual selector &.is-style-*, .is-style-* & |
v1 |
CSS has single selector .is-style-* block-{slug} |
v2 |
JS file at resources/js/blocks/{slug}.js |
v2 |
Mark the block's current version (v1 / v2 / mixed) before proceeding.
AXIS 1 — Design drift detection
Prerequisites: a design reference must be available. Check in order:
- MCP design tool with the project's file open (Pencil / Paper / Figma / Stitch)
docs/plans/*/assets/section-{slug}-ref.pngon diskdocs/plans/*/assets/section-{slug}-spec.mdon disk
If none available, report drift: NOT_VERIFIED and skip to Axis 2.
Execution:
- Dispatch
visual-verifieragent with:url: current environment's URL for a page rendering the blockselector:block-{slug}(v2) or.b-{slug}(v1)ref: the reference path found abovespec: spec file path if available
- Collect: MATCH | DRIFT | MISSING | FAIL_ARBITRARY_VALUES
- If DRIFT: capture the exact divergences (typography size, color hex, spacing px) for Phase 6 report
For detailed axis procedures (CSS coverage, variation expansion, gap checks G1–G8), see references/evolution-axes.md.
G9. Component reuse gap
Grep the view for co-occurring <x-eyebrow and <h2 inline — these should use
<x-section-header> instead. Also grep for <a elements carrying button utility
classes (e.g. btn-, rounded-, px-, py-, font-) — these should be
<x-button>.
Each instance where a component from the Phase 0b inventory exists but is not used: flag as IMPROVEMENT, naming the component (e.g., "use <x-section-header> instead of inline <x-eyebrow> + <h2>").
G10. CSS custom property cascade not used
Detection — check the view for any of:
match($tone)expressions returning Tailwind class strings (e.g.match($tone) { 'fg' => 'text-fg', 'dark' => 'text-depth-fg' })- Props declared as
tone="fg"/tone="dark"/variant="*-dark"encoding color context in PHP and passing it to child components - Hardcoded color utility classes (
text-depth-fg,text-identity,text-white) applied directly on semantic elements (h2,p,span) instead of via inherited CSS variables
Colors must cascade from custom properties in the block's CSS; the view must not encode color context via conditional logic or hardcoded utilities. Each instance is CRITICAL.
When detected, generate corrected CSS for the Phase 6 report:
- Run inline component inventory: glob
resources/views/components/*.blade.php, grep each forvar(--)patterns → build local variable name registry (same logic as block-scaffolding Phase 0b; runs inline here, not delegated) - Read
design-guide.md## Tokens → Colorsto determine background context:- If
design-guide.mdexists atdocs/plans/*/components/*/design-guide.md→ apply decision table - If
design-guide.mdis absent → treat as Ambiguous; generate with/* VERIFY: design-guide.md not found — confirm background context */
- If
- Apply decision table:
Token found Background CSS action bg-depth,bg-primary,bg-dark,bg-inverseDark Override cascade vars with *-on-darkequivalentsbg-identity,bg-sage,bg-accentIdentity (brand color bg) Override cascade vars with *-on-identityequivalents (e.g.var(--color-identity-fg))bg-bg,bg-surface,bg-muted, absentLight (default) No override — inherit :rootdefaultsUnrecognized token Ambiguous Generate with /* VERIFY: background context unknown */ - Include generated CSS in Phase 6 report (see report-format.md G10 section template)
G11. nl2br on non-textarea fields
For each nl2br(esc_html($var)) call in the view, trace the variable back to its
ACF field definition in app/Blocks/{ClassName}.php. If the field uses addText()
(single-line text), nl2br() is a no-op at best and misleading at worst. Flag as
IMPROVEMENT — remove nl2br() or change the field to addTextarea() / addWysiwyg().
Phase 6 — Report and propose
See references/report-format.md for the refactoring report format.
Phase 7 — Apply approved changes
After user approves proposals, apply in order: CSS removals → variation expansions → gap fixes. Run lando theme-build && lando flush to verify.
Phase 8 — Verification
Use Playwright MCP to screenshot at canonical width and verify all variations. Then:
git commit -m "refactor(blocks): {slug} — {summary of applied changes}"
Anti-drift — don't reintroduce
See /block-scaffolding anti-drift table — same rules apply during refactor. Every
proposal in Phase 6 should, after applied, produce code that would pass /block-scaffolding
as if the block were being created today.