Claude-skill-registry-data migrate-component
Migrate WordPress components from v1 (page-parts/) or v2 (layouts/) to v3 (vendi-theme-parts/components/) structure. Use when migrating components, moving layouts to v3, or converting component files.
git clone https://github.com/majiayu000/claude-skill-registry-data
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/migrate-component" ~/.claude/skills/majiayu000-claude-skill-registry-data-migrate-component && rm -rf "$T"
data/migrate-component/SKILL.mdComponent Migration Skill: v1/v2 to v3
You are helping migrate WordPress theme components to the v3 structure (
vendi-theme-parts/components/). There are two migration paths:
- v2 → v3: ACF Flexible Layout components from
layouts/ - v1 → v3: Utility/site components from
orpage-parts/site/page-parts/page/
Your Role
Guide the user through migrating WordPress theme components one at a time. Each component migration includes:
- Moving files with git mv
- Creating a component class
- Refactoring the main template
- Migrating sub-layouts (if applicable)
- Creating documentation
The user will handle
git add and git commit commands - you should NOT run these.
Cleanup After Migration
After successfully migrating a component, clean up the old v2 folders:
- Check for leftover files: Look for any files in
that weren't migratedlayouts/[component_name]/ - Handle dead/unused files:
- If you find old sub-layouts or files that aren't referenced in the current code, move them to
_LEGACY/ - Only create the
folder if there are actually dead files to preserve_LEGACY/ - Example:
git mv layouts/[component_name]/layouts/old_file.php vendi-theme-parts/components/[component_name]/_LEGACY/old_file.php
- If you find old sub-layouts or files that aren't referenced in the current code, move them to
- Remove empty directories: After all files are moved, remove the empty v2 directories
- Use
to safely remove only empty directoriesrmdir - Example:
rmdir layouts/[component_name]/layouts && rmdir layouts/[component_name]
- Use
This ensures the codebase stays clean and old unused code is preserved in _LEGACY folders if needed.
Reference Components
v2 Components (Content/Layout):
- Simple:
,basic_copy_blockform - With custom root setup:
,accordioncontent_callout_block - With sub-layouts:
,accordion
,media_block
,eventscarousel_resources - Without region/wrap pattern:
media_block - Using VendiComponent:
ad_row
v1 Components (Utility/Site):
- Utility component:
ad-slot
v2 to v3 Migration (Content Components)
File Structure Transformation
v2 Structure (layouts/)
layouts/[component_name]/ ├── component.php ├── render.css ├── admin.css (optional, kept as-is) └── thumbnail.png
v3 Structure (vendi-theme-parts/components/)
vendi-theme-parts/components/[component_name]/ ├── [component_name].php (was component.php) ├── [component_name].class.php (new) ├── [component_name].css (was render.css) ├── [component_name].docs.yaml (new) ├── [component_name].thumbnail.png (was thumbnail.png) ├── admin.css (kept as-is, not used in v3) └── docs/ ├── overview.md └── accessibility.md
Migration Steps
Step 1: Examine the v2 Component
First, explore the existing component:
find layouts/[component_name] -type f
Read the main files:
(orlayouts/[component_name]/component.php
)[component_name].phplayouts/[component_name]/render.css- Any sub-layout files in
layouts/[component_name]/layouts/
Step 2: Move Files with git mv
Create the v3 directory and move files:
mkdir -p vendi-theme-parts/components/[component_name] git mv layouts/[component_name]/component.php vendi-theme-parts/components/[component_name]/[component_name].php git mv layouts/[component_name]/render.css vendi-theme-parts/components/[component_name]/[component_name].css git mv layouts/[component_name]/thumbnail.png vendi-theme-parts/components/[component_name]/[component_name].thumbnail.png
If
admin.css exists:
git mv layouts/[component_name]/admin.css vendi-theme-parts/components/[component_name]/admin.css
Step 3: Create Component Class
Create
[component_name].class.php in the component directory.
Component Class Hierarchy:
- Full component with auto-generated wrappers (BaseComponent
,<section>
,<div class="region">
)<div class="content-wrap">
- Simplified version with only root tag, no extra HTML wrappersVendiComponent
Conventions:
- Top-level components typically extend
BaseComponent - Sub-layouts commonly extend
VendiComponent - Choice depends on needed HTML structure, not strict rules
Purpose: Enable automatic asset loading (CSS/JS only loaded when component is used) and provide helper methods for cleaner templates.
Minimal Class Template:
<?php namespace Vendi\Theme\Component; use Vendi\Theme\BaseComponent; class [component_name] extends BaseComponent { public function __construct() { parent::__construct('[css-class-name]'); } protected function initComponent(): void { parent::initComponent(); // Add dynamic classes or attributes to root element // Example: $this->addRootClass('variant-' . get_sub_field('variant')); // Example: $this->addRootAttribute('data-role', 'carousel'); } // Override wrapper class names if v2 used different names protected function getContentWrapClassName(): string { return 'wrap'; // Default is 'content-wrap' } protected function getRegionWrapClassName(): string { return 'region'; // Default is 'region' (rarely needs override) } }
With Helper Methods (for complex templates):
<?php namespace Vendi\Theme\Component; use Vendi\Theme\BaseComponent; class [component_name] extends BaseComponent { public function __construct() { parent::__construct('[css-class-name]'); } protected function abortRender(): bool { if (!$this->getRequiredField()) { return true; } return parent::abortRender(); } public function getRequiredField(): ?array { $field = $this->getSubField('field_name'); if (!is_array($field)) { return null; } return $field; } // Add getter methods to simplify template public function getHeadline(): ?string { return $this->getSubField('headline'); } }
Important Notes:
- Add helper methods ONLY when templates have complex logic
- Don't create simple pass-through methods
- Keep templates simple and readable
Step 4: Refactor Main PHP Template
Understanding Auto-Generated Wrappers:
v3 automatically wraps content with:
<section class="[component-class]"> <div class="region"> <div class="content-wrap"> <!-- your content -->
Remove these wrappers from the template - they're handled by
renderComponentWrapperStart().
Standard v2 to v3 Conversion:
Before (v2):
<section class="section-[name] <?php esc_attr_e('dynamic-class-'.get_sub_field('field')); ?>"> <div class="region"> <div class="wrap"> <!-- content --> </div> </div> </section>
After (v3):
<?php use Vendi\Theme\Component\[component_name]; use Vendi\Theme\ComponentUtility; /** @var [component_name] $component */ $component = ComponentUtility::get_new_component_instance([component_name]::class); if (!$component->renderComponentWrapperStart()) { return; } ?> <!-- content only (all wrappers removed) --> <?php $component->renderComponentWrapperEnd();
Important Cases:
-
If v2 used
instead of<div class="wrap">
: Override<div class="content-wrap">
in the class to returngetContentWrapClassName()
to preserve existing CSS.'wrap' -
If component doesn't follow region/wrap pattern: Some components have custom structures like
. In these cases, ONLY remove the outer<section><div class="custom-class">...</div></section>
tag. Keep all other markup as-is.<section>Example:
// v2: <section class="media-block-container"><div class='narrow-left-aligned'>...</div></section> // v3: Only remove <section>, keep <div class='narrow-left-aligned'>...</div>
Step 5: Handle Sub-Layouts (if applicable)
If the component has sub-layouts in
layouts/[component_name]/layouts/:
Move Sub-Layout Files:
# For each sub-layout, create its own directory mkdir -p vendi-theme-parts/components/[component_name]/[field_name]/[layout_name] git mv layouts/[component_name]/layouts/[layout].php vendi-theme-parts/components/[component_name]/[field_name]/[layout_name]/[layout_name].php
Create Loader File:
Create
[field_name]/[field_name].php:
<?php while (have_rows("[field_name]")) { the_row(); $layout = get_row_layout(); if (!in_array($layout, ['layout1', 'layout2'], true)) { continue; } vendi_load_component_v3(['component_name', 'field_name', $layout]); }
Update Sub-Layout Files:
Critical: The global variable for state has been renamed in v3.
// v2 (OLD - don't use) global $vendi_layout_component_object_state; $data = $vendi_layout_component_object_state['key'] ?? null; // v3 (NEW - use this) global $vendi_component_object_state; $data = $vendi_component_object_state['key'] ?? null;
When sub-layouts need to pass state to other sub-layouts:
vendi_load_component_v3(['component_name', 'field_name', 'layout'], ['key' => $value]);
Note: There is NO
vendi_load_component_v3_with_state function. State is passed as the second parameter to vendi_load_component_v3.
Sub-Layout Classes (Optional):
Sub-layouts can have their own class files for complex logic. They commonly extend
VendiComponent:
<?php namespace Vendi\Theme\Component; use Vendi\Theme\VendiComponent; class [sub_layout_name] extends VendiComponent { public function __construct() { parent::__construct('[css-class-name]'); } public function getEvents(): array { return tribe_get_events(['posts_per_page' => 4, 'start_date' => 'now']); } protected function abortRender(): bool { if (!$this->getEvents()) { return true; } return parent::abortRender(); } }
Reference
events/events/recent_events for a complete example.
Step 6: Create Documentation
Create documentation structure:
mkdir -p vendi-theme-parts/components/[component_name]/docs
Create
:[component_name].docs.yaml
component-name: [Human Readable Name] role: component example-prefix: [component-name] parent-field: content_components pages: - Overview - Accessibility
Create
:
Describe:docs/overview.md
- Component purpose and functionality
- When to use this component
- When to choose a different component
- Available component options and fields
- Visual elements and features
- Any special behaviors
Infer content from the component template, class, and CSS.
Create
:
Document accessibility considerations:docs/accessibility.md
- Heading hierarchy
- Color and contrast requirements
- Semantic HTML structure
- Image alt text requirements
- Interactive element accessibility
- Keyboard navigation
- Screen reader announcements
- Focus management
- Motion and animation considerations
Reference
basic_copy_block, content_callout_block, media_block, or carousel_resources for examples.
Code Style Guidelines
Template Structure
Templates follow a three-part structure:
<?php // === TOP: Setup === use Vendi\Theme\Component\example; use Vendi\Theme\ComponentUtility; /** @var example $component */ $component = ComponentUtility::get_new_component_instance(example::class); if (!$component->renderComponentWrapperStart()) { return; } // Pre-calculate any needed variables $items = $component->getItems(); ?> <!-- === MIDDLE: Template with alternative syntax === --> <?php if ($condition): ?> <div class="example">Content</div> <?php endif; ?> <?php foreach ($items as $item): ?> <p><?php echo $item; ?></p> <?php endforeach; ?> <?php // === BOTTOM: Cleanup === $component->renderComponentWrapperEnd();
Alternative PHP Syntax
ALWAYS use alternative PHP syntax in the middle (template) section:
✅ Correct:
<?php if ($condition): ?> <div>Content</div> <?php endif; ?> <?php foreach ($items as $item): ?> <p><?php echo $item; ?></p> <?php endforeach; ?> <?php while (have_rows('field')): ?> <?php the_row(); ?> <?php endwhile; ?>
❌ Incorrect (don't use curly braces in template section):
<?php if ($condition) { ?> <div>Content</div> <?php } ?> <?php foreach ($items as $item) { ?> <p><?php echo $item; ?></p> <?php } ?>
HTML Attributes
Always use double quotes for HTML attributes:
✅
<div class="example" data-role="carousel">
❌ <div class='example' data-role='carousel'>
SVG Loading
Use the
vendi_get_svg() helper function:
✅
<?php vendi_get_svg('/svgs/icon.svg'); ?>
❌ <?php echo file_get_contents(VENDI_CUSTOM_THEME_PATH . '/svgs/icon.svg'); ?>
Variable Usage
Echo variables directly - don't create intermediate variables:
✅
<?php echo vendi_fly_get_attachment_picture($id, 'size'); ?>
❌ <?php $image = vendi_fly_get_attachment_picture($id, 'size'); echo $image; ?>
Class Methods
Avoid rendering from class methods. Classes should not output to the HTTP stream.
Use getter methods that return data, then render in templates:
✅ Correct:
// Class public function getItems(): array { return ['item1', 'item2']; } // Template <?php foreach ($component->getItems() as $item): ?> <p><?php echo $item; ?></p> <?php endforeach; ?>
❌ Incorrect:
// Class - DON'T DO THIS public function renderItems(): void { foreach ($this->getItems() as $item) { echo "<p>{$item}</p>"; } }
Exception: Very rare cases where rendering from class is absolutely necessary.
Key Principles
- One file per commit - User handles git add/commit, NOT you
- Preserve template logic - Don't refactor working code unless necessary
- Dynamic classes go in
usinginitComponent()
oraddRootClass()addRootAttribute() - Admin.css is kept but not used in v3
- Helper methods only for complex template needs, not simple pass-throughs
- Alternative PHP syntax in template sections (if:/endif, foreach:/endforeach)
- Double quotes for all HTML attributes
- No rendering from class methods - return data, render in templates
Common Patterns
Loading Function Calls
v2 to v3 conversions:
| v2 Function | v3 Function | Notes |
|---|---|---|
| | Array syntax in v3 |
| | State is second parameter |
| | Renamed global variable |
Dynamic Root Classes
Add dynamic classes in
initComponent():
protected function initComponent(): void { parent::initComponent(); // Add class based on field value $this->addRootClass('variant-' . get_sub_field('variant')); // Conditional class if ($this->shouldShowAd()) { $this->addRootClass('show-ad'); } // Add data attributes $this->addRootAttribute('data-role', 'carousel-container'); }
v2 Migration Workflow
When the user asks to migrate a v2 component:
- Explore: Read the v2 component files to understand structure
- Move Files: Use git mv to move files to v3 structure
- Create Class: Build component class with appropriate methods
- Refactor Template: Update main PHP file to v3 pattern
- Handle Sub-Layouts: Migrate any sub-layouts if present
- Document: Create docs.yaml and markdown documentation
- Clean Up: Remove empty v2 directories with rmdir
- Remind: Tell user the component is ready to commit (they handle git add/commit)
Use the TodoWrite tool to track progress through these steps for complex migrations.
Always reference the existing migrated components for patterns and examples.
v1 to v3 Migration (Utility/Site Components)
Overview
v1 components are utility components stored in
page-parts/site/ or page-parts/page/ directories. These are not flexible content components - they're called from other components using loader functions.
Key Differences from v2:
- Single files, not directories (e.g.,
)page-parts/site/ad-slot.php - No CSS/JS files to migrate (assets stored elsewhere or inline)
- No sub-layouts (simple utility components)
- Documentation: Only
(no accessibility.md for utility components)overview.md - Loader functions: Called via
orvendi_load_site_component()vendi_load_page_component() - State via global: Use
global for passed parameters$vendi_component_object_state
File Structure Transformation
v1 Structure (page-parts/)
page-parts/site/ad-slot.php (single file)
v3 Structure (vendi-theme-parts/components/)
vendi-theme-parts/components/ad-slot/ ├── ad-slot.php (migrated from page-parts/site/) ├── ad-slot.class.php (new) ├── ad-slot.docs.yaml (new) └── docs/ └── overview.md (only overview, no accessibility)
Migration Steps
Step 1: Identify Component Type
v1 components are loaded via:
vendi_load_site_component('component-name')vendi_load_site_component_with_state('component-name', ['key' => $value])vendi_load_page_component('component-name')
These map to v3 as:
vendi_load_component_v3(['component-name'])vendi_load_component_v3(['component-name'], ['key' => $value])
Step 2: Move File
mkdir -p vendi-theme-parts/components/[component-name] git mv page-parts/site/[component-name].php vendi-theme-parts/components/[component-name]/[component-name].php
Step 3: Create Class File
Create
[component-name].class.php extending VendiComponent (utility components don't need full wrappers):
<?php namespace Vendi\Theme\Component; use Vendi\Theme\VendiComponent; class component_name extends VendiComponent { public function __construct() { parent::__construct('component-class-name'); } protected function abortRender(): bool { if (!$this->getRequiredData()) { return true; } return parent::abortRender(); } // Getter methods for state data public function getStateValue(): mixed { global $vendi_component_object_state; return $vendi_component_object_state['key'] ?? null; } // Helper methods for data from state objects public function getSomething(): ?string { return get_field('field_name', $this->getStateValue()); } }
Important: Utility components still use
$vendi_component_object_state global to access passed state.
Step 4: Refactor Template
Update the template to use component class:
<?php use Vendi\Theme\Component\component_name; use Vendi\Theme\ComponentUtility; /** @var component_name $component */ $component = ComponentUtility::get_new_component_instance(component_name::class); if (!$component->renderComponentWrapperStart()) { return; } // Extract data using component methods $value = $component->getSomething(); ?> <div class="content"> <?php if ($value): ?> <?php echo esc_html($value); ?> <?php endif; ?> </div> <?php $component->renderComponentWrapperEnd();
Code Style:
- Use alternative PHP syntax (if:/endif, foreach:/endforeach)
- Double quotes for HTML attributes
- Inline variable assignment for single-use variables
Step 5: Update All Loader References
Find all instances of the old loader functions and update them:
Find references:
grep -r "vendi_load_site_component.*'component-name'" --include="*.php"
Update pattern:
// Old v1 vendi_load_site_component_with_state('component-name', ['key' => $value]); // New v3 vendi_load_component_v3(['component-name'], ['key' => $value]);
Update ALL occurrences across:
- Other v3 components
- v2 components (layouts/)
- v1 components (page-parts/)
Step 6: Create Documentation
docs.yaml (minimal for utility components):
component-name: [Human Readable Name] role: component example-prefix: [component-name] parent-field: site # or 'page' depending on origin pages: - Overview
docs/overview.md:
- Describe component purpose and function
- Mark as "Utility Component" or "Site Component"
- Document that it's called from other components, not flexible content
- List required state parameters with types
- Show usage example with
vendi_load_component_v3() - Document any special behaviors or integrations
No accessibility.md - Utility components don't need accessibility documentation.
Step 7: No CSS/JS Migration
v1 components typically don't have dedicated CSS/JS files in their directories. Assets are:
- Inline in the template (keep as-is)
- Loaded from external sources (keep as-is)
- In global theme files (leave alone)
Do not search for or migrate CSS/JS files - they're optional and stored elsewhere.
Step 8: Clean Up
The original file was moved with
git mv, so no cleanup needed. The empty page-parts/site/ or page-parts/page/ directory will remain (it may contain other components).
v1 Migration Workflow
When the user asks to migrate a v1 component:
- Identify: Confirm it's from page-parts/ directory
- Move File: Use git mv to move single file to v3 structure
- Create Class: Build utility component class (VendiComponent)
- Refactor Template: Update to use component class and state from global
- Update References: Find and replace ALL loader function calls across codebase
- Document: Create docs.yaml and overview.md (no accessibility.md)
- Remind: Tell user the component is ready to commit (they handle git add/commit)
Use the TodoWrite tool to track progress through these steps.
Key Reminders for v1 Migrations
- Use VendiComponent - Utility components don't need full BaseComponent wrappers
- Access state via global -
still used in class methods$vendi_component_object_state - Update ALL references - Search entire codebase for loader function calls
- No CSS/JS - Don't search for or migrate asset files
- Only overview.md - No accessibility documentation for utility components
- Single file - No sub-layouts or complex directory structures