@stellisoft/stellify-mcp 0.1.33 → 0.1.35

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 CHANGED
@@ -352,6 +352,20 @@ Search for routes/pages in the project by name.
352
352
 
353
353
  ---
354
354
 
355
+ ### Views & Blade Templates
356
+
357
+ Stellify stores Blade views as elements instead of files. The root element's `name` field maps to the view name:
358
+
359
+ - Element with `name="notes.index"` → `view('notes.index', $data)`
360
+ - Element with `name="layouts.app"` → `@extends('layouts.app')`
361
+ - Element with `name="components.card"` → `<x-card>`
362
+
363
+ Use `update_element` to set the `name` on a root element after creating it with `html_to_elements`.
364
+
365
+ **Convention for reusable templates:** Attach layouts, components, and partials to a template route (e.g., `/template/app-layout`, `/template/card`) to keep them organized and editable.
366
+
367
+ ---
368
+
355
369
  ### Element Tools (UI Components)
356
370
 
357
371
  #### `create_element`
@@ -441,8 +455,36 @@ Convert HTML to Stellify elements in ONE operation. This is the fastest way to b
441
455
  - `elements` (required): HTML string to convert
442
456
  - `page` (optional): Route UUID to attach elements to. Omit for Vue components.
443
457
  - `selection` (optional): Parent element UUID to attach to (alternative to page)
458
+ - `file` (optional): Vue component file UUID. Pass this to auto-wire @click handlers to method UUIDs.
444
459
  - `test` (optional): If true, returns structure without creating elements
445
460
 
461
+ **⚠️ CRITICAL: Multiple Root Elements**
462
+
463
+ When passing HTML with **multiple root-level elements** (e.g., `<header>`, `<main>`, `<footer>`), only the **FIRST root element** gets attached to the route via `routeParent`. Other elements are created but become **orphaned** (not attached to the route).
464
+
465
+ **Wrong approach (causes orphaned elements):**
466
+ ```
467
+ html_to_elements(page: routeUUID, elements: "<header>...</header><main>...</main><footer>...</footer>")
468
+ // Result: Only <header> is attached to the route. <main> and <footer> are orphaned!
469
+ ```
470
+
471
+ **Correct approach (make separate calls for each root element):**
472
+ ```
473
+ // Call 1: Header
474
+ html_to_elements(page: routeUUID, elements: "<header>...</header>")
475
+
476
+ // Call 2: Main content
477
+ html_to_elements(page: routeUUID, elements: "<main>...</main>")
478
+
479
+ // Call 3: Footer
480
+ html_to_elements(page: routeUUID, elements: "<footer>...</footer>")
481
+ ```
482
+
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
+
446
488
  **Features:**
447
489
  - Parses HTML structure
448
490
  - Creates all elements with proper nesting
package/dist/index.js CHANGED
@@ -596,7 +596,7 @@ Use the returned UUID with html_to_elements (page parameter) or get_route for fu
596
596
  },
597
597
  {
598
598
  name: 'update_element',
599
- description: `Update a UI element. Data object: tag, classes, text, event handlers (method UUIDs), classBindings. Include context for significant UI components.`,
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')).`,
600
600
  inputSchema: {
601
601
  type: 'object',
602
602
  properties: {
@@ -685,12 +685,46 @@ Note: To reorder elements, use update_element to modify the parent element's 'da
685
685
  },
686
686
  {
687
687
  name: 'html_to_elements',
688
- description: `Convert HTML to Stellify elements. Returns element UUIDs to use in save_file template array.
688
+ description: `Convert HTML to Stellify elements.
689
689
 
690
- Auto-detects Vue bindings ({{ var }}). For Vue components, omit 'page'. For pages, provide route UUID.
690
+ **IMPORTANT - Choose the right approach:**
691
+
692
+ **For SSR/Blade Pages (WordPress imports, static content, layouts):**
693
+ - MUST pass 'page' (route UUID) - elements attach to the route for server-side rendering
694
+ - This is the most common use case
695
+
696
+ **For Vue Components (client-side interactivity):**
697
+ - Omit 'page' - elements are standalone, referenced by file's template array
698
+ - Returns UUIDs to use in save_file's template array
699
+
700
+ **Where elements go:**
701
+ - Pass 'page' (route UUID): Elements attached to the route for SSR rendering
702
+ - Pass 'selection' (element UUID): Elements attached as children of existing element
703
+ - Omit both: Elements are standalone (Vue components only) - use returned UUIDs in save_file's template array
704
+
705
+ **⚠️ CRITICAL: Multiple Root Elements Limitation**
706
+ When HTML contains multiple root-level elements (e.g., <header>, <main>, <footer>), only the FIRST root element gets attached to the route. Other elements become orphaned!
707
+
708
+ **WRONG:** \`html_to_elements(page: routeUUID, elements: "<header>...</header><main>...</main><footer>...</footer>")\`
709
+ → Only <header> attaches to route. <main> and <footer> are orphaned!
710
+
711
+ **CORRECT:** Make separate calls for each root element:
712
+ 1. \`html_to_elements(page: routeUUID, elements: "<header>...</header>")\`
713
+ 2. \`html_to_elements(page: routeUUID, elements: "<main>...</main>")\`
714
+ 3. \`html_to_elements(page: routeUUID, elements: "<footer>...</footer>")\`
715
+
716
+ **OR** wrap all elements in a single container div.
691
717
 
692
718
  **@click auto-wiring:** Pass 'file' UUID to auto-resolve @click="methodName" handlers. Methods must exist in the file first.
693
719
 
720
+ **Blade Syntax Handling:**
721
+ When element text or attributes contain Blade syntax like \`{{ $post->title }}\` or \`{!! $post->content !!}\`, do NOT store them as literal text. Instead:
722
+ - Parse the Blade expression and create a statement/clause for it
723
+ - Store the statement UUID in the element's \`statements\` array
724
+ - The SSR assembler will evaluate these at render time
725
+
726
+ For example, \`{{ $post->title }}\` should become a statement that references the \`$post\` variable and accesses its \`title\` property.
727
+
694
728
  Prefer SVG icons over emoji (encoding issues).`,
695
729
  inputSchema: {
696
730
  type: 'object',
@@ -701,11 +735,11 @@ Prefer SVG icons over emoji (encoding issues).`,
701
735
  },
702
736
  page: {
703
737
  type: 'string',
704
- description: 'Route UUID to attach elements to. Optional for Vue components.',
738
+ description: 'Route UUID to attach elements to. REQUIRED for SSR/Blade pages (WordPress imports, static content, layouts). Only omit for Vue component templates.',
705
739
  },
706
740
  selection: {
707
741
  type: 'string',
708
- description: 'Parent element UUID to attach to (alternative to page)',
742
+ description: 'Parent element UUID to attach to (alternative to page). Use when adding children to existing elements.',
709
743
  },
710
744
  file: {
711
745
  type: 'string',
@@ -1154,7 +1188,7 @@ Changes are EPHEMERAL (not saved). For persistent changes, use update_element or
1154
1188
  },
1155
1189
  api: {
1156
1190
  type: 'boolean',
1157
- description: 'Generate API-style responses (default: true)',
1191
+ description: 'Generate API-style responses (default: true). Set to FALSE for SSR/Blade pages (controllers return views). Set to TRUE for API endpoints (controllers return JSON).',
1158
1192
  },
1159
1193
  },
1160
1194
  required: ['name'],
@@ -1405,31 +1439,45 @@ const SERVER_INSTRUCTIONS = `Stellify is a coding platform where code is stored
1405
1439
 
1406
1440
  - Files are stored as json. The json has a data key that references its methods (using uuids), and each method has a data key that references statements, and each statement has a data key that references clauses.
1407
1441
 
1408
- ## Example Workflow
1409
- 1. Research: Call the get_project tool to understand the current project structure, existing files, and directories. This helps avoid duplicates and informs where to create new files.
1410
- 2. Plan: If the user is in plan mode, create a plan and prompt the user to accept before starting.
1411
- 3. Execute: Map out the solution in full before calling any tools. Use the tools to verify assumptions and gather information about the project as needed.
1412
- 4. Create: create_file, create_method (with body), create_statement_with_code
1413
- 5. Wire: html_to_elements (pass file UUID to auto-wire @click handlers)
1414
- 6. Finalize: save_file with template/data/statements arrays
1415
- 7. Verify: Call \`get_assembled_code\` to see the actual rendered output and fix any issues
1416
- 8. Test: Use run_code to execute methods and verify behavior. For UI components, use broadcast_element_command to demonstrate functionality in real-time.
1442
+ ## Choosing Between SSR Pages and Vue Components
1417
1443
 
1418
- ## Component Response Handling (IMPORTANT)
1444
+ **Use SSR/Blade Pages (routes + elements) when:**
1445
+ - Building static pages, layouts, or content pages
1446
+ - Importing from WordPress, HTML templates, or static sites
1447
+ - No client-side interactivity needed beyond links/forms
1448
+ - SEO is important
1419
1449
 
1420
- When creating Vue/ React etc. components, ALWAYS check the \`appJs\` field in the response:
1450
+ **Use Vue Components (files with type='js', extension='vue') when:**
1451
+ - Client-side interactivity is required (counters, toggles, dynamic forms)
1452
+ - Real-time updates needed
1453
+ - Complex state management
1421
1454
 
1422
- 1. **appJs.action_required === "create_or_select_mount_file"**: No mount file exists. You MUST immediately ask the user: "Would you like me to create an app.js mount file for this component?" Do NOT proceed without user confirmation.
1455
+ **CRITICAL DIFFERENCE:**
1456
+ - SSR Pages: Elements attach directly to routes. Use \`html_to_elements\` with \`page\` parameter (route UUID). Elements render via Blade.
1457
+ - Vue Components: Elements are standalone, referenced by file's \`template\` array. Use \`html_to_elements\` WITHOUT \`page\` parameter.
1423
1458
 
1424
- 2. **appJs.action_required === "register_component"**: Mount file exists but component isn't registered. Call save_file on the mount file (appJs.uuid) to add the component UUID to its includes array.
1459
+ ## Workflow for SSR Pages (most common)
1460
+ 1. Research: Call get_project to understand current structure
1461
+ 2. Create route: \`create_route\` for each page
1462
+ 3. Add elements: \`html_to_elements\` with \`page\` parameter set to route UUID
1463
+ 4. Wire controller (if needed): \`save_route\` with controller and controller_method UUIDs
1425
1464
 
1426
- 3. **No action_required**: Component is already registered in a mount file. Proceed normally.
1465
+ ## Workflow for Vue Components (client-side interactivity)
1466
+ 1. Research: Call get_project to understand current structure
1467
+ 2. **Call \`get_stellify_framework_api\`** to check for composables before writing custom code
1468
+ 3. Create file: \`create_file\` with type='js', extension='vue'
1469
+ 4. Create methods: \`create_method\` (with body parameter)
1470
+ 5. Create state: \`create_statement_with_code\` for refs/reactive
1471
+ 6. Create template: \`html_to_elements\` WITHOUT \`page\` parameter (returns UUIDs)
1472
+ 7. **Handle mount file**: Check \`appJs\` in create_file response. If \`action_required\` exists, ask user about mount file
1473
+ 8. Finalize: \`save_file\` with template/data/statements arrays (include frameworkImports for composables)
1474
+ 9. Verify: Call \`get_assembled_code\` to check the output
1427
1475
  `;
1428
1476
  // Legacy detailed instructions preserved as comments for reference if needed
1429
1477
  // Create MCP server
1430
1478
  const server = new Server({
1431
1479
  name: 'stellify-mcp',
1432
- version: '0.1.33',
1480
+ version: '0.1.35',
1433
1481
  }, {
1434
1482
  capabilities: {
1435
1483
  tools: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stellisoft/stellify-mcp",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "mcpName": "io.github.MattStellisoft/stellify-mcp",
5
5
  "description": "MCP server for Stellify - AI-native code generation platform",
6
6
  "main": "dist/index.js",
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.33",
9
+ "version": "0.1.35",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@stellisoft/stellify-mcp",
14
- "version": "0.1.33",
14
+ "version": "0.1.35",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -0,0 +1,292 @@
1
+ ---
2
+ name: wordpress-import
3
+ description: "Use this skill when importing WordPress sites into Stellify. Invoke with /wordpress-import to start the guided workflow. Covers: analyzing WordPress exports, converting post types to Eloquent models, transforming PHP templates to Vue components, migrating forms and plugins, and creating routes and API endpoints."
4
+ license: MIT
5
+ metadata:
6
+ author: stellify
7
+ ---
8
+
9
+ # WordPress Import Skill
10
+
11
+ You are importing a WordPress site into Stellify by reading its PHP theme files and recreating the site as a structured Laravel application using the Stellify MCP tools.
12
+
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
+
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
+ ## Step 1 — Identify the Active Theme
20
+
21
+ 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.
22
+
23
+ Before proceeding, ask the user:
24
+
25
+ > **How do you want to handle the database?**
26
+ >
27
+ > **A) Connect to existing WordPress database** — Models map directly to the WordPress tables (`wp_posts`, `wp_users`, etc.) using the existing column names. No migrations, no data copying. Your Laravel app reads and writes to the same database WordPress uses. This is the fastest path.
28
+ >
29
+ > **B) Fresh database** — Create new Laravel migrations with clean column names (`title` instead of `post_title`, etc.). You start with an empty database and can optionally migrate content from WordPress later.
30
+
31
+ These are the only questions you may ask. Do not ask anything else.
32
+
33
+ ## Step 2 — Inventory the Theme
34
+
35
+ Read the full theme directory. Categorise every file:
36
+
37
+ - **Core templates**: index.php, single.php, page.php, archive.php, search.php, 404.php, front-page.php, home.php
38
+ - **Structural partials**: header.php, footer.php, sidebar.php
39
+ - **Template parts**: anything in template-parts/ or patterns/
40
+ - **Functions**: functions.php (and any files it includes/requires)
41
+ - **Styles**: style.css, any CSS/SCSS files
42
+ - **Config**: theme.json if present (block theme configuration)
43
+
44
+ Print the inventory before proceeding.
45
+
46
+ ## Step 3 — Analyse functions.php
47
+
48
+ Read functions.php thoroughly. Extract:
49
+
50
+ - **Custom post types** registered via `register_post_type()` → these become Laravel models
51
+ - **Taxonomies** registered via `register_taxonomy()` → these become models or enums
52
+ - **Navigation menus** registered via `register_nav_menus()` → these define nav structure
53
+ - **Widget areas** registered via `register_sidebar()` → note for layout
54
+ - **Enqueued scripts/styles** via `wp_enqueue_script/style()` → note any JS dependencies
55
+ - **Custom image sizes** via `add_image_size()` → note for media handling
56
+ - **Theme supports** via `add_theme_support()` → note post formats, thumbnails, etc.
57
+ - **Shortcodes** via `add_shortcode()` → these become Blade includes or components
58
+ - **AJAX handlers** via `wp_ajax_*` → these become API routes
59
+ - **Any included files** → read those too
60
+
61
+ ## Step 4 — Plan the Laravel Structure
62
+
63
+ Before calling any Stellify tools, write out the full plan:
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
115
+
116
+ If the user chose Mode A (existing database), skip migrations entirely — the tables already exist.
117
+
118
+ ## Step 5 — Build in Stellify (Order of Operations)
119
+
120
+ Execute the plan using Stellify MCP tools in this order. This order matters — parent records must exist before children.
121
+
122
+ ### 5a. Create Models, Migrations & Controllers
123
+
124
+ 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
+
126
+ **If Mode A (connect to existing WordPress database):**
127
+
128
+ Create resources that map to the existing WordPress tables. No migrations. Set the model's table and primary key to match WordPress.
129
+
130
+ ```php
131
+ // Example Post model
132
+ class Post extends Model {
133
+ protected $table = 'wp_posts';
134
+ protected $primaryKey = 'ID';
135
+
136
+ // Scope to only published posts (not revisions, drafts, etc.)
137
+ public function scopePublished($query) {
138
+ return $query->where('post_status', 'publish')
139
+ ->where('post_type', 'post');
140
+ }
141
+ }
142
+ ```
143
+
144
+ Key WordPress table mappings:
145
+ - Posts & Pages → `wp_posts` (distinguished by `post_type` column: 'post', 'page', or custom types)
146
+ - Categories & Tags → `wp_terms` + `wp_term_taxonomy` + `wp_term_relationships`
147
+ - Users → `wp_users`
148
+ - Post meta / custom fields → `wp_postmeta`
149
+ - Comments → `wp_comments`
150
+ - Navigation menus → `wp_posts` with `post_type = 'nav_menu_item'` + `wp_terms` with taxonomy `nav_menu`
151
+
152
+ In Blade templates, use WordPress column names:
153
+ - `$post->post_title` (not `$post->title`)
154
+ - `$post->post_content` (not `$post->content`)
155
+ - `$post->post_excerpt` (not `$post->excerpt`)
156
+ - `$post->post_name` (this is the slug)
157
+ - `$post->post_date` (not `$post->published_at`)
158
+ - `$post->post_status` (not `$post->status`)
159
+
160
+ **If Mode B (fresh database):**
161
+
162
+ 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
+
164
+ ### 5b. Refine Controller Methods
165
+
166
+ `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:
167
+
168
+ ```php
169
+ // CORRECT for Stellify — returns a data array, Stellify handles the view binding
170
+ public function index(): array
171
+ {
172
+ return ['posts' => Post::where('status', 'published')->latest('published_at')->paginate(10)];
173
+ }
174
+
175
+ public function show(Post $post): array
176
+ {
177
+ return ['post' => $post];
178
+ }
179
+
180
+ // WRONG — do not return JSON
181
+ public function index()
182
+ {
183
+ return Post::where('status', 'published')->get();
184
+ }
185
+
186
+ // WRONG — do not call view() directly, Stellify handles this
187
+ public function index()
188
+ {
189
+ return view('posts.index', compact('posts'));
190
+ }
191
+ ```
192
+
193
+ **Mode A note:** WordPress stores posts, pages, and custom post types all in `wp_posts`, distinguished by the `post_type` column. Controllers must scope queries accordingly — e.g. `Post::where('post_type', 'post')->where('post_status', 'publish')`. It also stores revisions and auto-drafts in the same table, so always filter by `post_status`.
194
+
195
+ ### 5c. Create Routes
196
+ Create route entries mapping URLs to controller methods.
197
+
198
+ ### 5d. Create the Layout (layouts/app.blade.php)
199
+ Read header.php and footer.php. Create a single Blade layout that combines:
200
+ - The HTML structure from header.php (doctype, head, opening body, nav)
201
+ - `@yield('content')` for page content
202
+ - The structure from footer.php (closing elements, footer content)
203
+
204
+ Translate WordPress functions to Blade:
205
+ - `wp_head()` → `@vite(['resources/css/app.css', 'resources/js/app.js'])` and `<meta>` tags
206
+ - `wp_footer()` → nothing needed (Vite handles it)
207
+ - `wp_nav_menu()` → `@include('partials.nav')`
208
+ - `bloginfo('name')` → `{{ config('app.name') }}`
209
+ - `bloginfo('description')` → `{{ config('app.description', '') }}`
210
+ - `body_class()` → appropriate Tailwind classes
211
+ - `language_attributes()` → `lang="{{ str_replace('_', '-', app()->getLocale()) }}"`
212
+
213
+ ### 5e. Create Blade Views
214
+ For each WordPress template file, read the PHP and create the equivalent Blade view. **All views use `@extends('layouts.app')` and `@section('content')`.**
215
+
216
+ **WordPress → Blade translation guide:**
217
+
218
+ The Blade column depends on which database mode was chosen. Mode A uses WordPress column names directly; Mode B uses clean Laravel names.
219
+
220
+ | WordPress | Blade (Mode A — existing DB) | Blade (Mode B — fresh DB) |
221
+ |-----------|------------------------------|---------------------------|
222
+ | `the_title()` | `{{ $post->post_title }}` | `{{ $post->title }}` |
223
+ | `the_content()` | `{!! $post->post_content !!}` | `{!! $post->content !!}` |
224
+ | `the_excerpt()` | `{{ $post->post_excerpt }}` | `{{ $post->excerpt }}` |
225
+ | `the_permalink()` | `{{ route('posts.show', $post->post_name) }}` | `{{ route('posts.show', $post->slug) }}` |
226
+ | `the_post_thumbnail()` | via `wp_postmeta` lookup | `<img src="{{ asset('storage/' . $post->featured_image) }}" />` |
227
+ | `the_date()` / `the_time()` | `{{ \Carbon\Carbon::parse($post->post_date)->format('d M Y') }}` | `{{ $post->published_at->format('d M Y') }}` |
228
+ | `the_author()` | `{{ $post->author->display_name }}` | `{{ $post->author->name }}` |
229
+ | `comments_template()` | `@include('partials.comments', ['post' => $post])` | same |
230
+ | `get_template_part('content')` | `@include('partials.post-card', ['post' => $post])` | same |
231
+ | `have_posts() / the_post()` | Use an `s-loop` element (see below) | same |
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.
258
+
259
+ This applies to any listing — post archives, category pages, search results, related posts, comment lists, navigation menu items, etc.
260
+
261
+ ### 5f. Create Partials & Components
262
+ 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`.
263
+
264
+ ### 5g. Style with Tailwind
265
+ Do not try to port WordPress CSS. Read the visual intent from the theme's CSS/theme.json and apply Tailwind utility classes directly in the Blade templates. For example:
266
+ - WordPress container → `<div class="max-w-4xl mx-auto px-4">`
267
+ - WordPress navigation → `<nav class="flex items-center gap-6">`
268
+ - WordPress post grid → `<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">`
269
+
270
+ ## Step 6 — Review
271
+
272
+ After building everything, print a summary:
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
282
+
283
+ ## Important Rules
284
+
285
+ - **Blade for SSR, Vue for interactivity.** If the WordPress source is server-rendered PHP (posts, pages, archives, menus, layouts), create Blade templates using `@extends`, `@section`, `@include`, `@foreach`, `{{ }}` and `{!! !!}`. If the WordPress source uses JavaScript for interactivity (AJAX, dynamic filtering, sliders, modals, live search, infinite scroll), create a Vue component. When creating Vue components, ensure they are registered and mounted in `app.js` via `createApp` — do not just import them.
286
+ - **Do not ask questions** except the two permitted in Step 1 (which theme, and which database mode). Make reasonable decisions and document them.
287
+ - **Work file by file** — read a WordPress file, create the Stellify equivalent, move to the next.
288
+ - **Show progress** — print what you're reading and what you're creating as you go.
289
+ - **Handle block themes** — if theme.json exists and templates are in HTML with block markup (`<!-- wp:xxx -->`), parse the block structure rather than traditional PHP templates. The output is still Blade (or Vue where the block is interactive).
290
+ - **Style with Tailwind** — do not port WordPress CSS. Interpret the visual intent and use Tailwind utilities.
291
+ - **Be pragmatic** — focus on the core templates that define the site's main pages. Skip hyper-specific template variations unless they're clearly important.
292
+ - **Content handling depends on database mode** — In Mode A (existing DB), the content is already there via the WordPress tables. In Mode B (fresh DB), content is separate and can be migrated later.