@stellisoft/stellify-mcp 0.1.35 → 0.1.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -7
- package/dist/index.js +43 -11
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/wordpress-import.md +84 -112
package/README.md
CHANGED
|
@@ -374,12 +374,80 @@ Create a new UI element. Provide either `page` (route UUID) for root elements, o
|
|
|
374
374
|
**Parameters:**
|
|
375
375
|
- `type` (required): Element type - one of:
|
|
376
376
|
- HTML5: `s-wrapper`, `s-input`, `s-form`, `s-svg`, `s-shape`, `s-media`, `s-iframe`
|
|
377
|
-
- Components: `s-
|
|
377
|
+
- Components: `s-transition`, `s-freestyle`, `s-motion`
|
|
378
378
|
- Blade: `s-directive`
|
|
379
379
|
- Shadcn/ui: `s-chart`, `s-table`, `s-combobox`, `s-accordion`, `s-calendar`, `s-contiguous`
|
|
380
380
|
- `page` (optional): UUID of the page/route (for root elements)
|
|
381
381
|
- `parent` (optional): UUID of the parent element (for child elements)
|
|
382
382
|
|
|
383
|
+
**Using `s-directive` for Blade Conditionals:**
|
|
384
|
+
|
|
385
|
+
`s-directive` elements output Blade directives (like `@if`, `@foreach`, `@endif`). They are **sibling elements** — they don't wrap children. To conditionally render content:
|
|
386
|
+
|
|
387
|
+
1. Create an `s-directive` element with a statement for the opening directive (e.g., `@if(...)`)
|
|
388
|
+
2. Create the content element(s) as the **next sibling(s)**
|
|
389
|
+
3. Create another `s-directive` element with a statement for the closing directive (e.g., `@endif`)
|
|
390
|
+
|
|
391
|
+
Example — conditionally showing an image:
|
|
392
|
+
```
|
|
393
|
+
// 1. Create statement for @if
|
|
394
|
+
create_statement_with_code({
|
|
395
|
+
file: "<file-uuid>",
|
|
396
|
+
code: "@if($item->featured_image)"
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
// 2. Create opening directive element and set its statement
|
|
400
|
+
create_element({ type: "s-directive", page: "<route-uuid>" })
|
|
401
|
+
update_element({ uuid: "<if-directive-uuid>", data: { "statement": "<if-statement-uuid>" } })
|
|
402
|
+
|
|
403
|
+
// 3. Create the image as the next sibling
|
|
404
|
+
html_to_elements({ page: "<route-uuid>", elements: "<img class=\"w-full\" />" })
|
|
405
|
+
// Then update with dynamic src:
|
|
406
|
+
update_element({ uuid: "<img-uuid>", data: { "srcField": "featured_image" } })
|
|
407
|
+
|
|
408
|
+
// 4. Create statement for @endif
|
|
409
|
+
create_statement_with_code({ file: "<file-uuid>", code: "@endif" })
|
|
410
|
+
|
|
411
|
+
// 5. Create closing directive element
|
|
412
|
+
create_element({ type: "s-directive", page: "<route-uuid>" })
|
|
413
|
+
update_element({ uuid: "<endif-directive-uuid>", data: { "statement": "<endif-statement-uuid>" } })
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
The three elements render in order as siblings:
|
|
417
|
+
```blade
|
|
418
|
+
@if($item->featured_image)
|
|
419
|
+
<img class="w-full" src="{{ $item->featured_image }}" />
|
|
420
|
+
@endif
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**Using `s-directive` for Loops:**
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
// 1. Create @foreach directive
|
|
427
|
+
create_statement_with_code({ file: "<file-uuid>", code: "@foreach($posts as $item)" })
|
|
428
|
+
create_element({ type: "s-directive", page: "<route-uuid>" })
|
|
429
|
+
update_element({ uuid: "<foreach-uuid>", data: { "statement": "<foreach-statement-uuid>" } })
|
|
430
|
+
|
|
431
|
+
// 2. Create loop content (article with dynamic fields)
|
|
432
|
+
html_to_elements({ page: "<route-uuid>", elements: "<article><h2></h2><p></p></article>" })
|
|
433
|
+
// Update elements to use loop item fields:
|
|
434
|
+
update_element({ uuid: "<h2-uuid>", data: { "textField": "title" } }) // → {{ $item->title }}
|
|
435
|
+
update_element({ uuid: "<p-uuid>", data: { "textField": "excerpt" } }) // → {{ $item->excerpt }}
|
|
436
|
+
|
|
437
|
+
// 3. Create @endforeach directive
|
|
438
|
+
create_statement_with_code({ file: "<file-uuid>", code: "@endforeach" })
|
|
439
|
+
create_element({ type: "s-directive", page: "<route-uuid>" })
|
|
440
|
+
update_element({ uuid: "<endforeach-uuid>", data: { "statement": "<endforeach-statement-uuid>" } })
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**Loop Item Attributes:**
|
|
444
|
+
Inside `@foreach` loops, use these attributes on elements to reference `$item`:
|
|
445
|
+
- `textField: "fieldName"` → outputs `{{ $item->fieldName }}`
|
|
446
|
+
- `hrefField: "fieldName"` → outputs `href="{{ $item->fieldName }}"`
|
|
447
|
+
- `srcField: "fieldName"` → outputs `src="{{ $item->fieldName }}"`
|
|
448
|
+
- `hrefExpression: "{{ route('posts.show', $item->slug) }}"` → for complex expressions
|
|
449
|
+
- `srcExpression`, `altExpression` → same pattern for other attributes
|
|
450
|
+
|
|
383
451
|
---
|
|
384
452
|
|
|
385
453
|
#### `update_element`
|
|
@@ -397,7 +465,18 @@ Update an existing UI element.
|
|
|
397
465
|
- `locked`: Prevent editing (boolean)
|
|
398
466
|
- `tag`: HTML tag (div, input, button, etc.)
|
|
399
467
|
- `classes`: CSS classes array `["class1", "class2"]`
|
|
400
|
-
- `text`:
|
|
468
|
+
- `text`: Static text content
|
|
469
|
+
- `statements`: Array of statement UUIDs for dynamic Blade content
|
|
470
|
+
|
|
471
|
+
**Loop item fields** (for elements inside `@foreach` loops, references `$item`):
|
|
472
|
+
- `textField`: Field name → outputs `{{ $item->fieldName }}`
|
|
473
|
+
- `hrefField`: Field name → outputs `href="{{ $item->fieldName }}"`
|
|
474
|
+
- `srcField`: Field name → outputs `src="{{ $item->fieldName }}"`
|
|
475
|
+
|
|
476
|
+
**Expression attributes** (for complex Blade expressions):
|
|
477
|
+
- `hrefExpression`: Full Blade expression for href (e.g., `"{{ route('posts.show', $item->slug) }}"`)
|
|
478
|
+
- `srcExpression`: Full Blade expression for src
|
|
479
|
+
- `altExpression`: Full Blade expression for alt
|
|
401
480
|
|
|
402
481
|
**Event handlers** (set value to method UUID):
|
|
403
482
|
- `click`: @click
|
|
@@ -480,11 +559,6 @@ html_to_elements(page: routeUUID, elements: "<main>...</main>")
|
|
|
480
559
|
html_to_elements(page: routeUUID, elements: "<footer>...</footer>")
|
|
481
560
|
```
|
|
482
561
|
|
|
483
|
-
**Alternative approach (wrap in a single container):**
|
|
484
|
-
```
|
|
485
|
-
html_to_elements(page: routeUUID, elements: "<div class='min-h-screen flex flex-col'><header>...</header><main>...</main><footer>...</footer></div>")
|
|
486
|
-
```
|
|
487
|
-
|
|
488
562
|
**Features:**
|
|
489
563
|
- Parses HTML structure
|
|
490
564
|
- Creates all elements with proper nesting
|
package/dist/index.js
CHANGED
|
@@ -569,16 +569,15 @@ Use the returned UUID with html_to_elements (page parameter) or get_route for fu
|
|
|
569
569
|
},
|
|
570
570
|
{
|
|
571
571
|
name: 'create_element',
|
|
572
|
-
description: `Create a UI element. Provide page (route UUID) for root elements, or parent (element UUID) for children
|
|
572
|
+
description: `Create a UI element. Provide page (route UUID) for root elements, or parent (element UUID) for children.`,
|
|
573
573
|
inputSchema: {
|
|
574
574
|
type: 'object',
|
|
575
575
|
properties: {
|
|
576
576
|
type: {
|
|
577
577
|
type: 'string',
|
|
578
578
|
enum: [
|
|
579
|
-
's-wrapper', 's-input', 's-form', 's-svg', 's-shape', 's-media',
|
|
580
|
-
's-
|
|
581
|
-
's-directive'
|
|
579
|
+
's-wrapper', 's-input', 's-form', 's-svg', 's-shape', 's-media',
|
|
580
|
+
's-freestyle', 's-directive'
|
|
582
581
|
],
|
|
583
582
|
description: 'Element type - must be one of the valid Stellify element types',
|
|
584
583
|
},
|
|
@@ -596,7 +595,32 @@ Use the returned UUID with html_to_elements (page parameter) or get_route for fu
|
|
|
596
595
|
},
|
|
597
596
|
{
|
|
598
597
|
name: 'update_element',
|
|
599
|
-
description: `Update a UI element. Data object: tag, classes, text, event handlers (method UUIDs), classBindings. Set 'name' on root elements to create Blade views (e.g., name="notes.index" for view('notes.index'))
|
|
598
|
+
description: `Update a UI element. Data object: tag, classes, text, event handlers (method UUIDs), classBindings. Set 'name' on root elements to create Blade views (e.g., name="notes.index" for view('notes.index')).
|
|
599
|
+
|
|
600
|
+
**For elements inside @foreach loops (SSR/Blade):**
|
|
601
|
+
Use these attributes to reference the loop variable (defaults to \`$item\`):
|
|
602
|
+
- \`textField\`: Field name for text content → outputs \`{{ $item->fieldName }}\`
|
|
603
|
+
- \`hrefField\`: Field name for href → outputs \`href="{{ $item->fieldName }}"\` (field value ONLY, no prefix)
|
|
604
|
+
- \`srcField\`: Field name for src → outputs \`src="{{ $item->fieldName }}"\`
|
|
605
|
+
|
|
606
|
+
**For hrefs with path prefixes (IMPORTANT):**
|
|
607
|
+
\`hrefField\` outputs ONLY the field value. There is NO \`hrefPrefix\` attribute.
|
|
608
|
+
For links like \`/post/slug-here\`, you MUST use \`hrefExpression\`:
|
|
609
|
+
- \`hrefExpression: "/post/{{ $item->slug }}"\` → outputs \`href="/post/{{ $item->slug }}"\`
|
|
610
|
+
- \`hrefExpression: "/category/{{ $item->slug }}"\` → outputs \`href="/category/{{ $item->slug }}"\`
|
|
611
|
+
|
|
612
|
+
**For complex Blade expressions in attributes:**
|
|
613
|
+
Use expression attributes when you need more than simple field access:
|
|
614
|
+
- \`hrefExpression\`: Blade expression for href → outputs \`href="..."\` with the expression
|
|
615
|
+
- \`srcExpression\`: Blade expression for src → outputs \`src="..."\` with the expression
|
|
616
|
+
- \`altExpression\`: Blade expression for alt → outputs \`alt="..."\` with the expression
|
|
617
|
+
|
|
618
|
+
Examples:
|
|
619
|
+
- Path prefix: \`hrefExpression: "/post/{{ $item->slug }}"\`
|
|
620
|
+
- Route helper: \`hrefExpression: "{{ route('posts.show', $item->slug) }}"\`
|
|
621
|
+
|
|
622
|
+
**For Blade text content:**
|
|
623
|
+
Use the \`statements\` array with statement UUIDs containing Blade code. The statement's \`code\` property will be output directly for Blade to evaluate.`,
|
|
600
624
|
inputSchema: {
|
|
601
625
|
type: 'object',
|
|
602
626
|
properties: {
|
|
@@ -718,12 +742,20 @@ When HTML contains multiple root-level elements (e.g., <header>, <main>, <footer
|
|
|
718
742
|
**@click auto-wiring:** Pass 'file' UUID to auto-resolve @click="methodName" handlers. Methods must exist in the file first.
|
|
719
743
|
|
|
720
744
|
**Blade Syntax Handling:**
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
745
|
+
For SSR/Blade pages, do NOT pass raw Blade expressions in text or attributes. The HTML parser stores them literally which causes rendering issues. Instead:
|
|
746
|
+
|
|
747
|
+
1. **For static HTML:** Pass clean HTML without Blade syntax, then use \`update_element\` to add dynamic behavior
|
|
748
|
+
2. **For loop content:** After creating elements, use \`update_element\` with:
|
|
749
|
+
- \`textField\`, \`hrefField\`, \`srcField\` for simple field access (outputs \`{{ $item->field }}\`)
|
|
750
|
+
- \`hrefExpression\`, \`srcExpression\`, \`altExpression\` for paths with prefixes or complex expressions
|
|
751
|
+
- \`statements\` array with statement UUIDs for text content with Blade code
|
|
752
|
+
3. **For conditionals:** Use \`s-directive\` elements as siblings (see update_element docs)
|
|
753
|
+
|
|
754
|
+
**IMPORTANT - Links with path prefixes:**
|
|
755
|
+
\`hrefField\` outputs ONLY the field value with no prefix. There is NO \`hrefPrefix\` attribute.
|
|
756
|
+
For links like \`/post/my-slug\`, use \`hrefExpression: "/post/{{ $item->slug }}"\` instead.
|
|
725
757
|
|
|
726
|
-
|
|
758
|
+
**Loop variable:** Inside \`@foreach\` loops created with \`s-directive\`, the default loop variable is \`$item\`. Use \`textField: "title"\` to output \`{{ $item->title }}\`.
|
|
727
759
|
|
|
728
760
|
Prefer SVG icons over emoji (encoding issues).`,
|
|
729
761
|
inputSchema: {
|
|
@@ -1477,7 +1509,7 @@ const SERVER_INSTRUCTIONS = `Stellify is a coding platform where code is stored
|
|
|
1477
1509
|
// Create MCP server
|
|
1478
1510
|
const server = new Server({
|
|
1479
1511
|
name: 'stellify-mcp',
|
|
1480
|
-
version: '0.1.
|
|
1512
|
+
version: '0.1.37',
|
|
1481
1513
|
}, {
|
|
1482
1514
|
capabilities: {
|
|
1483
1515
|
tools: {},
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/Stellify-Software-Ltd/stellify-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.37",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@stellisoft/stellify-mcp",
|
|
14
|
-
"version": "0.1.
|
|
14
|
+
"version": "0.1.37",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -12,10 +12,6 @@ You are importing a WordPress site into Stellify by reading its PHP theme files
|
|
|
12
12
|
|
|
13
13
|
**Match the rendering approach of the WordPress site.** If content is server-rendered in WordPress (which most of it will be — posts, pages, archives, navigation), create Blade templates. If a feature uses JavaScript for client-side interactivity (AJAX forms, dynamic filtering, live search, infinite scroll, modals, sliders), create a Vue component for that specific piece. The default is Blade — only reach for Vue when the WordPress source is doing something that genuinely requires JS.
|
|
14
14
|
|
|
15
|
-
## Step 0 — Discover Tools
|
|
16
|
-
|
|
17
|
-
List all available Stellify MCP tools. Understand what each tool does, what parameters it requires, and any dependencies between them (e.g. you need a file before you can add methods to it, you need a method before you can add statements). Print a summary of the tools and their creation order.
|
|
18
|
-
|
|
19
15
|
## Step 1 — Identify the Active Theme
|
|
20
16
|
|
|
21
17
|
Look in `./wp-content/themes/` to find the active theme. If there are multiple themes, check for the most recently modified one, or the one with the most template files. If unsure, ask which theme to import.
|
|
@@ -60,60 +56,13 @@ Read functions.php thoroughly. Extract:
|
|
|
60
56
|
|
|
61
57
|
## Step 4 — Plan the Laravel Structure
|
|
62
58
|
|
|
63
|
-
Before calling any Stellify tools,
|
|
64
|
-
|
|
65
|
-
### Models
|
|
66
|
-
- Post (always — WordPress core)
|
|
67
|
-
- Page (always — WordPress core)
|
|
68
|
-
- Category, Tag (if used)
|
|
69
|
-
- Any custom post types found in Step 3
|
|
70
|
-
- User (if the theme has author pages)
|
|
71
|
-
|
|
72
|
-
### Controllers
|
|
73
|
-
- PostController (index, show) — handles blog listing and single posts
|
|
74
|
-
- PageController (show) — handles static pages
|
|
75
|
-
- One controller per custom post type
|
|
76
|
-
- HomeController — if front-page.php or a static homepage exists
|
|
77
|
-
- SearchController — if search.php exists
|
|
78
|
-
|
|
79
|
-
### Routes (web.php)
|
|
80
|
-
Map the WordPress URL structure:
|
|
81
|
-
- `/` → HomeController or PostController@index
|
|
82
|
-
- `/blog` or `/posts` → PostController@index (if static homepage)
|
|
83
|
-
- `/{post-slug}` → PostController@show
|
|
84
|
-
- `/{page-slug}` → PageController@show
|
|
85
|
-
- `/category/{slug}` → PostController@index (filtered)
|
|
86
|
-
- `/tag/{slug}` → PostController@index (filtered)
|
|
87
|
-
- `/search` → SearchController@index
|
|
88
|
-
- Custom post type archives and singles
|
|
89
|
-
|
|
90
|
-
### Blade Views
|
|
91
|
-
Everything is Blade. The directory structure should be:
|
|
92
|
-
```
|
|
93
|
-
layouts/
|
|
94
|
-
app.blade.php ← from header.php + footer.php
|
|
95
|
-
posts/
|
|
96
|
-
index.blade.php ← from archive.php or index.php
|
|
97
|
-
show.blade.php ← from single.php
|
|
98
|
-
pages/
|
|
99
|
-
show.blade.php ← from page.php
|
|
100
|
-
partials/
|
|
101
|
-
nav.blade.php ← from wp_nav_menu output
|
|
102
|
-
sidebar.blade.php ← from sidebar.php
|
|
103
|
-
post-card.blade.php ← from template-parts/content.php
|
|
104
|
-
comments.blade.php ← from comments.php
|
|
105
|
-
search-form.blade.php ← from searchform.php
|
|
106
|
-
errors/
|
|
107
|
-
404.blade.php ← from 404.php
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Migrations (Mode B only)
|
|
111
|
-
If the user chose Mode B (fresh database), plan one migration per model with fields derived from:
|
|
112
|
-
- WordPress core fields: title, slug, content, excerpt, featured_image, status, published_at
|
|
113
|
-
- Custom fields from `get_post_meta()` calls found in templates
|
|
114
|
-
- Any ACF or custom meta boxes registered in functions.php
|
|
59
|
+
Before calling any Stellify tools, plan:
|
|
115
60
|
|
|
116
|
-
|
|
61
|
+
- **Models:** One per post type (Post, Page, plus any custom types from Step 3)
|
|
62
|
+
- **Controllers:** One per model with index/show actions
|
|
63
|
+
- **Routes:** Match WordPress URL structure (archive at `/`, singles at `/{slug}`, taxonomies at `/category/{slug}`)
|
|
64
|
+
- **Views:** Mirror WordPress template hierarchy — layout from header+footer, index/show views per post type, partials from template-parts
|
|
65
|
+
- **Migrations (Mode B only):** One per model with fields from WordPress core + any custom fields found in templates
|
|
117
66
|
|
|
118
67
|
## Step 5 — Build in Stellify (Order of Operations)
|
|
119
68
|
|
|
@@ -123,6 +72,17 @@ Execute the plan using Stellify MCP tools in this order. This order matters —
|
|
|
123
72
|
|
|
124
73
|
Use the `create_resources` tool with `api: false` to generate models and controllers together. The `api: false` flag ensures controller methods return data arrays (for Blade views) rather than JSON responses.
|
|
125
74
|
|
|
75
|
+
**Route Model Binding with Slugs:** WordPress URLs use slugs (e.g., `/post/my-article-title`). After creating each model with a `slug` field, add a `getRouteKeyName()` method so Laravel's route model binding works correctly:
|
|
76
|
+
|
|
77
|
+
```php
|
|
78
|
+
public function getRouteKeyName(): string
|
|
79
|
+
{
|
|
80
|
+
return 'slug';
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Add this method to **Post**, **Category**, **Tag**, and any custom post type models that use slugs in their URLs. Without this, routes like `/post/{post}` will try to look up by ID instead of slug, causing 404 errors.
|
|
85
|
+
|
|
126
86
|
**If Mode A (connect to existing WordPress database):**
|
|
127
87
|
|
|
128
88
|
Create resources that map to the existing WordPress tables. No migrations. Set the model's table and primary key to match WordPress.
|
|
@@ -132,7 +92,13 @@ Create resources that map to the existing WordPress tables. No migrations. Set t
|
|
|
132
92
|
class Post extends Model {
|
|
133
93
|
protected $table = 'wp_posts';
|
|
134
94
|
protected $primaryKey = 'ID';
|
|
135
|
-
|
|
95
|
+
|
|
96
|
+
// Enable slug-based route model binding (WordPress uses post_name for slugs)
|
|
97
|
+
public function getRouteKeyName(): string
|
|
98
|
+
{
|
|
99
|
+
return 'post_name';
|
|
100
|
+
}
|
|
101
|
+
|
|
136
102
|
// Scope to only published posts (not revisions, drafts, etc.)
|
|
137
103
|
public function scopePublished($query) {
|
|
138
104
|
return $query->where('post_status', 'publish')
|
|
@@ -161,6 +127,15 @@ In Blade templates, use WordPress column names:
|
|
|
161
127
|
|
|
162
128
|
Create new resources with clean Laravel migrations for each model identified in Step 4. Use clean column names (`title`, `slug`, `content`, `excerpt`, `status`, `published_at`, etc.). After the import is complete, suggest a data migration SQL query or artisan command that maps data from the WordPress tables to the new schema.
|
|
163
129
|
|
|
130
|
+
For each model with a `slug` field, add `getRouteKeyName()`:
|
|
131
|
+
|
|
132
|
+
```php
|
|
133
|
+
public function getRouteKeyName(): string
|
|
134
|
+
{
|
|
135
|
+
return 'slug';
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
164
139
|
### 5b. Refine Controller Methods
|
|
165
140
|
|
|
166
141
|
`create_resources` (Step 5a) scaffolds controllers automatically. Review and refine the generated methods to ensure they query the right data. Controller methods in Stellify return data arrays — Stellify automatically merges these into the Blade view context. For example:
|
|
@@ -211,52 +186,58 @@ Translate WordPress functions to Blade:
|
|
|
211
186
|
- `language_attributes()` → `lang="{{ str_replace('_', '-', app()->getLocale()) }}"`
|
|
212
187
|
|
|
213
188
|
### 5e. Create Blade Views
|
|
214
|
-
For each WordPress template file, read the PHP and create the equivalent Blade view
|
|
189
|
+
For each WordPress template file, read the PHP and create the equivalent Blade view using `html_to_elements`. **All views use `@extends('layouts.app')` and `@section('content')`.**
|
|
190
|
+
|
|
191
|
+
**⚠️ Multiple Root Elements:** When converting HTML with multiple root-level elements (e.g., `<header>`, `<main>`, `<footer>`), only the first root element gets attached to the route. Make separate `html_to_elements` calls for each root element.
|
|
215
192
|
|
|
216
193
|
**WordPress → Blade translation guide:**
|
|
217
194
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
|
223
|
-
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
227
|
-
| `
|
|
228
|
-
| `
|
|
229
|
-
| `
|
|
230
|
-
| `get_template_part(
|
|
231
|
-
| `have_posts()
|
|
232
|
-
| `wp_link_pages()` | `{{ $posts->links() }}` | same |
|
|
233
|
-
| `get_search_form()` | `@include('partials.search-form')` | same |
|
|
234
|
-
| `wp_nav_menu()` | `@include('partials.nav')` | same |
|
|
235
|
-
| `get_header()` / `get_footer()` | handled by `@extends('layouts.app')` | same |
|
|
236
|
-
| `esc_html()` / `esc_attr()` | `{{ }}` (Blade auto-escapes) | same |
|
|
237
|
-
| `wp_kses_post()` | `{!! !!}` (for trusted HTML content) | same |
|
|
238
|
-
|
|
239
|
-
**Stellify `s-loop` elements for iteration:**
|
|
240
|
-
|
|
241
|
-
WordPress's "The Loop" (`have_posts() / the_post()`) requires two things in Stellify:
|
|
242
|
-
|
|
243
|
-
1. **Set the element type to `s-loop`** using `update_element` with `type: "s-loop"` and `variable` set to the collection name from the controller's return array. This tells Stellify to iterate over that variable and pass each item as `$item` to child elements.
|
|
244
|
-
|
|
245
|
-
2. **Write the `@foreach` in the Blade template as well.** The Blade content inside the element still needs the `@foreach` loop.
|
|
246
|
-
|
|
247
|
-
Example: If the controller returns `['posts' => Post::paginate(10)]`, the element that lists posts should be updated to:
|
|
248
|
-
```json
|
|
249
|
-
{
|
|
250
|
-
"uuid": "<element-uuid>",
|
|
251
|
-
"data": {
|
|
252
|
-
"type": "s-loop",
|
|
253
|
-
"variable": "posts"
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
And the Blade content inside uses `@foreach($posts as $post)` ... `@endforeach` as normal.
|
|
195
|
+
Mode A uses WordPress column names; Mode B uses clean Laravel names.
|
|
196
|
+
|
|
197
|
+
**IMPORTANT:** Inside `@foreach` loops, use `$item` as the loop variable. This matches the Stellify assembler's expectations for `textField`, `hrefField`, `srcField` attributes.
|
|
198
|
+
|
|
199
|
+
| WordPress | Blade (Mode A) | Blade (Mode B) | Stellify Attribute |
|
|
200
|
+
|-----------|----------------|----------------|-------------------|
|
|
201
|
+
| `the_title()` | `{{ $item->post_title }}` | `{{ $item->title }}` | `textField: "title"` |
|
|
202
|
+
| `the_content()` | `{!! $item->post_content !!}` | `{!! $item->content !!}` | statement with code |
|
|
203
|
+
| `the_excerpt()` | `{{ $item->post_excerpt }}` | `{{ $item->excerpt }}` | `textField: "excerpt"` |
|
|
204
|
+
| `the_permalink()` | `{{ route('posts.show', $item->post_name) }}` | `{{ route('posts.show', $item->slug) }}` | `hrefExpression: "..."` |
|
|
205
|
+
| `the_date()` | `{{ $item->post_date->format('d M Y') }}` | `{{ $item->published_at->format('d M Y') }}` | statement with code |
|
|
206
|
+
| `the_author()` | `{{ $item->author->display_name }}` | `{{ $item->author->name }}` | statement with code |
|
|
207
|
+
| `get_template_part()` | `@include('partials.post-card', ['post' => $item])` | same | s-directive |
|
|
208
|
+
| `have_posts()` | `@foreach($posts as $item)` | same | s-directive pair |
|
|
258
209
|
|
|
259
|
-
|
|
210
|
+
**Conditional Rendering with `s-directive`:**
|
|
211
|
+
|
|
212
|
+
WordPress blocks that render conditionally (like `<!-- wp:post-featured-image -->`) use `s-directive` elements. See the MCP tool documentation for the sibling pattern — create an opening directive, content elements, then a closing directive as siblings.
|
|
213
|
+
|
|
214
|
+
Common WordPress conditionals to convert (use `$item` inside loops):
|
|
215
|
+
- `<!-- wp:post-featured-image -->` → `@if($item->featured_image)` ... `@endif`
|
|
216
|
+
- `<!-- wp:post-excerpt -->` → `@if($item->post_excerpt)` ... `@endif`
|
|
217
|
+
- `<!-- wp:post-comments -->` → `@if($item->comments->count() > 0)` ... `@endif`
|
|
218
|
+
- `<!-- wp:query-no-results -->` → `@if($posts->isEmpty())` ... `@endif` (outside loop)
|
|
219
|
+
|
|
220
|
+
**Iteration (The Loop):**
|
|
221
|
+
|
|
222
|
+
WordPress's "The Loop" (`have_posts() / the_post()`) maps to `@foreach` directives. Use `s-directive` elements for the opening `@foreach` and closing `@endforeach`.
|
|
223
|
+
|
|
224
|
+
**IMPORTANT - Loop Variable:** The default loop variable is `$item`. When creating elements inside a loop:
|
|
225
|
+
1. Do NOT pass raw Blade syntax to `html_to_elements` — it will be stored literally
|
|
226
|
+
2. Create clean HTML first, then use `update_element` to add dynamic attributes:
|
|
227
|
+
- `textField: "title"` → outputs `{{ $item->title }}`
|
|
228
|
+
- `hrefField: "slug"` → outputs `href="{{ $item->slug }}"`
|
|
229
|
+
- `srcField: "featured_image"` → outputs `src="{{ $item->featured_image }}"`
|
|
230
|
+
3. For complex expressions (route helpers, method calls), use:
|
|
231
|
+
- `hrefExpression: "{{ route('posts.show', $item->slug) }}"`
|
|
232
|
+
- `srcExpression: "{{ $item->featured_image }}"`
|
|
233
|
+
4. For text with Blade code, create a statement with `create_statement_with_code`, then add its UUID to the element's `statements` array via `update_element`
|
|
234
|
+
|
|
235
|
+
Example loop structure using `s-directive` siblings:
|
|
236
|
+
```
|
|
237
|
+
1. s-directive with statement: "@foreach($posts as $item)"
|
|
238
|
+
2. article element (content to repeat)
|
|
239
|
+
3. s-directive with statement: "@endforeach"
|
|
240
|
+
```
|
|
260
241
|
|
|
261
242
|
### 5f. Create Partials & Components
|
|
262
243
|
Convert template-parts/ files into Blade partials using `@include`. Static partials receive data via the second argument: `@include('partials.post-card', ['post' => $post])`. If a template part relies on JavaScript for interactivity (e.g. a slider, a filterable gallery, a live search form), create a Vue component instead and ensure it is registered and mounted in `app.js`.
|
|
@@ -267,18 +248,9 @@ Do not try to port WordPress CSS. Read the visual intent from the theme's CSS/th
|
|
|
267
248
|
- WordPress navigation → `<nav class="flex items-center gap-6">`
|
|
268
249
|
- WordPress post grid → `<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">`
|
|
269
250
|
|
|
270
|
-
## Step 6 — Review
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
- Database mode chosen (A or B)
|
|
274
|
-
- Total models created (and which WordPress tables they map to, if Mode A)
|
|
275
|
-
- Total controllers and methods
|
|
276
|
-
- Total routes
|
|
277
|
-
- Total Blade views and partials
|
|
278
|
-
- Total Vue components (if any) and why each was needed
|
|
279
|
-
- Any WordPress features that could NOT be mapped (e.g. specific plugins, shortcodes with no equivalent)
|
|
280
|
-
- If Mode B: provide a SQL migration query or artisan command to copy content from the WordPress database to the new schema
|
|
281
|
-
- Suggestions for manual follow-up
|
|
251
|
+
## Step 6 — Review (Optional)
|
|
252
|
+
|
|
253
|
+
If requested, summarize what was created and note any WordPress features that couldn't be mapped.
|
|
282
254
|
|
|
283
255
|
## Important Rules
|
|
284
256
|
|