@lebtiga/sonic-agent 1.0.0

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.
Files changed (62) hide show
  1. package/LICENSE.txt +223 -0
  2. package/README.md +61 -0
  3. package/bin/sonic.js +304 -0
  4. package/lib/index.js +20 -0
  5. package/lib/installer.js +156 -0
  6. package/lib/license.js +48 -0
  7. package/package.json +46 -0
  8. package/plugin/.claude-plugin/plugin.json +13 -0
  9. package/plugin/README.md +100 -0
  10. package/plugin/agents/sonic.md +80 -0
  11. package/plugin/commands/sonic-build.md +145 -0
  12. package/plugin/commands/sonic-help.md +71 -0
  13. package/plugin/skills/accessibility-qa/SKILL.md +160 -0
  14. package/plugin/skills/accessibility-qa/templates/accessibility-qa-report-template.md +123 -0
  15. package/plugin/skills/accessibility-qa/templates/wcag-compliance-statement.md +70 -0
  16. package/plugin/skills/aka-wireframe-wp/SKILL.md +149 -0
  17. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/README.md +190 -0
  18. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/footer.php +49 -0
  19. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/functions.php +395 -0
  20. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/header.php +58 -0
  21. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/index.php +39 -0
  22. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/page-answer.php +62 -0
  23. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/page-authority-hub.php +122 -0
  24. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/page-knowledge.php +58 -0
  25. package/plugin/skills/aka-wireframe-wp/assets/aka-framework-theme/style.css +633 -0
  26. package/plugin/skills/aka-wireframe-wp/references/content-generator.md +371 -0
  27. package/plugin/skills/aka-wireframe-wp/references/internal-linker.md +430 -0
  28. package/plugin/skills/aka-wireframe-wp/references/orchestrator.md +269 -0
  29. package/plugin/skills/aka-wireframe-wp/references/prompts-library.md +880 -0
  30. package/plugin/skills/aka-wireframe-wp/references/seo-optimizer.md +433 -0
  31. package/plugin/skills/aka-wireframe-wp/references/strategy-planner.md +317 -0
  32. package/plugin/skills/aka-wireframe-wp/references/wordpress-deployer.md +545 -0
  33. package/plugin/skills/authority-site-builder/SKILL.md +138 -0
  34. package/plugin/skills/brand-philosophy/SKILL.md +77 -0
  35. package/plugin/skills/freepik-spaces/SKILL.md +122 -0
  36. package/plugin/skills/freepik-spaces/docs/automation-guide.md +233 -0
  37. package/plugin/skills/freepik-spaces/docs/research-notes.md +264 -0
  38. package/plugin/skills/freepik-spaces/plans/naseberry-demo-plan.md +320 -0
  39. package/plugin/skills/freepik-spaces/templates/naseberry-demo.json +302 -0
  40. package/plugin/skills/freepik-spaces/templates/saas-demo.json +212 -0
  41. package/plugin/skills/frontend-design/LICENSE.txt +177 -0
  42. package/plugin/skills/frontend-design/SKILL.md +77 -0
  43. package/plugin/skills/programmatic-seo/SKILL.md +236 -0
  44. package/plugin/skills/programmatic-seo/references/playbooks.md +293 -0
  45. package/plugin/skills/seo-qa/SKILL.md +132 -0
  46. package/plugin/skills/seo-qa/templates/schema-localbusiness.json +49 -0
  47. package/plugin/skills/seo-qa/templates/schema-service.json +36 -0
  48. package/plugin/skills/seo-qa/templates/seo-qa-report-template.md +90 -0
  49. package/plugin/skills/visual-identity/SKILL.md +109 -0
  50. package/plugin/skills/visual-identity/templates/style-guide-template.md +108 -0
  51. package/plugin/skills/website-image-gen/SKILL.md +82 -0
  52. package/plugin/skills/website-image-gen/templates/blog-featured.md +56 -0
  53. package/plugin/skills/website-image-gen/templates/hero-service-photo.md +56 -0
  54. package/plugin/skills/wordpress-pro/SKILL.md +105 -0
  55. package/plugin/skills/wordpress-pro/references/gutenberg-blocks.md +870 -0
  56. package/plugin/skills/wordpress-pro/references/hooks-filters.md +845 -0
  57. package/plugin/skills/wordpress-pro/references/performance-security.md +1012 -0
  58. package/plugin/skills/wordpress-pro/references/plugin-architecture.md +1041 -0
  59. package/plugin/skills/wordpress-pro/references/theme-development.md +858 -0
  60. package/plugin/sops/SOP-Sonic 777/authority-site-sop.html +1100 -0
  61. package/plugin/sops/SOP-WORDPRESS-330-PAGE-SITES.md +926 -0
  62. package/scripts/postinstall.js +109 -0
@@ -0,0 +1,870 @@
1
+ # Gutenberg Blocks
2
+
3
+ ---
4
+
5
+ ## Block Development Overview
6
+
7
+ WordPress 6.4+ uses the Block Editor (Gutenberg) as the primary editing experience. Blocks are the fundamental building units.
8
+
9
+ ### Block Types
10
+
11
+ | Type | Description | Use Case |
12
+ |------|-------------|----------|
13
+ | Static | Fixed HTML output | Simple content, images |
14
+ | Dynamic | Server-rendered | Posts list, dynamic data |
15
+ | Interactive | Client-side JS | Accordions, tabs, carousels |
16
+
17
+ ---
18
+
19
+ ## Project Setup
20
+
21
+ ### Using @wordpress/create-block
22
+
23
+ ```bash
24
+ # Create a new block plugin
25
+ npx @wordpress/create-block my-block --namespace my-plugin
26
+
27
+ # Create with specific template
28
+ npx @wordpress/create-block my-block --template @wordpress/create-block-interactive-template
29
+
30
+ # Create dynamic block
31
+ npx @wordpress/create-block my-block --variant dynamic
32
+ ```
33
+
34
+ ### Generated Structure
35
+
36
+ ```
37
+ my-block/
38
+ ├── my-block.php # Plugin file
39
+ ├── package.json # NPM dependencies
40
+ ├── src/
41
+ │ ├── block.json # Block metadata
42
+ │ ├── edit.js # Editor component
43
+ │ ├── save.js # Frontend save
44
+ │ ├── index.js # Block registration
45
+ │ ├── editor.scss # Editor styles
46
+ │ └── style.scss # Frontend styles
47
+ ├── build/ # Compiled assets
48
+ └── readme.txt
49
+ ```
50
+
51
+ ### package.json Scripts
52
+
53
+ ```json
54
+ {
55
+ "name": "my-block",
56
+ "version": "1.0.0",
57
+ "scripts": {
58
+ "build": "wp-scripts build",
59
+ "start": "wp-scripts start",
60
+ "format": "wp-scripts format",
61
+ "lint:js": "wp-scripts lint-js",
62
+ "lint:css": "wp-scripts lint-style",
63
+ "packages-update": "wp-scripts packages-update"
64
+ },
65
+ "devDependencies": {
66
+ "@wordpress/scripts": "^27.0.0"
67
+ }
68
+ }
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Block Registration
74
+
75
+ ### block.json (WordPress 6.4+)
76
+
77
+ ```json
78
+ {
79
+ "$schema": "https://schemas.wp.org/trunk/block.json",
80
+ "apiVersion": 3,
81
+ "name": "my-plugin/my-block",
82
+ "version": "1.0.0",
83
+ "title": "My Block",
84
+ "category": "widgets",
85
+ "icon": "smiley",
86
+ "description": "A custom block for displaying content.",
87
+ "keywords": ["custom", "content", "block"],
88
+ "supports": {
89
+ "html": false,
90
+ "align": ["wide", "full"],
91
+ "anchor": true,
92
+ "color": {
93
+ "background": true,
94
+ "text": true,
95
+ "link": true,
96
+ "gradients": true
97
+ },
98
+ "spacing": {
99
+ "margin": true,
100
+ "padding": true,
101
+ "blockGap": true
102
+ },
103
+ "typography": {
104
+ "fontSize": true,
105
+ "lineHeight": true
106
+ },
107
+ "__experimentalBorder": {
108
+ "color": true,
109
+ "radius": true,
110
+ "style": true,
111
+ "width": true
112
+ }
113
+ },
114
+ "attributes": {
115
+ "content": {
116
+ "type": "string",
117
+ "source": "html",
118
+ "selector": "p"
119
+ },
120
+ "alignment": {
121
+ "type": "string",
122
+ "default": "left"
123
+ },
124
+ "showBorder": {
125
+ "type": "boolean",
126
+ "default": false
127
+ },
128
+ "items": {
129
+ "type": "array",
130
+ "default": []
131
+ },
132
+ "selectedPostId": {
133
+ "type": "number"
134
+ }
135
+ },
136
+ "example": {
137
+ "attributes": {
138
+ "content": "Example content for the block preview."
139
+ }
140
+ },
141
+ "textdomain": "my-plugin",
142
+ "editorScript": "file:./index.js",
143
+ "editorStyle": "file:./index.css",
144
+ "style": "file:./style-index.css",
145
+ "viewScript": "file:./view.js",
146
+ "render": "file:./render.php"
147
+ }
148
+ ```
149
+
150
+ ### PHP Registration
151
+
152
+ ```php
153
+ <?php
154
+ declare(strict_types=1);
155
+
156
+ /**
157
+ * Register all blocks
158
+ */
159
+ function my_plugin_register_blocks(): void {
160
+ // Auto-register from block.json
161
+ register_block_type(__DIR__ . '/build/my-block');
162
+
163
+ // Or with additional arguments
164
+ register_block_type(__DIR__ . '/build/another-block', [
165
+ 'render_callback' => 'my_plugin_render_another_block',
166
+ ]);
167
+ }
168
+ add_action('init', 'my_plugin_register_blocks');
169
+
170
+ /**
171
+ * Register block category
172
+ */
173
+ function my_plugin_block_categories(array $categories): array {
174
+ return array_merge(
175
+ [
176
+ [
177
+ 'slug' => 'my-plugin-blocks',
178
+ 'title' => __('My Plugin Blocks', 'my-plugin'),
179
+ 'icon' => 'wordpress',
180
+ ],
181
+ ],
182
+ $categories
183
+ );
184
+ }
185
+ add_filter('block_categories_all', 'my_plugin_block_categories');
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Static Block Development
191
+
192
+ ### index.js (Entry Point)
193
+
194
+ ```javascript
195
+ /**
196
+ * WordPress dependencies
197
+ */
198
+ import { registerBlockType } from '@wordpress/blocks';
199
+
200
+ /**
201
+ * Internal dependencies
202
+ */
203
+ import Edit from './edit';
204
+ import save from './save';
205
+ import metadata from './block.json';
206
+ import './style.scss';
207
+
208
+ /**
209
+ * Register block
210
+ */
211
+ registerBlockType(metadata.name, {
212
+ edit: Edit,
213
+ save,
214
+ });
215
+ ```
216
+
217
+ ### edit.js (Editor Component)
218
+
219
+ ```javascript
220
+ /**
221
+ * WordPress dependencies
222
+ */
223
+ import { __ } from '@wordpress/i18n';
224
+ import {
225
+ useBlockProps,
226
+ RichText,
227
+ InspectorControls,
228
+ BlockControls,
229
+ AlignmentToolbar,
230
+ MediaUpload,
231
+ MediaUploadCheck,
232
+ } from '@wordpress/block-editor';
233
+ import {
234
+ PanelBody,
235
+ ToggleControl,
236
+ TextControl,
237
+ SelectControl,
238
+ RangeControl,
239
+ Button,
240
+ } from '@wordpress/components';
241
+ import './editor.scss';
242
+
243
+ /**
244
+ * Edit component
245
+ *
246
+ * @param {Object} props Block props
247
+ * @param {Object} props.attributes Block attributes
248
+ * @param {Function} props.setAttributes Function to update attributes
249
+ * @return {JSX.Element} Block edit component
250
+ */
251
+ export default function Edit({ attributes, setAttributes }) {
252
+ const { content, alignment, showBorder, imageId, imageUrl, columns } = attributes;
253
+
254
+ const blockProps = useBlockProps({
255
+ className: `align-${alignment}${showBorder ? ' has-border' : ''}`,
256
+ });
257
+
258
+ const onSelectImage = (media) => {
259
+ setAttributes({
260
+ imageId: media.id,
261
+ imageUrl: media.url,
262
+ });
263
+ };
264
+
265
+ const onRemoveImage = () => {
266
+ setAttributes({
267
+ imageId: undefined,
268
+ imageUrl: undefined,
269
+ });
270
+ };
271
+
272
+ return (
273
+ <>
274
+ <BlockControls>
275
+ <AlignmentToolbar
276
+ value={alignment}
277
+ onChange={(newAlignment) =>
278
+ setAttributes({ alignment: newAlignment })
279
+ }
280
+ />
281
+ </BlockControls>
282
+
283
+ <InspectorControls>
284
+ <PanelBody title={__('Settings', 'my-plugin')} initialOpen={true}>
285
+ <ToggleControl
286
+ label={__('Show Border', 'my-plugin')}
287
+ checked={showBorder}
288
+ onChange={(value) => setAttributes({ showBorder: value })}
289
+ />
290
+
291
+ <RangeControl
292
+ label={__('Columns', 'my-plugin')}
293
+ value={columns}
294
+ onChange={(value) => setAttributes({ columns: value })}
295
+ min={1}
296
+ max={6}
297
+ />
298
+
299
+ <SelectControl
300
+ label={__('Layout', 'my-plugin')}
301
+ value={attributes.layout}
302
+ options={[
303
+ { label: __('Default', 'my-plugin'), value: 'default' },
304
+ { label: __('Card', 'my-plugin'), value: 'card' },
305
+ { label: __('Minimal', 'my-plugin'), value: 'minimal' },
306
+ ]}
307
+ onChange={(value) => setAttributes({ layout: value })}
308
+ />
309
+ </PanelBody>
310
+
311
+ <PanelBody title={__('Image', 'my-plugin')} initialOpen={false}>
312
+ <MediaUploadCheck>
313
+ <MediaUpload
314
+ onSelect={onSelectImage}
315
+ allowedTypes={['image']}
316
+ value={imageId}
317
+ render={({ open }) => (
318
+ <div className="editor-post-featured-image">
319
+ {imageUrl ? (
320
+ <>
321
+ <img src={imageUrl} alt="" />
322
+ <Button
323
+ onClick={onRemoveImage}
324
+ isDestructive
325
+ >
326
+ {__('Remove Image', 'my-plugin')}
327
+ </Button>
328
+ </>
329
+ ) : (
330
+ <Button onClick={open} variant="secondary">
331
+ {__('Select Image', 'my-plugin')}
332
+ </Button>
333
+ )}
334
+ </div>
335
+ )}
336
+ />
337
+ </MediaUploadCheck>
338
+ </PanelBody>
339
+ </InspectorControls>
340
+
341
+ <div {...blockProps}>
342
+ {imageUrl && (
343
+ <img src={imageUrl} alt="" className="block-image" />
344
+ )}
345
+ <RichText
346
+ tagName="p"
347
+ value={content}
348
+ onChange={(value) => setAttributes({ content: value })}
349
+ placeholder={__('Enter content...', 'my-plugin')}
350
+ allowedFormats={['core/bold', 'core/italic', 'core/link']}
351
+ />
352
+ </div>
353
+ </>
354
+ );
355
+ }
356
+ ```
357
+
358
+ ### save.js (Frontend Output)
359
+
360
+ ```javascript
361
+ /**
362
+ * WordPress dependencies
363
+ */
364
+ import { useBlockProps, RichText } from '@wordpress/block-editor';
365
+
366
+ /**
367
+ * Save component
368
+ *
369
+ * @param {Object} props Block props
370
+ * @param {Object} props.attributes Block attributes
371
+ * @return {JSX.Element} Block save component
372
+ */
373
+ export default function save({ attributes }) {
374
+ const { content, alignment, showBorder, imageUrl } = attributes;
375
+
376
+ const blockProps = useBlockProps.save({
377
+ className: `align-${alignment}${showBorder ? ' has-border' : ''}`,
378
+ });
379
+
380
+ return (
381
+ <div {...blockProps}>
382
+ {imageUrl && (
383
+ <img src={imageUrl} alt="" className="block-image" />
384
+ )}
385
+ <RichText.Content tagName="p" value={content} />
386
+ </div>
387
+ );
388
+ }
389
+ ```
390
+
391
+ ---
392
+
393
+ ## Dynamic Block Development
394
+
395
+ Dynamic blocks render on the server, useful for content that changes or requires PHP logic.
396
+
397
+ ### block.json for Dynamic Block
398
+
399
+ ```json
400
+ {
401
+ "$schema": "https://schemas.wp.org/trunk/block.json",
402
+ "apiVersion": 3,
403
+ "name": "my-plugin/recent-posts",
404
+ "title": "Recent Posts",
405
+ "category": "widgets",
406
+ "icon": "list-view",
407
+ "description": "Display recent posts with customizable options.",
408
+ "supports": {
409
+ "html": false,
410
+ "align": ["wide", "full"]
411
+ },
412
+ "attributes": {
413
+ "numberOfPosts": {
414
+ "type": "number",
415
+ "default": 5
416
+ },
417
+ "postType": {
418
+ "type": "string",
419
+ "default": "post"
420
+ },
421
+ "showExcerpt": {
422
+ "type": "boolean",
423
+ "default": true
424
+ },
425
+ "showFeaturedImage": {
426
+ "type": "boolean",
427
+ "default": true
428
+ },
429
+ "categories": {
430
+ "type": "array",
431
+ "default": []
432
+ }
433
+ },
434
+ "textdomain": "my-plugin",
435
+ "editorScript": "file:./index.js",
436
+ "style": "file:./style-index.css",
437
+ "render": "file:./render.php"
438
+ }
439
+ ```
440
+
441
+ ### edit.js for Dynamic Block
442
+
443
+ ```javascript
444
+ /**
445
+ * WordPress dependencies
446
+ */
447
+ import { __ } from '@wordpress/i18n';
448
+ import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
449
+ import {
450
+ PanelBody,
451
+ RangeControl,
452
+ ToggleControl,
453
+ SelectControl,
454
+ Spinner,
455
+ } from '@wordpress/components';
456
+ import { useSelect } from '@wordpress/data';
457
+ import { store as coreStore } from '@wordpress/core-data';
458
+ import ServerSideRender from '@wordpress/server-side-render';
459
+
460
+ /**
461
+ * Edit component for dynamic block
462
+ */
463
+ export default function Edit({ attributes, setAttributes }) {
464
+ const { numberOfPosts, postType, showExcerpt, showFeaturedImage } = attributes;
465
+
466
+ const blockProps = useBlockProps();
467
+
468
+ // Fetch post types for select
469
+ const postTypes = useSelect((select) => {
470
+ const { getPostTypes } = select(coreStore);
471
+ const types = getPostTypes({ per_page: -1 });
472
+ return types?.filter((type) => type.viewable && type.rest_base) || [];
473
+ }, []);
474
+
475
+ // Fetch categories
476
+ const categories = useSelect((select) => {
477
+ const { getEntityRecords } = select(coreStore);
478
+ return getEntityRecords('taxonomy', 'category', { per_page: -1 }) || [];
479
+ }, []);
480
+
481
+ const postTypeOptions = postTypes.map((type) => ({
482
+ label: type.labels.singular_name,
483
+ value: type.slug,
484
+ }));
485
+
486
+ return (
487
+ <>
488
+ <InspectorControls>
489
+ <PanelBody title={__('Settings', 'my-plugin')}>
490
+ <RangeControl
491
+ label={__('Number of Posts', 'my-plugin')}
492
+ value={numberOfPosts}
493
+ onChange={(value) => setAttributes({ numberOfPosts: value })}
494
+ min={1}
495
+ max={20}
496
+ />
497
+
498
+ {postTypeOptions.length > 0 && (
499
+ <SelectControl
500
+ label={__('Post Type', 'my-plugin')}
501
+ value={postType}
502
+ options={postTypeOptions}
503
+ onChange={(value) => setAttributes({ postType: value })}
504
+ />
505
+ )}
506
+
507
+ <ToggleControl
508
+ label={__('Show Excerpt', 'my-plugin')}
509
+ checked={showExcerpt}
510
+ onChange={(value) => setAttributes({ showExcerpt: value })}
511
+ />
512
+
513
+ <ToggleControl
514
+ label={__('Show Featured Image', 'my-plugin')}
515
+ checked={showFeaturedImage}
516
+ onChange={(value) => setAttributes({ showFeaturedImage: value })}
517
+ />
518
+ </PanelBody>
519
+ </InspectorControls>
520
+
521
+ <div {...blockProps}>
522
+ <ServerSideRender
523
+ block="my-plugin/recent-posts"
524
+ attributes={attributes}
525
+ LoadingResponsePlaceholder={() => (
526
+ <div className="loading-placeholder">
527
+ <Spinner />
528
+ <p>{__('Loading...', 'my-plugin')}</p>
529
+ </div>
530
+ )}
531
+ EmptyResponsePlaceholder={() => (
532
+ <p>{__('No posts found.', 'my-plugin')}</p>
533
+ )}
534
+ />
535
+ </div>
536
+ </>
537
+ );
538
+ }
539
+ ```
540
+
541
+ ### render.php (Server-Side Render)
542
+
543
+ ```php
544
+ <?php
545
+ /**
546
+ * Server-side rendering for Recent Posts block
547
+ *
548
+ * @var array $attributes Block attributes
549
+ * @var string $content Block content
550
+ * @var WP_Block $block Block instance
551
+ */
552
+
553
+ declare(strict_types=1);
554
+
555
+ defined('ABSPATH') || exit;
556
+
557
+ $number_of_posts = absint($attributes['numberOfPosts'] ?? 5);
558
+ $post_type = sanitize_key($attributes['postType'] ?? 'post');
559
+ $show_excerpt = (bool) ($attributes['showExcerpt'] ?? true);
560
+ $show_featured_image = (bool) ($attributes['showFeaturedImage'] ?? true);
561
+ $categories = array_map('absint', $attributes['categories'] ?? []);
562
+
563
+ $query_args = [
564
+ 'post_type' => $post_type,
565
+ 'posts_per_page' => $number_of_posts,
566
+ 'post_status' => 'publish',
567
+ 'orderby' => 'date',
568
+ 'order' => 'DESC',
569
+ ];
570
+
571
+ if (!empty($categories) && $post_type === 'post') {
572
+ $query_args['category__in'] = $categories;
573
+ }
574
+
575
+ $posts_query = new WP_Query($query_args);
576
+
577
+ $wrapper_attributes = get_block_wrapper_attributes([
578
+ 'class' => 'recent-posts-block',
579
+ ]);
580
+ ?>
581
+
582
+ <div <?php echo $wrapper_attributes; ?>>
583
+ <?php if ($posts_query->have_posts()) : ?>
584
+ <ul class="recent-posts-list">
585
+ <?php while ($posts_query->have_posts()) : $posts_query->the_post(); ?>
586
+ <li class="recent-posts-item">
587
+ <?php if ($show_featured_image && has_post_thumbnail()) : ?>
588
+ <div class="post-thumbnail">
589
+ <a href="<?php the_permalink(); ?>">
590
+ <?php the_post_thumbnail('thumbnail'); ?>
591
+ </a>
592
+ </div>
593
+ <?php endif; ?>
594
+
595
+ <div class="post-content">
596
+ <h3 class="post-title">
597
+ <a href="<?php the_permalink(); ?>">
598
+ <?php the_title(); ?>
599
+ </a>
600
+ </h3>
601
+
602
+ <time class="post-date" datetime="<?php echo esc_attr(get_the_date('c')); ?>">
603
+ <?php echo esc_html(get_the_date()); ?>
604
+ </time>
605
+
606
+ <?php if ($show_excerpt) : ?>
607
+ <div class="post-excerpt">
608
+ <?php the_excerpt(); ?>
609
+ </div>
610
+ <?php endif; ?>
611
+ </div>
612
+ </li>
613
+ <?php endwhile; ?>
614
+ </ul>
615
+ <?php else : ?>
616
+ <p class="no-posts"><?php esc_html_e('No posts found.', 'my-plugin'); ?></p>
617
+ <?php endif; ?>
618
+
619
+ <?php wp_reset_postdata(); ?>
620
+ </div>
621
+ ```
622
+
623
+ ---
624
+
625
+ ## Block Patterns
626
+
627
+ ### Registering Patterns in PHP
628
+
629
+ ```php
630
+ <?php
631
+ /**
632
+ * Register block patterns
633
+ */
634
+ function my_plugin_register_patterns(): void {
635
+ // Register pattern category
636
+ register_block_pattern_category('my-plugin-patterns', [
637
+ 'label' => __('My Plugin Patterns', 'my-plugin'),
638
+ ]);
639
+
640
+ // Register pattern
641
+ register_block_pattern('my-plugin/hero-section', [
642
+ 'title' => __('Hero Section', 'my-plugin'),
643
+ 'description' => __('A full-width hero section with heading and CTA.', 'my-plugin'),
644
+ 'categories' => ['my-plugin-patterns', 'featured'],
645
+ 'keywords' => ['hero', 'banner', 'cta'],
646
+ 'blockTypes' => ['core/template-part/header'],
647
+ 'content' => '<!-- wp:cover {"dimRatio":60,"overlayColor":"black","align":"full"} -->
648
+ <div class="wp-block-cover alignfull">
649
+ <span class="wp-block-cover__background has-black-background-color has-background-dim-60"></span>
650
+ <div class="wp-block-cover__inner-container">
651
+ <!-- wp:heading {"textAlign":"center","level":1} -->
652
+ <h1 class="wp-block-heading has-text-align-center">' . esc_html__('Welcome', 'my-plugin') . '</h1>
653
+ <!-- /wp:heading -->
654
+ <!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
655
+ <div class="wp-block-buttons">
656
+ <!-- wp:button -->
657
+ <div class="wp-block-button"><a class="wp-block-button__link wp-element-button">' . esc_html__('Get Started', 'my-plugin') . '</a></div>
658
+ <!-- /wp:button -->
659
+ </div>
660
+ <!-- /wp:buttons -->
661
+ </div>
662
+ </div>
663
+ <!-- /wp:cover -->',
664
+ ]);
665
+ }
666
+ add_action('init', 'my_plugin_register_patterns');
667
+ ```
668
+
669
+ ### Pattern File (patterns/feature-grid.php)
670
+
671
+ ```php
672
+ <?php
673
+ /**
674
+ * Title: Feature Grid
675
+ * Slug: my-plugin/feature-grid
676
+ * Categories: my-plugin-patterns
677
+ * Keywords: features, grid, cards
678
+ * Block Types: core/group
679
+ * Viewport Width: 1200
680
+ */
681
+
682
+ declare(strict_types=1);
683
+
684
+ defined('ABSPATH') || exit;
685
+ ?>
686
+
687
+ <!-- wp:group {"align":"wide","layout":{"type":"constrained"}} -->
688
+ <div class="wp-block-group alignwide">
689
+ <!-- wp:heading {"textAlign":"center"} -->
690
+ <h2 class="wp-block-heading has-text-align-center"><?php esc_html_e('Our Features', 'my-plugin'); ?></h2>
691
+ <!-- /wp:heading -->
692
+
693
+ <!-- wp:columns {"align":"wide"} -->
694
+ <div class="wp-block-columns alignwide">
695
+ <!-- wp:column -->
696
+ <div class="wp-block-column">
697
+ <!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|m","right":"var:preset|spacing|m","bottom":"var:preset|spacing|m","left":"var:preset|spacing|m"}},"border":{"radius":"8px"}},"backgroundColor":"base-alt"} -->
698
+ <div class="wp-block-group has-base-alt-background-color has-background">
699
+ <!-- wp:image {"width":"64px","sizeSlug":"full"} -->
700
+ <figure class="wp-block-image size-full is-resized" style="width:64px"><img src="<?php echo esc_url(get_template_directory_uri()); ?>/assets/images/icon-feature-1.svg" alt="" /></figure>
701
+ <!-- /wp:image -->
702
+ <!-- wp:heading {"level":3} -->
703
+ <h3 class="wp-block-heading"><?php esc_html_e('Feature One', 'my-plugin'); ?></h3>
704
+ <!-- /wp:heading -->
705
+ <!-- wp:paragraph -->
706
+ <p><?php esc_html_e('Description of the first feature goes here.', 'my-plugin'); ?></p>
707
+ <!-- /wp:paragraph -->
708
+ </div>
709
+ <!-- /wp:group -->
710
+ </div>
711
+ <!-- /wp:column -->
712
+
713
+ <!-- wp:column -->
714
+ <div class="wp-block-column">
715
+ <!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|m","right":"var:preset|spacing|m","bottom":"var:preset|spacing|m","left":"var:preset|spacing|m"}},"border":{"radius":"8px"}},"backgroundColor":"base-alt"} -->
716
+ <div class="wp-block-group has-base-alt-background-color has-background">
717
+ <!-- wp:image {"width":"64px","sizeSlug":"full"} -->
718
+ <figure class="wp-block-image size-full is-resized" style="width:64px"><img src="<?php echo esc_url(get_template_directory_uri()); ?>/assets/images/icon-feature-2.svg" alt="" /></figure>
719
+ <!-- /wp:image -->
720
+ <!-- wp:heading {"level":3} -->
721
+ <h3 class="wp-block-heading"><?php esc_html_e('Feature Two', 'my-plugin'); ?></h3>
722
+ <!-- /wp:heading -->
723
+ <!-- wp:paragraph -->
724
+ <p><?php esc_html_e('Description of the second feature goes here.', 'my-plugin'); ?></p>
725
+ <!-- /wp:paragraph -->
726
+ </div>
727
+ <!-- /wp:group -->
728
+ </div>
729
+ <!-- /wp:column -->
730
+
731
+ <!-- wp:column -->
732
+ <div class="wp-block-column">
733
+ <!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|m","right":"var:preset|spacing|m","bottom":"var:preset|spacing|m","left":"var:preset|spacing|m"}},"border":{"radius":"8px"}},"backgroundColor":"base-alt"} -->
734
+ <div class="wp-block-group has-base-alt-background-color has-background">
735
+ <!-- wp:image {"width":"64px","sizeSlug":"full"} -->
736
+ <figure class="wp-block-image size-full is-resized" style="width:64px"><img src="<?php echo esc_url(get_template_directory_uri()); ?>/assets/images/icon-feature-3.svg" alt="" /></figure>
737
+ <!-- /wp:image -->
738
+ <!-- wp:heading {"level":3} -->
739
+ <h3 class="wp-block-heading"><?php esc_html_e('Feature Three', 'my-plugin'); ?></h3>
740
+ <!-- /wp:heading -->
741
+ <!-- wp:paragraph -->
742
+ <p><?php esc_html_e('Description of the third feature goes here.', 'my-plugin'); ?></p>
743
+ <!-- /wp:paragraph -->
744
+ </div>
745
+ <!-- /wp:group -->
746
+ </div>
747
+ <!-- /wp:column -->
748
+ </div>
749
+ <!-- /wp:columns -->
750
+ </div>
751
+ <!-- /wp:group -->
752
+ ```
753
+
754
+ ---
755
+
756
+ ## Interactivity API (WordPress 6.5+)
757
+
758
+ For client-side interactivity without custom JavaScript build processes.
759
+
760
+ ### Interactive Block Example
761
+
762
+ ```json
763
+ {
764
+ "$schema": "https://schemas.wp.org/trunk/block.json",
765
+ "apiVersion": 3,
766
+ "name": "my-plugin/counter",
767
+ "title": "Interactive Counter",
768
+ "supports": {
769
+ "interactivity": true
770
+ },
771
+ "textdomain": "my-plugin",
772
+ "editorScript": "file:./index.js",
773
+ "viewScriptModule": "file:./view.js",
774
+ "render": "file:./render.php"
775
+ }
776
+ ```
777
+
778
+ ### render.php with Interactivity
779
+
780
+ ```php
781
+ <?php
782
+ /**
783
+ * Interactive counter block render
784
+ */
785
+
786
+ declare(strict_types=1);
787
+
788
+ $initial_count = absint($attributes['initialCount'] ?? 0);
789
+
790
+ wp_interactivity_state('my-plugin/counter', [
791
+ 'count' => $initial_count,
792
+ ]);
793
+ ?>
794
+
795
+ <div
796
+ <?php echo get_block_wrapper_attributes(); ?>
797
+ data-wp-interactive="my-plugin/counter"
798
+ >
799
+ <button
800
+ data-wp-on--click="actions.decrement"
801
+ data-wp-bind--disabled="!state.canDecrement"
802
+ >
803
+ -
804
+ </button>
805
+
806
+ <span data-wp-text="state.count"></span>
807
+
808
+ <button data-wp-on--click="actions.increment">
809
+ +
810
+ </button>
811
+ </div>
812
+ ```
813
+
814
+ ### view.js (Interactivity Store)
815
+
816
+ ```javascript
817
+ /**
818
+ * WordPress dependencies
819
+ */
820
+ import { store, getContext } from '@wordpress/interactivity';
821
+
822
+ store('my-plugin/counter', {
823
+ state: {
824
+ get canDecrement() {
825
+ const { count } = store('my-plugin/counter').state;
826
+ return count > 0;
827
+ },
828
+ },
829
+ actions: {
830
+ increment() {
831
+ const state = store('my-plugin/counter').state;
832
+ state.count++;
833
+ },
834
+ decrement() {
835
+ const state = store('my-plugin/counter').state;
836
+ if (state.count > 0) {
837
+ state.count--;
838
+ }
839
+ },
840
+ },
841
+ });
842
+ ```
843
+
844
+ ---
845
+
846
+ ## Best Practices
847
+
848
+ ### Do
849
+
850
+ - Use `block.json` for all block metadata (API version 3)
851
+ - Leverage `useBlockProps` for proper block wrapper handling
852
+ - Use `InspectorControls` for sidebar settings
853
+ - Implement `example` in block.json for previews
854
+ - Use `ServerSideRender` for dynamic block previews
855
+ - Follow WordPress Coding Standards for PHP render callbacks
856
+ - Use CSS custom properties from theme.json
857
+ - Test blocks in isolation and within posts
858
+ - Support align wide/full when appropriate
859
+ - Use the Interactivity API for simple client-side logic
860
+
861
+ ### Do Not
862
+
863
+ - Skip the `$schema` property in block.json
864
+ - Use deprecated block API versions (use apiVersion 3)
865
+ - Forget to escape output in render.php
866
+ - Hardcode styles (use theme.json presets)
867
+ - Create unnecessary server requests in edit components
868
+ - Ignore block validation warnings
869
+ - Skip internationalization for text strings
870
+ - Bundle React/WordPress packages (use externals)