@wp-typia/project-tools 0.23.1 → 0.24.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 (152) hide show
  1. package/dist/runtime/built-in-block-non-ts-basic-artifacts.d.ts +9 -0
  2. package/dist/runtime/built-in-block-non-ts-basic-artifacts.js +84 -0
  3. package/dist/runtime/built-in-block-non-ts-compound-artifacts.d.ts +9 -0
  4. package/dist/runtime/built-in-block-non-ts-compound-artifacts.js +36 -0
  5. package/dist/runtime/built-in-block-non-ts-compound-templates.d.ts +23 -0
  6. package/dist/runtime/built-in-block-non-ts-compound-templates.js +453 -0
  7. package/dist/runtime/built-in-block-non-ts-family-artifacts.d.ts +8 -26
  8. package/dist/runtime/built-in-block-non-ts-family-artifacts.js +8 -1034
  9. package/dist/runtime/built-in-block-non-ts-interactivity-artifacts.d.ts +9 -0
  10. package/dist/runtime/built-in-block-non-ts-interactivity-artifacts.js +83 -0
  11. package/dist/runtime/built-in-block-non-ts-persistence-artifacts.d.ts +9 -0
  12. package/dist/runtime/built-in-block-non-ts-persistence-artifacts.js +33 -0
  13. package/dist/runtime/built-in-block-non-ts-persistence-templates.d.ts +23 -0
  14. package/dist/runtime/built-in-block-non-ts-persistence-templates.js +395 -0
  15. package/dist/runtime/cli-add-collision.js +8 -0
  16. package/dist/runtime/cli-add-help.js +10 -7
  17. package/dist/runtime/cli-add-kind-ids.d.ts +1 -1
  18. package/dist/runtime/cli-add-kind-ids.js +1 -0
  19. package/dist/runtime/cli-add-types.d.ts +28 -1
  20. package/dist/runtime/cli-add-types.js +2 -0
  21. package/dist/runtime/cli-add-workspace-ability-anchors.d.ts +24 -0
  22. package/dist/runtime/cli-add-workspace-ability-anchors.js +294 -0
  23. package/dist/runtime/cli-add-workspace-ability-registry.d.ts +10 -0
  24. package/dist/runtime/cli-add-workspace-ability-registry.js +51 -0
  25. package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +1 -1
  26. package/dist/runtime/cli-add-workspace-ability-scaffold.js +5 -311
  27. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +1 -1
  28. package/dist/runtime/cli-add-workspace-ai-anchors.d.ts +4 -4
  29. package/dist/runtime/cli-add-workspace-ai-anchors.js +4 -232
  30. package/dist/runtime/cli-add-workspace-ai-scaffold.js +4 -2
  31. package/dist/runtime/cli-add-workspace-ai-source-emitters.d.ts +1 -4
  32. package/dist/runtime/cli-add-workspace-ai-source-emitters.js +1 -145
  33. package/dist/runtime/cli-add-workspace-ai-sync-rest-anchors.d.ts +5 -0
  34. package/dist/runtime/cli-add-workspace-ai-sync-rest-anchors.js +236 -0
  35. package/dist/runtime/cli-add-workspace-ai-sync-script-source.d.ts +4 -0
  36. package/dist/runtime/cli-add-workspace-ai-sync-script-source.js +145 -0
  37. package/dist/runtime/cli-add-workspace-assets.d.ts +6 -63
  38. package/dist/runtime/cli-add-workspace-assets.js +6 -950
  39. package/dist/runtime/cli-add-workspace-binding-source-anchors.d.ts +23 -0
  40. package/dist/runtime/cli-add-workspace-binding-source-anchors.js +112 -0
  41. package/dist/runtime/cli-add-workspace-binding-source-source-emitters.d.ts +33 -0
  42. package/dist/runtime/cli-add-workspace-binding-source-source-emitters.js +436 -0
  43. package/dist/runtime/cli-add-workspace-binding-source-types.d.ts +20 -0
  44. package/dist/runtime/cli-add-workspace-binding-source-types.js +1 -0
  45. package/dist/runtime/cli-add-workspace-binding-source.d.ts +40 -0
  46. package/dist/runtime/cli-add-workspace-binding-source.js +275 -0
  47. package/dist/runtime/cli-add-workspace-block-style.d.ts +22 -0
  48. package/dist/runtime/cli-add-workspace-block-style.js +148 -0
  49. package/dist/runtime/cli-add-workspace-block-transform.d.ts +32 -0
  50. package/dist/runtime/cli-add-workspace-block-transform.js +197 -0
  51. package/dist/runtime/cli-add-workspace-contract.js +1 -1
  52. package/dist/runtime/cli-add-workspace-core-variation.d.ts +20 -0
  53. package/dist/runtime/cli-add-workspace-core-variation.js +322 -0
  54. package/dist/runtime/cli-add-workspace-editor-plugin-anchors.d.ts +37 -0
  55. package/dist/runtime/cli-add-workspace-editor-plugin-anchors.js +206 -0
  56. package/dist/runtime/cli-add-workspace-editor-plugin-source-emitters.d.ts +47 -0
  57. package/dist/runtime/cli-add-workspace-editor-plugin-source-emitters.js +219 -0
  58. package/dist/runtime/cli-add-workspace-editor-plugin.d.ts +22 -0
  59. package/dist/runtime/cli-add-workspace-editor-plugin.js +78 -0
  60. package/dist/runtime/cli-add-workspace-hooked-block.d.ts +23 -0
  61. package/dist/runtime/cli-add-workspace-hooked-block.js +57 -0
  62. package/dist/runtime/cli-add-workspace-integration-env-files.d.ts +33 -0
  63. package/dist/runtime/cli-add-workspace-integration-env-files.js +65 -0
  64. package/dist/runtime/cli-add-workspace-integration-env-package-json.d.ts +38 -0
  65. package/dist/runtime/cli-add-workspace-integration-env-package-json.js +122 -0
  66. package/dist/runtime/cli-add-workspace-integration-env-source-emitters.d.ts +44 -0
  67. package/dist/runtime/cli-add-workspace-integration-env-source-emitters.js +262 -0
  68. package/dist/runtime/cli-add-workspace-integration-env.js +5 -345
  69. package/dist/runtime/cli-add-workspace-pattern-anchors.d.ts +10 -0
  70. package/dist/runtime/cli-add-workspace-pattern-anchors.js +95 -0
  71. package/dist/runtime/cli-add-workspace-pattern-options.d.ts +20 -0
  72. package/dist/runtime/cli-add-workspace-pattern-options.js +113 -0
  73. package/dist/runtime/cli-add-workspace-pattern-source-emitters.d.ts +20 -0
  74. package/dist/runtime/cli-add-workspace-pattern-source-emitters.js +57 -0
  75. package/dist/runtime/cli-add-workspace-pattern.d.ts +42 -0
  76. package/dist/runtime/cli-add-workspace-pattern.js +99 -0
  77. package/dist/runtime/cli-add-workspace-post-meta.js +1 -1
  78. package/dist/runtime/cli-add-workspace-registration-hooks.d.ts +50 -0
  79. package/dist/runtime/cli-add-workspace-registration-hooks.js +162 -0
  80. package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +6 -9
  81. package/dist/runtime/cli-add-workspace-rest-anchors.js +6 -466
  82. package/dist/runtime/cli-add-workspace-rest-bootstrap-anchors.d.ts +17 -0
  83. package/dist/runtime/cli-add-workspace-rest-bootstrap-anchors.js +108 -0
  84. package/dist/runtime/cli-add-workspace-rest-contract-sync-anchors.d.ts +9 -0
  85. package/dist/runtime/cli-add-workspace-rest-contract-sync-anchors.js +142 -0
  86. package/dist/runtime/cli-add-workspace-rest-generated-source-emitters.d.ts +51 -0
  87. package/dist/runtime/cli-add-workspace-rest-generated-source-emitters.js +415 -0
  88. package/dist/runtime/cli-add-workspace-rest-generated.js +5 -3
  89. package/dist/runtime/cli-add-workspace-rest-manual-source-emitters.d.ts +80 -0
  90. package/dist/runtime/cli-add-workspace-rest-manual-source-emitters.js +238 -0
  91. package/dist/runtime/cli-add-workspace-rest-manual.js +3 -16
  92. package/dist/runtime/cli-add-workspace-rest-php-templates.d.ts +1 -7
  93. package/dist/runtime/cli-add-workspace-rest-php-templates.js +3 -322
  94. package/dist/runtime/cli-add-workspace-rest-resource-php-routing-template.d.ts +33 -0
  95. package/dist/runtime/cli-add-workspace-rest-resource-php-routing-template.js +145 -0
  96. package/dist/runtime/cli-add-workspace-rest-resource-sync-anchors.d.ts +9 -0
  97. package/dist/runtime/cli-add-workspace-rest-resource-sync-anchors.js +162 -0
  98. package/dist/runtime/cli-add-workspace-rest-schema-helper-php-template.d.ts +7 -0
  99. package/dist/runtime/cli-add-workspace-rest-schema-helper-php-template.js +193 -0
  100. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +5 -99
  101. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +5 -663
  102. package/dist/runtime/cli-add-workspace-rest-source-utils.d.ts +17 -0
  103. package/dist/runtime/cli-add-workspace-rest-source-utils.js +50 -0
  104. package/dist/runtime/cli-add-workspace-rest-sync-script-shared.d.ts +56 -0
  105. package/dist/runtime/cli-add-workspace-rest-sync-script-shared.js +122 -0
  106. package/dist/runtime/cli-add-workspace-rest-types.d.ts +3 -3
  107. package/dist/runtime/cli-add-workspace-variation.d.ts +22 -0
  108. package/dist/runtime/cli-add-workspace-variation.js +162 -0
  109. package/dist/runtime/cli-add-workspace.d.ts +42 -107
  110. package/dist/runtime/cli-add-workspace.js +42 -674
  111. package/dist/runtime/cli-add.d.ts +3 -3
  112. package/dist/runtime/cli-add.js +2 -2
  113. package/dist/runtime/cli-core.d.ts +2 -1
  114. package/dist/runtime/cli-core.js +1 -1
  115. package/dist/runtime/cli-doctor-workspace-bindings.js +59 -0
  116. package/dist/runtime/cli-doctor-workspace-block-addons.js +33 -5
  117. package/dist/runtime/cli-doctor.d.ts +2 -0
  118. package/dist/runtime/cli-doctor.js +13 -2
  119. package/dist/runtime/cli-help.js +6 -4
  120. package/dist/runtime/index.d.ts +5 -2
  121. package/dist/runtime/index.js +4 -2
  122. package/dist/runtime/local-dev-presets.js +2 -1
  123. package/dist/runtime/package-versions.d.ts +1 -0
  124. package/dist/runtime/package-versions.js +10 -2
  125. package/dist/runtime/pattern-catalog.d.ts +122 -0
  126. package/dist/runtime/pattern-catalog.js +471 -0
  127. package/dist/runtime/post-meta-binding-fields.d.ts +46 -0
  128. package/dist/runtime/post-meta-binding-fields.js +135 -0
  129. package/dist/runtime/typia-llm-json-schema.d.ts +24 -0
  130. package/dist/runtime/typia-llm-json-schema.js +33 -0
  131. package/dist/runtime/typia-llm-openapi-constraints.d.ts +20 -0
  132. package/dist/runtime/typia-llm-openapi-constraints.js +254 -0
  133. package/dist/runtime/typia-llm-projection.d.ts +25 -0
  134. package/dist/runtime/typia-llm-projection.js +58 -0
  135. package/dist/runtime/typia-llm-render.d.ts +21 -0
  136. package/dist/runtime/typia-llm-render.js +252 -0
  137. package/dist/runtime/typia-llm-sync.d.ts +10 -0
  138. package/dist/runtime/typia-llm-sync.js +63 -0
  139. package/dist/runtime/typia-llm-types.d.ts +197 -0
  140. package/dist/runtime/typia-llm-types.js +1 -0
  141. package/dist/runtime/typia-llm.d.ts +9 -255
  142. package/dist/runtime/typia-llm.js +5 -634
  143. package/dist/runtime/workspace-inventory-mutations.js +13 -0
  144. package/dist/runtime/workspace-inventory-section-descriptors.js +9 -1
  145. package/dist/runtime/workspace-inventory-templates.d.ts +2 -2
  146. package/dist/runtime/workspace-inventory-templates.js +9 -1
  147. package/dist/runtime/workspace-inventory-types.d.ts +9 -1
  148. package/package.json +8 -3
  149. package/templates/_shared/compound/core/scripts/block-config.ts.mustache +22 -0
  150. package/templates/_shared/compound/core/scripts/sync-types-to-block-json.ts.mustache +103 -2
  151. package/templates/_shared/compound/core/src/inner-blocks-templates.ts.mustache +13 -0
  152. package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +22 -1
@@ -0,0 +1,9 @@
1
+ import type { BuiltInCodeArtifact } from "./built-in-block-code-artifacts.js";
2
+ import type { ScaffoldTemplateVariables } from "./scaffold.js";
3
+ /**
4
+ * Builds the interactivity family non-TypeScript scaffold artifacts.
5
+ *
6
+ * @param variables Scaffold template variables used to render the artifact set.
7
+ * @returns The SCSS artifacts owned by the interactivity template family.
8
+ */
9
+ export declare function buildInteractivityArtifacts(variables: ScaffoldTemplateVariables): BuiltInCodeArtifact[];
@@ -0,0 +1,83 @@
1
+ import { renderArtifact } from "./built-in-block-non-ts-render-utils.js";
2
+ const INTERACTIVITY_STYLE_TEMPLATE = `.{{cssClassName}} {
3
+ position: relative;
4
+ padding: 1rem;
5
+ border: 1px solid #dcdcde;
6
+ border-radius: 0.75rem;
7
+ background: #fff;
8
+
9
+ &__content {
10
+ display: grid;
11
+ gap: 0.75rem;
12
+ }
13
+
14
+ &__counter {
15
+ display: inline-flex;
16
+ gap: 0.35rem;
17
+ align-items: center;
18
+ font-size: 0.9rem;
19
+ font-weight: 600;
20
+ }
21
+
22
+ &__progress {
23
+ width: 100%;
24
+ height: 0.5rem;
25
+ overflow: hidden;
26
+ background: #f0f0f1;
27
+ border-radius: 999px;
28
+ }
29
+
30
+ &__progress-bar {
31
+ height: 100%;
32
+ background: #3858e9;
33
+ transition: width 0.2s ease;
34
+ }
35
+
36
+ &__animation {
37
+ min-height: 1.5rem;
38
+ font-size: 0.85rem;
39
+ color: #3858e9;
40
+ opacity: 0;
41
+ transition: opacity 0.2s ease;
42
+
43
+ &.is-active {
44
+ opacity: 1;
45
+ }
46
+ }
47
+
48
+ &__completion {
49
+ font-weight: 700;
50
+ color: #06752d;
51
+ }
52
+
53
+ &__reset {
54
+ align-self: start;
55
+ padding: 0.4rem 0.7rem;
56
+ border: 1px solid #dcdcde;
57
+ border-radius: 999px;
58
+ background: transparent;
59
+ cursor: pointer;
60
+ }
61
+ }
62
+ `;
63
+ const INTERACTIVITY_EDITOR_STYLE_TEMPLATE = `/**
64
+ * {{title}} Block Editor Styles
65
+ */
66
+
67
+ .{{cssClassName}} {
68
+ outline: 1px dashed #ddd;
69
+ outline-offset: -1px;
70
+ }
71
+ `;
72
+ /**
73
+ * Builds the interactivity family non-TypeScript scaffold artifacts.
74
+ *
75
+ * @param variables Scaffold template variables used to render the artifact set.
76
+ * @returns The SCSS artifacts owned by the interactivity template family.
77
+ */
78
+ export function buildInteractivityArtifacts(variables) {
79
+ return [
80
+ renderArtifact("src/editor.scss", INTERACTIVITY_EDITOR_STYLE_TEMPLATE, variables),
81
+ renderArtifact("src/style.scss", INTERACTIVITY_STYLE_TEMPLATE, variables),
82
+ ];
83
+ }
@@ -0,0 +1,9 @@
1
+ import type { BuiltInCodeArtifact } from "./built-in-block-code-artifacts.js";
2
+ import type { ScaffoldTemplateVariables } from "./scaffold.js";
3
+ /**
4
+ * Builds the persistence family non-TypeScript scaffold artifacts.
5
+ *
6
+ * @param variables Scaffold template variables used to render the artifact set.
7
+ * @returns The persistence SCSS and PHP artifacts for the selected render targets.
8
+ */
9
+ export declare function buildPersistenceArtifacts(variables: ScaffoldTemplateVariables): BuiltInCodeArtifact[];
@@ -0,0 +1,33 @@
1
+ import { buildAlternateRenderEntryArtifact, renderArtifact, } from "./built-in-block-non-ts-render-utils.js";
2
+ import { PERSISTENCE_RENDER_TARGETS_TEMPLATE, PERSISTENCE_RENDER_TEMPLATE, PERSISTENCE_STYLE_TEMPLATE, } from "./built-in-block-non-ts-persistence-templates.js";
3
+ import { getScaffoldAlternateRenderTargets } from "./scaffold-template-variable-groups.js";
4
+ /**
5
+ * Builds the persistence family non-TypeScript scaffold artifacts.
6
+ *
7
+ * @param variables Scaffold template variables used to render the artifact set.
8
+ * @returns The persistence SCSS and PHP artifacts for the selected render targets.
9
+ */
10
+ export function buildPersistenceArtifacts(variables) {
11
+ const alternateRenderTargets = getScaffoldAlternateRenderTargets(variables);
12
+ if (!alternateRenderTargets.enabled) {
13
+ return [
14
+ renderArtifact("src/style.scss", PERSISTENCE_STYLE_TEMPLATE, variables),
15
+ renderArtifact("src/render.php", PERSISTENCE_RENDER_TEMPLATE, variables),
16
+ ];
17
+ }
18
+ const artifacts = [
19
+ renderArtifact("src/style.scss", PERSISTENCE_STYLE_TEMPLATE, variables),
20
+ renderArtifact("src/render-targets.php", PERSISTENCE_RENDER_TARGETS_TEMPLATE, variables),
21
+ buildAlternateRenderEntryArtifact("src/render.php", "web", variables),
22
+ ];
23
+ if (alternateRenderTargets.hasEmail) {
24
+ artifacts.push(buildAlternateRenderEntryArtifact("src/render-email.php", "email", variables));
25
+ }
26
+ if (alternateRenderTargets.hasMjml) {
27
+ artifacts.push(buildAlternateRenderEntryArtifact("src/render-mjml.php", "mjml", variables));
28
+ }
29
+ if (alternateRenderTargets.hasPlainText) {
30
+ artifacts.push(buildAlternateRenderEntryArtifact("src/render-text.php", "plain-text", variables));
31
+ }
32
+ return artifacts;
33
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * SCSS template for persistence block editor and frontend styles.
3
+ *
4
+ * @remarks Consumed by `buildPersistenceArtifacts()` with
5
+ * `{{cssClassName}}` and `{{frontendCssClassName}}` scaffold placeholders.
6
+ */
7
+ export declare const PERSISTENCE_STYLE_TEMPLATE = ".{{cssClassName}} {\n\tborder: 1px solid #dcdcde;\n\tborder-radius: 12px;\n\tpadding: 16px;\n\tbackground: #fff;\n\n\t&__meta {\n\t\tcolor: #50575e;\n\t\tfont-size: 13px;\n\t\tmargin: 8px 0 0;\n\t}\n}\n\n.{{frontendCssClassName}} {\n\tdisplay: grid;\n\tgap: 12px;\n\tborder: 1px solid #dcdcde;\n\tborder-radius: 12px;\n\tpadding: 16px;\n\tbackground: #f6f7f7;\n\n\t&__count {\n\t\tdisplay: inline-flex;\n\t\tmin-width: 3rem;\n\t\tjustify-content: center;\n\t\tfont-weight: 700;\n\t}\n\n\t&__notice,\n\t&__error {\n\t\tmargin: 0;\n\t\tfont-size: 13px;\n\t}\n\n\t&__notice {\n\t\tcolor: #50575e;\n\t}\n\n\t&__error {\n\t\tcolor: #b32d2e;\n\t}\n\n\tbutton {\n\t\twidth: fit-content;\n\t}\n}\n";
8
+ /**
9
+ * Main PHP `render.php` template for the persistence block.
10
+ *
11
+ * @remarks Consumed by `buildPersistenceArtifacts()` with scaffold
12
+ * placeholders for block identity, namespace, text domain, storage mode,
13
+ * persistence policy, and frontend CSS class names.
14
+ */
15
+ export declare const PERSISTENCE_RENDER_TEMPLATE = "<?php\n/**\n * Dynamic render entry for the {{title}} block.\n *\n * @package {{pascalCase}}\n */\n\nif ( ! defined( 'ABSPATH' ) ) {\n\texit;\n}\n\n$validator_path = __DIR__ . '/typia-validator.php';\nif ( ! file_exists( $validator_path ) ) {\n\treturn '';\n}\n\n$validator = require $validator_path;\nif ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {\n\treturn '';\n}\n\n$normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );\n$validation = $validator->validate( $normalized );\n$resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';\n\nif ( empty( $validation['valid'] ) || '' === $resource_key ) {\n\treturn '';\n}\n\n$alignment = isset( $normalized['alignment'] ) ? (string) $normalized['alignment'] : 'left';\n$button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';\n$content = isset( $normalized['content'] ) ? (string) $normalized['content'] : '';\n$post_id = is_object( $block ) && isset( $block->context['postId'] )\n\t? (int) $block->context['postId']\n\t: (int) get_queried_object_id();\n$storage_mode = '{{dataStorageMode}}';\n$persistence_policy = '{{persistencePolicy}}';\n\n{{phpPrefix}}_record_rendered_block_instance(\n\t(int) $post_id,\n\t'{{namespace}}/{{slugKebabCase}}',\n\t$resource_key\n);\n\n$notice_message = 'authenticated' === $persistence_policy\n\t? __( 'Sign in to persist this counter.', '{{textDomain}}' )\n\t: __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );\n$context = array(\n\t'bootstrapReady' => false,\n\t'buttonLabel' => $button_label,\n\t'canWrite' => false,\n\t'client' => array(\n\t\t'writeExpiry' => 0,\n\t\t'writeNonce' => '',\n\t\t'writeToken' => '',\n\t),\n\t'count' => 0,\n\t'error' => '',\n\t'isBootstrapping' => false,\n\t'isLoading' => false,\n\t'isSaving' => false,\n\t'isVisible' => ! empty( $normalized['isVisible'] ),\n\t'persistencePolicy' => $persistence_policy,\n\t'postId' => (int) $post_id,\n\t'resourceKey' => $resource_key,\n\t'storage' => $storage_mode,\n);\n\n$wrapper_attributes = get_block_wrapper_attributes(\n\tarray(\n\t\t'data-wp-context' => wp_json_encode( $context ),\n\t\t'data-wp-interactive' => '{{slugKebabCase}}',\n\t\t'data-wp-init' => 'callbacks.init',\n\t\t'data-wp-run--mounted' => 'callbacks.mounted',\n\t)\n);\n?>\n\n<div <?php echo $wrapper_attributes; ?>>\n\t<div class=\"{{frontendCssClassName}}\">\n\t\t<p class=\"{{frontendCssClassName}}__content\" style=\"<?php echo esc_attr( 'text-align:' . $alignment ); ?>\">\n\t\t\t<?php echo esc_html( $content ); ?>\n\t\t</p>\n\t\t<p\n\t\t\tclass=\"{{frontendCssClassName}}__notice\"\n\t\t\tdata-wp-bind--hidden=\"!context.bootstrapReady || context.canWrite\"\n\t\t\thidden\n\t\t>\n\t\t\t<?php echo esc_html( $notice_message ); ?>\n\t\t</p>\n\t\t<p\n\t\t\tclass=\"{{frontendCssClassName}}__error\"\n\t\t\trole=\"status\"\n\t\t\taria-live=\"polite\"\n\t\t\taria-atomic=\"true\"\n\t\t\tdata-wp-bind--hidden=\"!context.error\"\n\t\t\tdata-wp-text=\"context.error\"\n\t\t\thidden\n\t\t></p>\n\t\t<?php if ( ! empty( $normalized['showCount'] ) ) : ?>\n\t\t\t<span\n\t\t\t\tclass=\"{{frontendCssClassName}}__count\"\n\t\t\t\trole=\"status\"\n\t\t\t\taria-live=\"polite\"\n\t\t\t\taria-atomic=\"true\"\n\t\t\t\tdata-wp-text=\"context.count\"\n\t\t\t>\n\t\t\t\t0\n\t\t\t</span>\n\t\t<?php endif; ?>\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tdisabled\n\t\t\tdata-wp-bind--disabled=\"!context.canWrite\"\n\t\t\tdata-wp-on--click=\"actions.increment\"\n\t\t>\n\t\t\t<?php echo esc_html( $button_label ); ?>\n\t\t</button>\n\t</div>\n</div>\n";
16
+ /**
17
+ * PHP helper template for persistence alternate render targets.
18
+ *
19
+ * @remarks Consumed by `buildPersistenceArtifacts()` for email, MJML,
20
+ * plain-text, and web render entries with block identity, namespace, text
21
+ * domain, storage, policy, frontend class, and title placeholders.
22
+ */
23
+ export declare const PERSISTENCE_RENDER_TARGETS_TEMPLATE = "<?php\n/**\n * Alternate render target helpers for the {{title}} block.\n *\n * @package {{pascalCase}}\n */\n\nif ( ! defined( 'ABSPATH' ) ) {\n\texit;\n}\n\nif ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_build_render_context' ) ) {\n\tfunction {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block ) {\n\t\t$validator_path = __DIR__ . '/typia-validator.php';\n\t\tif ( ! file_exists( $validator_path ) ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$validator = require $validator_path;\n\t\tif ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );\n\t\t$validation = $validator->validate( $normalized );\n\t\t$resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';\n\n\t\tif ( empty( $validation['valid'] ) || '' === $resource_key ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$alignment = isset( $normalized['alignment'] ) ? (string) $normalized['alignment'] : 'left';\n\t\t$button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';\n\t\t$rendered_content = isset( $normalized['content'] ) ? (string) $normalized['content'] : '';\n\t\t$post_id = is_object( $block ) && isset( $block->context['postId'] )\n\t\t\t? (int) $block->context['postId']\n\t\t\t: (int) get_queried_object_id();\n\t\t$storage_mode = '{{dataStorageMode}}';\n\t\t$persistence_policy = '{{persistencePolicy}}';\n\n\t\t{{phpPrefix}}_record_rendered_block_instance(\n\t\t\t(int) $post_id,\n\t\t\t'{{namespace}}/{{slugKebabCase}}',\n\t\t\t$resource_key\n\t\t);\n\n\t\t$notice_message = 'authenticated' === $persistence_policy\n\t\t\t? __( 'Sign in to persist this counter.', '{{textDomain}}' )\n\t\t\t: __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );\n\t\t$web_context = array(\n\t\t\t'bootstrapReady' => false,\n\t\t\t'buttonLabel' => $button_label,\n\t\t\t'canWrite' => false,\n\t\t\t'client' => array(\n\t\t\t\t'writeExpiry' => 0,\n\t\t\t\t'writeNonce' => '',\n\t\t\t\t'writeToken' => '',\n\t\t\t),\n\t\t\t'count' => 0,\n\t\t\t'error' => '',\n\t\t\t'isBootstrapping' => false,\n\t\t\t'isLoading' => false,\n\t\t\t'isSaving' => false,\n\t\t\t'isVisible' => ! empty( $normalized['isVisible'] ),\n\t\t\t'persistencePolicy' => $persistence_policy,\n\t\t\t'postId' => (int) $post_id,\n\t\t\t'resourceKey' => $resource_key,\n\t\t\t'storage' => $storage_mode,\n\t\t);\n\n\t\treturn array(\n\t\t\t'alignment' => $alignment,\n\t\t\t'buttonLabel' => $button_label,\n\t\t\t'content' => $rendered_content,\n\t\t\t'frontendClassName' => '{{frontendCssClassName}}',\n\t\t\t'isVisible' => ! empty( $normalized['isVisible'] ),\n\t\t\t'normalized' => $normalized,\n\t\t\t'noticeMessage' => $notice_message,\n\t\t\t'postId' => (int) $post_id,\n\t\t\t'resourceKey' => $resource_key,\n\t\t\t'showCount' => ! empty( $normalized['showCount'] ),\n\t\t\t'title' => {{titleJson}},\n\t\t\t'webContext' => $web_context,\n\t\t);\n\t}\n}\n\nif ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {\n\tfunction {{phpPrefix}}_{{slugSnakeCase}}_render_target( string $target, $attributes, $content = '', $block = null ): string {\n\t\t$context = {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block );\n\t\tif ( null === $context || empty( $context['isVisible'] ) ) {\n\t\t\treturn '';\n\t\t}\n\n\t\t$target_context = apply_filters(\n\t\t\t'{{phpPrefix}}_{{slugSnakeCase}}_render_target_context',\n\t\t\t$context,\n\t\t\t$target,\n\t\t\t$attributes,\n\t\t\t$block\n\t\t);\n\n\t\t$markup = '';\n\t\tswitch ( $target ) {\n\t\t\tcase 'email':\n\t\t\t\t$parts = array(\n\t\t\t\t\t'<div class=\"' . esc_attr( $target_context['frontendClassName'] ) . '-email\">',\n\t\t\t\t\t'<p style=\"text-align:' . esc_attr( $target_context['alignment'] ) . ';\">' . esc_html( $target_context['content'] ) . '</p>',\n\t\t\t\t);\n\t\t\t\tif ( ! empty( $target_context['showCount'] ) ) {\n\t\t\t\t\t$parts[] = '<p><strong>' . esc_html__( 'Count', '{{textDomain}}' ) . ':</strong> ' . esc_html( (string) $target_context['webContext']['count'] ) . '</p>';\n\t\t\t\t}\n\t\t\t\t$parts[] = '<p>' . esc_html( $target_context['noticeMessage'] ) . '</p>';\n\t\t\t\t$parts[] = '</div>';\n\t\t\t\t$markup = implode( '', $parts );\n\t\t\t\tbreak;\n\t\t\tcase 'mjml':\n\t\t\t\t$markup = '<mjml><mj-body><mj-section><mj-column><mj-text align=\"' . esc_attr( $target_context['alignment'] ) . '\">' . esc_html( $target_context['content'] ) . '</mj-text>';\n\t\t\t\tif ( ! empty( $target_context['showCount'] ) ) {\n\t\t\t\t\t$markup .= '<mj-text>' . esc_html__( 'Count', '{{textDomain}}' ) . ': ' . esc_html( (string) $target_context['webContext']['count'] ) . '</mj-text>';\n\t\t\t\t}\n\t\t\t\t$markup .= '<mj-text>' . esc_html( $target_context['noticeMessage'] ) . '</mj-text></mj-column></mj-section></mj-body></mjml>';\n\t\t\t\tbreak;\n\t\t\tcase 'plain-text':\n\t\t\t\t$markup = implode(\n\t\t\t\t\t\"\\n\",\n\t\t\t\t\tarray_filter(\n\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t$target_context['title'],\n\t\t\t\t\t\t\t$target_context['content'],\n\t\t\t\t\t\t\t! empty( $target_context['showCount'] )\n\t\t\t\t\t\t\t\t? sprintf( '%s: %s', __( 'Count', '{{textDomain}}' ), (string) $target_context['webContext']['count'] )\n\t\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\t\t$target_context['noticeMessage'],\n\t\t\t\t\t\t),\n\t\t\t\t\t\tstatic fn( $value ) => is_string( $value ) && '' !== $value\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'web':\n\t\t\tdefault:\n\t\t\t\t$wrapper_attributes = get_block_wrapper_attributes(\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'data-wp-context' => wp_json_encode( $target_context['webContext'] ),\n\t\t\t\t\t\t'data-wp-interactive' => '{{slugKebabCase}}',\n\t\t\t\t\t\t'data-wp-init' => 'callbacks.init',\n\t\t\t\t\t\t'data-wp-run--mounted' => 'callbacks.mounted',\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t\tob_start();\n\t\t\t\t?>\n<div <?php echo $wrapper_attributes; ?>>\n\t<div class=\"{{frontendCssClassName}}\">\n\t\t<p class=\"{{frontendCssClassName}}__content\" style=\"<?php echo esc_attr( 'text-align:' . $target_context['alignment'] ); ?>\">\n\t\t\t<?php echo esc_html( $target_context['content'] ); ?>\n\t\t</p>\n\t\t<p\n\t\t\tclass=\"{{frontendCssClassName}}__notice\"\n\t\t\tdata-wp-bind--hidden=\"!context.bootstrapReady || context.canWrite\"\n\t\t\thidden\n\t\t>\n\t\t\t<?php echo esc_html( $target_context['noticeMessage'] ); ?>\n\t\t</p>\n\t\t<p\n\t\t\tclass=\"{{frontendCssClassName}}__error\"\n\t\t\trole=\"status\"\n\t\t\taria-live=\"polite\"\n\t\t\taria-atomic=\"true\"\n\t\t\tdata-wp-bind--hidden=\"!context.error\"\n\t\t\tdata-wp-text=\"context.error\"\n\t\t\thidden\n\t\t></p>\n\t\t<?php if ( ! empty( $target_context['showCount'] ) ) : ?>\n\t\t\t<span\n\t\t\t\tclass=\"{{frontendCssClassName}}__count\"\n\t\t\t\trole=\"status\"\n\t\t\t\taria-live=\"polite\"\n\t\t\t\taria-atomic=\"true\"\n\t\t\t\tdata-wp-text=\"context.count\"\n\t\t\t>\n\t\t\t\t0\n\t\t\t</span>\n\t\t<?php endif; ?>\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tdisabled\n\t\t\tdata-wp-bind--disabled=\"!context.canWrite\"\n\t\t\tdata-wp-on--click=\"actions.increment\"\n\t\t>\n\t\t\t<?php echo esc_html( $target_context['buttonLabel'] ); ?>\n\t\t</button>\n\t</div>\n</div>\n\t\t\t\t<?php\n\t\t\t\t$markup = (string) ob_get_clean();\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn apply_filters(\n\t\t\t'{{phpPrefix}}_{{slugSnakeCase}}_render_target_markup',\n\t\t\t$markup,\n\t\t\t$target,\n\t\t\t$target_context\n\t\t);\n\t}\n}\n";
@@ -0,0 +1,395 @@
1
+ /**
2
+ * SCSS template for persistence block editor and frontend styles.
3
+ *
4
+ * @remarks Consumed by `buildPersistenceArtifacts()` with
5
+ * `{{cssClassName}}` and `{{frontendCssClassName}}` scaffold placeholders.
6
+ */
7
+ export const PERSISTENCE_STYLE_TEMPLATE = `.{{cssClassName}} {
8
+ border: 1px solid #dcdcde;
9
+ border-radius: 12px;
10
+ padding: 16px;
11
+ background: #fff;
12
+
13
+ &__meta {
14
+ color: #50575e;
15
+ font-size: 13px;
16
+ margin: 8px 0 0;
17
+ }
18
+ }
19
+
20
+ .{{frontendCssClassName}} {
21
+ display: grid;
22
+ gap: 12px;
23
+ border: 1px solid #dcdcde;
24
+ border-radius: 12px;
25
+ padding: 16px;
26
+ background: #f6f7f7;
27
+
28
+ &__count {
29
+ display: inline-flex;
30
+ min-width: 3rem;
31
+ justify-content: center;
32
+ font-weight: 700;
33
+ }
34
+
35
+ &__notice,
36
+ &__error {
37
+ margin: 0;
38
+ font-size: 13px;
39
+ }
40
+
41
+ &__notice {
42
+ color: #50575e;
43
+ }
44
+
45
+ &__error {
46
+ color: #b32d2e;
47
+ }
48
+
49
+ button {
50
+ width: fit-content;
51
+ }
52
+ }
53
+ `;
54
+ /**
55
+ * Main PHP `render.php` template for the persistence block.
56
+ *
57
+ * @remarks Consumed by `buildPersistenceArtifacts()` with scaffold
58
+ * placeholders for block identity, namespace, text domain, storage mode,
59
+ * persistence policy, and frontend CSS class names.
60
+ */
61
+ export const PERSISTENCE_RENDER_TEMPLATE = `<?php
62
+ /**
63
+ * Dynamic render entry for the {{title}} block.
64
+ *
65
+ * @package {{pascalCase}}
66
+ */
67
+
68
+ if ( ! defined( 'ABSPATH' ) ) {
69
+ exit;
70
+ }
71
+
72
+ $validator_path = __DIR__ . '/typia-validator.php';
73
+ if ( ! file_exists( $validator_path ) ) {
74
+ return '';
75
+ }
76
+
77
+ $validator = require $validator_path;
78
+ if ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {
79
+ return '';
80
+ }
81
+
82
+ $normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
83
+ $validation = $validator->validate( $normalized );
84
+ $resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
85
+
86
+ if ( empty( $validation['valid'] ) || '' === $resource_key ) {
87
+ return '';
88
+ }
89
+
90
+ $alignment = isset( $normalized['alignment'] ) ? (string) $normalized['alignment'] : 'left';
91
+ $button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';
92
+ $content = isset( $normalized['content'] ) ? (string) $normalized['content'] : '';
93
+ $post_id = is_object( $block ) && isset( $block->context['postId'] )
94
+ ? (int) $block->context['postId']
95
+ : (int) get_queried_object_id();
96
+ $storage_mode = '{{dataStorageMode}}';
97
+ $persistence_policy = '{{persistencePolicy}}';
98
+
99
+ {{phpPrefix}}_record_rendered_block_instance(
100
+ (int) $post_id,
101
+ '{{namespace}}/{{slugKebabCase}}',
102
+ $resource_key
103
+ );
104
+
105
+ $notice_message = 'authenticated' === $persistence_policy
106
+ ? __( 'Sign in to persist this counter.', '{{textDomain}}' )
107
+ : __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
108
+ $context = array(
109
+ 'bootstrapReady' => false,
110
+ 'buttonLabel' => $button_label,
111
+ 'canWrite' => false,
112
+ 'client' => array(
113
+ 'writeExpiry' => 0,
114
+ 'writeNonce' => '',
115
+ 'writeToken' => '',
116
+ ),
117
+ 'count' => 0,
118
+ 'error' => '',
119
+ 'isBootstrapping' => false,
120
+ 'isLoading' => false,
121
+ 'isSaving' => false,
122
+ 'isVisible' => ! empty( $normalized['isVisible'] ),
123
+ 'persistencePolicy' => $persistence_policy,
124
+ 'postId' => (int) $post_id,
125
+ 'resourceKey' => $resource_key,
126
+ 'storage' => $storage_mode,
127
+ );
128
+
129
+ $wrapper_attributes = get_block_wrapper_attributes(
130
+ array(
131
+ 'data-wp-context' => wp_json_encode( $context ),
132
+ 'data-wp-interactive' => '{{slugKebabCase}}',
133
+ 'data-wp-init' => 'callbacks.init',
134
+ 'data-wp-run--mounted' => 'callbacks.mounted',
135
+ )
136
+ );
137
+ ?>
138
+
139
+ <div <?php echo $wrapper_attributes; ?>>
140
+ <div class="{{frontendCssClassName}}">
141
+ <p class="{{frontendCssClassName}}__content" style="<?php echo esc_attr( 'text-align:' . $alignment ); ?>">
142
+ <?php echo esc_html( $content ); ?>
143
+ </p>
144
+ <p
145
+ class="{{frontendCssClassName}}__notice"
146
+ data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
147
+ hidden
148
+ >
149
+ <?php echo esc_html( $notice_message ); ?>
150
+ </p>
151
+ <p
152
+ class="{{frontendCssClassName}}__error"
153
+ role="status"
154
+ aria-live="polite"
155
+ aria-atomic="true"
156
+ data-wp-bind--hidden="!context.error"
157
+ data-wp-text="context.error"
158
+ hidden
159
+ ></p>
160
+ <?php if ( ! empty( $normalized['showCount'] ) ) : ?>
161
+ <span
162
+ class="{{frontendCssClassName}}__count"
163
+ role="status"
164
+ aria-live="polite"
165
+ aria-atomic="true"
166
+ data-wp-text="context.count"
167
+ >
168
+ 0
169
+ </span>
170
+ <?php endif; ?>
171
+ <button
172
+ type="button"
173
+ disabled
174
+ data-wp-bind--disabled="!context.canWrite"
175
+ data-wp-on--click="actions.increment"
176
+ >
177
+ <?php echo esc_html( $button_label ); ?>
178
+ </button>
179
+ </div>
180
+ </div>
181
+ `;
182
+ /**
183
+ * PHP helper template for persistence alternate render targets.
184
+ *
185
+ * @remarks Consumed by `buildPersistenceArtifacts()` for email, MJML,
186
+ * plain-text, and web render entries with block identity, namespace, text
187
+ * domain, storage, policy, frontend class, and title placeholders.
188
+ */
189
+ export const PERSISTENCE_RENDER_TARGETS_TEMPLATE = `<?php
190
+ /**
191
+ * Alternate render target helpers for the {{title}} block.
192
+ *
193
+ * @package {{pascalCase}}
194
+ */
195
+
196
+ if ( ! defined( 'ABSPATH' ) ) {
197
+ exit;
198
+ }
199
+
200
+ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_build_render_context' ) ) {
201
+ function {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block ) {
202
+ $validator_path = __DIR__ . '/typia-validator.php';
203
+ if ( ! file_exists( $validator_path ) ) {
204
+ return null;
205
+ }
206
+
207
+ $validator = require $validator_path;
208
+ if ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {
209
+ return null;
210
+ }
211
+
212
+ $normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
213
+ $validation = $validator->validate( $normalized );
214
+ $resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
215
+
216
+ if ( empty( $validation['valid'] ) || '' === $resource_key ) {
217
+ return null;
218
+ }
219
+
220
+ $alignment = isset( $normalized['alignment'] ) ? (string) $normalized['alignment'] : 'left';
221
+ $button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';
222
+ $rendered_content = isset( $normalized['content'] ) ? (string) $normalized['content'] : '';
223
+ $post_id = is_object( $block ) && isset( $block->context['postId'] )
224
+ ? (int) $block->context['postId']
225
+ : (int) get_queried_object_id();
226
+ $storage_mode = '{{dataStorageMode}}';
227
+ $persistence_policy = '{{persistencePolicy}}';
228
+
229
+ {{phpPrefix}}_record_rendered_block_instance(
230
+ (int) $post_id,
231
+ '{{namespace}}/{{slugKebabCase}}',
232
+ $resource_key
233
+ );
234
+
235
+ $notice_message = 'authenticated' === $persistence_policy
236
+ ? __( 'Sign in to persist this counter.', '{{textDomain}}' )
237
+ : __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
238
+ $web_context = array(
239
+ 'bootstrapReady' => false,
240
+ 'buttonLabel' => $button_label,
241
+ 'canWrite' => false,
242
+ 'client' => array(
243
+ 'writeExpiry' => 0,
244
+ 'writeNonce' => '',
245
+ 'writeToken' => '',
246
+ ),
247
+ 'count' => 0,
248
+ 'error' => '',
249
+ 'isBootstrapping' => false,
250
+ 'isLoading' => false,
251
+ 'isSaving' => false,
252
+ 'isVisible' => ! empty( $normalized['isVisible'] ),
253
+ 'persistencePolicy' => $persistence_policy,
254
+ 'postId' => (int) $post_id,
255
+ 'resourceKey' => $resource_key,
256
+ 'storage' => $storage_mode,
257
+ );
258
+
259
+ return array(
260
+ 'alignment' => $alignment,
261
+ 'buttonLabel' => $button_label,
262
+ 'content' => $rendered_content,
263
+ 'frontendClassName' => '{{frontendCssClassName}}',
264
+ 'isVisible' => ! empty( $normalized['isVisible'] ),
265
+ 'normalized' => $normalized,
266
+ 'noticeMessage' => $notice_message,
267
+ 'postId' => (int) $post_id,
268
+ 'resourceKey' => $resource_key,
269
+ 'showCount' => ! empty( $normalized['showCount'] ),
270
+ 'title' => {{titleJson}},
271
+ 'webContext' => $web_context,
272
+ );
273
+ }
274
+ }
275
+
276
+ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {
277
+ function {{phpPrefix}}_{{slugSnakeCase}}_render_target( string $target, $attributes, $content = '', $block = null ): string {
278
+ $context = {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block );
279
+ if ( null === $context || empty( $context['isVisible'] ) ) {
280
+ return '';
281
+ }
282
+
283
+ $target_context = apply_filters(
284
+ '{{phpPrefix}}_{{slugSnakeCase}}_render_target_context',
285
+ $context,
286
+ $target,
287
+ $attributes,
288
+ $block
289
+ );
290
+
291
+ $markup = '';
292
+ switch ( $target ) {
293
+ case 'email':
294
+ $parts = array(
295
+ '<div class="' . esc_attr( $target_context['frontendClassName'] ) . '-email">',
296
+ '<p style="text-align:' . esc_attr( $target_context['alignment'] ) . ';">' . esc_html( $target_context['content'] ) . '</p>',
297
+ );
298
+ if ( ! empty( $target_context['showCount'] ) ) {
299
+ $parts[] = '<p><strong>' . esc_html__( 'Count', '{{textDomain}}' ) . ':</strong> ' . esc_html( (string) $target_context['webContext']['count'] ) . '</p>';
300
+ }
301
+ $parts[] = '<p>' . esc_html( $target_context['noticeMessage'] ) . '</p>';
302
+ $parts[] = '</div>';
303
+ $markup = implode( '', $parts );
304
+ break;
305
+ case 'mjml':
306
+ $markup = '<mjml><mj-body><mj-section><mj-column><mj-text align="' . esc_attr( $target_context['alignment'] ) . '">' . esc_html( $target_context['content'] ) . '</mj-text>';
307
+ if ( ! empty( $target_context['showCount'] ) ) {
308
+ $markup .= '<mj-text>' . esc_html__( 'Count', '{{textDomain}}' ) . ': ' . esc_html( (string) $target_context['webContext']['count'] ) . '</mj-text>';
309
+ }
310
+ $markup .= '<mj-text>' . esc_html( $target_context['noticeMessage'] ) . '</mj-text></mj-column></mj-section></mj-body></mjml>';
311
+ break;
312
+ case 'plain-text':
313
+ $markup = implode(
314
+ "\\n",
315
+ array_filter(
316
+ array(
317
+ $target_context['title'],
318
+ $target_context['content'],
319
+ ! empty( $target_context['showCount'] )
320
+ ? sprintf( '%s: %s', __( 'Count', '{{textDomain}}' ), (string) $target_context['webContext']['count'] )
321
+ : null,
322
+ $target_context['noticeMessage'],
323
+ ),
324
+ static fn( $value ) => is_string( $value ) && '' !== $value
325
+ )
326
+ );
327
+ break;
328
+ case 'web':
329
+ default:
330
+ $wrapper_attributes = get_block_wrapper_attributes(
331
+ array(
332
+ 'data-wp-context' => wp_json_encode( $target_context['webContext'] ),
333
+ 'data-wp-interactive' => '{{slugKebabCase}}',
334
+ 'data-wp-init' => 'callbacks.init',
335
+ 'data-wp-run--mounted' => 'callbacks.mounted',
336
+ )
337
+ );
338
+ ob_start();
339
+ ?>
340
+ <div <?php echo $wrapper_attributes; ?>>
341
+ <div class="{{frontendCssClassName}}">
342
+ <p class="{{frontendCssClassName}}__content" style="<?php echo esc_attr( 'text-align:' . $target_context['alignment'] ); ?>">
343
+ <?php echo esc_html( $target_context['content'] ); ?>
344
+ </p>
345
+ <p
346
+ class="{{frontendCssClassName}}__notice"
347
+ data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
348
+ hidden
349
+ >
350
+ <?php echo esc_html( $target_context['noticeMessage'] ); ?>
351
+ </p>
352
+ <p
353
+ class="{{frontendCssClassName}}__error"
354
+ role="status"
355
+ aria-live="polite"
356
+ aria-atomic="true"
357
+ data-wp-bind--hidden="!context.error"
358
+ data-wp-text="context.error"
359
+ hidden
360
+ ></p>
361
+ <?php if ( ! empty( $target_context['showCount'] ) ) : ?>
362
+ <span
363
+ class="{{frontendCssClassName}}__count"
364
+ role="status"
365
+ aria-live="polite"
366
+ aria-atomic="true"
367
+ data-wp-text="context.count"
368
+ >
369
+ 0
370
+ </span>
371
+ <?php endif; ?>
372
+ <button
373
+ type="button"
374
+ disabled
375
+ data-wp-bind--disabled="!context.canWrite"
376
+ data-wp-on--click="actions.increment"
377
+ >
378
+ <?php echo esc_html( $target_context['buttonLabel'] ); ?>
379
+ </button>
380
+ </div>
381
+ </div>
382
+ <?php
383
+ $markup = (string) ob_get_clean();
384
+ break;
385
+ }
386
+
387
+ return apply_filters(
388
+ '{{phpPrefix}}_{{slugSnakeCase}}_render_target_markup',
389
+ $markup,
390
+ $target,
391
+ $target_context
392
+ );
393
+ }
394
+ }
395
+ `;
@@ -90,6 +90,14 @@ const PATTERN_COLLISION_DESCRIPTOR = {
90
90
  label: "A pattern",
91
91
  relativePath: ({ slug }) => path.join("src", "patterns", `${slug}.php`),
92
92
  },
93
+ {
94
+ label: "A full pattern",
95
+ relativePath: ({ slug }) => path.join("src", "patterns", "full", `${slug}.php`),
96
+ },
97
+ {
98
+ label: "A section pattern",
99
+ relativePath: ({ slug }) => path.join("src", "patterns", "sections", `${slug}.php`),
100
+ },
93
101
  ],
94
102
  inventoryCollision: {
95
103
  entries: (inventory) => inventory.patterns,
@@ -9,11 +9,13 @@ export function formatAddHelpText() {
9
9
  wp-typia add admin-view <name> [--source <rest-resource:slug|core-data:kind/name>] [--dry-run]
10
10
  wp-typia add block <name> [--template <${ADD_BLOCK_TEMPLATE_IDS.join("|")}>] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--inner-blocks-preset <freeform|ordered|horizontal|locked-structure>] [--alternate-render-targets <email,mjml,plain-text>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--dry-run]
11
11
  wp-typia add integration-env <name> [--wp-env] [--release-zip] [--service <none|docker-compose>] [--dry-run]
12
- wp-typia add variation <name> --block <block-slug> [--dry-run]
13
- wp-typia add style <name> --block <block-slug> [--dry-run]
14
- wp-typia add transform <name> --from <namespace/block> --to <block-slug|namespace/block-slug> [--dry-run]
15
- wp-typia add pattern <name> [--dry-run]
16
- wp-typia add binding-source <name> [--block <block-slug|namespace/block-slug> --attribute <attribute>] [--dry-run]
12
+ wp-typia add core-variation <block-name> <name> [--dry-run]
13
+ wp-typia add core-variation <name> --block <namespace/block> [--dry-run]
14
+ wp-typia add variation <name> --block <block-slug> [--dry-run]
15
+ wp-typia add style <name> --block <block-slug> [--dry-run]
16
+ wp-typia add transform <name> --from <namespace/block> --to <block-slug|namespace/block-slug> [--dry-run]
17
+ wp-typia add pattern <name> [--scope <full|section>] [--section-role <role>] [--tags <tag,...>] [--thumbnail-url <url>] [--dry-run]
18
+ wp-typia add binding-source <name> [--block <block-slug|namespace/block-slug> --attribute <attribute>] [--from-post-meta|--post-meta <post-meta> [--meta-path <field>]] [--dry-run]
17
19
  wp-typia add contract <name> [--type <ExportedTypeName>] [--dry-run]
18
20
  wp-typia add rest-resource <name> [--namespace <vendor/v1>] [--methods <${REST_RESOURCE_METHOD_IDS.join(",")}>] [--route-pattern <route-pattern>] [--permission-callback <callback>] [--controller-class <ClassName>] [--controller-extends <BaseClass>] [--dry-run]
19
21
  wp-typia add rest-resource <name> --manual [--namespace <vendor/v1>] [--method <GET|POST|PUT|PATCH|DELETE>] [--auth <public|authenticated|public-write-protected>] [--path <route-pattern>|--route-pattern <route-pattern>] [--permission-callback <callback>] [--controller-class <ClassName>] [--controller-extends <BaseClass>] [--query-type <Type>] [--body-type <Type>] [--response-type <Type>] [--secret-field <field>] [--secret-state-field|--secret-has-value-field <field>] [--secret-preserve-on-empty <true|false>] [--dry-run]
@@ -27,6 +29,7 @@ Notes:
27
29
  \`wp-typia add\` runs only inside official ${WORKSPACE_TEMPLATE_PACKAGE} workspaces scaffolded via \`wp-typia create <project-dir> --template workspace\`.
28
30
  Pass \`--dry-run\` to preview the workspace files that would change without writing them.
29
31
  Interactive add flows let you choose a template when \`--template\` is omitted; non-interactive runs default to \`basic\`.
32
+ \`add core-variation\` registers editor-side variations for existing core or external blocks without generating block.json or Typia manifests.
30
33
  \`add admin-view\` scaffolds an opt-in DataViews-powered WordPress admin screen under \`src/admin-views/\`.
31
34
  Pass \`--source rest-resource:<slug>\` to reuse a list-capable REST resource.
32
35
  Pass \`--source core-data:postType/post\` or \`--source core-data:taxonomy/category\` to bind a WordPress-owned entity collection.
@@ -38,8 +41,8 @@ Notes:
38
41
  \`add variation\` targets an existing block slug from \`scripts/block-config.ts\`.
39
42
  \`add style\` registers a Block Styles option for an existing generated block.
40
43
  \`add transform\` adds a block-to-block transform into an existing generated block.
41
- \`add pattern\` scaffolds a namespaced PHP pattern shell under \`src/patterns/\`.
42
- \`add binding-source\` scaffolds shared PHP and editor registration under \`src/bindings/\`; pass \`--block\` and \`--attribute\` together to declare an end-to-end bindable attribute on an existing generated block.
44
+ \`add pattern\` scaffolds a namespaced PHP pattern shell under \`src/patterns/full/\` or \`src/patterns/sections/\` and records typed catalog metadata in \`PATTERNS\`.
45
+ \`add binding-source\` scaffolds shared PHP and editor registration under \`src/bindings/\`; pass \`--block\` and \`--attribute\` together to declare an end-to-end bindable attribute on an existing generated block. Pass \`--from-post-meta\` or \`--post-meta\` to generate a source backed by a typed post-meta contract; \`--meta-path\` selects one top-level field as the default binding arg.
43
46
  \`add contract\` registers a standalone TypeScript wire contract under \`src/contracts/\` and generates a stable JSON Schema artifact without creating PHP route glue.
44
47
  \`add rest-resource\` scaffolds plugin-level TypeScript REST contracts under \`src/rest/\` and PHP route glue under \`inc/rest/\`. Use \`--route-pattern\`, \`--permission-callback\`, \`--controller-class\`, and \`--controller-extends\` when an existing WordPress controller or permission model needs to own part of the generated route surface.
45
48
  Pass \`--manual\` with \`add rest-resource\` to track an external/provider REST route with typed schemas, OpenAPI, clients, and drift checks without generating PHP route/controller files. Manual routes may still declare \`--permission-callback\`, \`--controller-class\`, and \`--controller-extends\` as metadata for the route owner. Settings contracts can add \`--secret-field\` with \`--secret-preserve-on-empty\` metadata so generated schemas, clients, and admin forms treat blank secret submissions as preserve-by-default.
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Keep this order stable because it drives help output and command metadata.
6
6
  */
7
- export declare const ADD_KIND_IDS: readonly ["admin-view", "block", "integration-env", "variation", "style", "transform", "pattern", "binding-source", "contract", "rest-resource", "post-meta", "ability", "ai-feature", "hooked-block", "editor-plugin"];
7
+ export declare const ADD_KIND_IDS: readonly ["admin-view", "block", "integration-env", "core-variation", "variation", "style", "transform", "pattern", "binding-source", "contract", "rest-resource", "post-meta", "ability", "ai-feature", "hooked-block", "editor-plugin"];
8
8
  /**
9
9
  * Union of supported top-level `wp-typia add` kind ids.
10
10
  */
@@ -8,6 +8,7 @@ export const ADD_KIND_IDS = [
8
8
  "admin-view",
9
9
  "block",
10
10
  "integration-env",
11
+ "core-variation",
11
12
  "variation",
12
13
  "style",
13
14
  "transform",