diff --git a/php-transformer/src/HtmlToBlocks/Diagnostics/DiagnosticsCollector.php b/php-transformer/src/HtmlToBlocks/Diagnostics/DiagnosticsCollector.php
new file mode 100644
index 00000000..3664d4ec
--- /dev/null
+++ b/php-transformer/src/HtmlToBlocks/Diagnostics/DiagnosticsCollector.php
@@ -0,0 +1,148 @@
+> $scriptMetadata Static script metadata preserved as bounded data.
+ * @param array> $fallbacks Fallback diagnostics emitted during conversion.
+ * @param array> $runtimeIslands Preserved runtime islands.
+ * @param array $blockValidityReport Block serialization validity report.
+ * @param array $semanticParityReport Semantic parity report.
+ * @return array>
+ */
+ public function collect(
+ string $transformerSource,
+ array $scriptMetadata,
+ array $fallbacks,
+ array $runtimeIslands,
+ array $blockValidityReport,
+ array $semanticParityReport
+ ): array {
+ $diagnostics = array(
+ array(
+ 'code' => 'html_to_blocks_core_slice',
+ 'message' => 'Converted supported core text, layout, media, gallery, embed, file, table, button, shortcode, spacer, definition-list, details, navigation, safe inline SVG images, and wrapper elements; unsupported elements are reported as fallbacks.',
+ 'source' => $transformerSource,
+ ),
+ );
+
+ foreach ( $scriptMetadata as $metadata ) {
+ $diagnostics[] = array(
+ 'code' => 'html_static_script_metadata',
+ 'message' => 'Static script data was preserved as bounded metadata and does not require client script execution.',
+ 'source' => $transformerSource,
+ 'reason' => 'script_static_metadata',
+ 'tag' => 'script',
+ 'selector' => $metadata['selector'] ?? null,
+ 'script_role' => $metadata['script_role'] ?? null,
+ );
+ }
+
+ foreach ( $fallbacks as $fallback ) {
+ if ( ! empty($fallback['diagnostic_code']) ) {
+ $diagnostics[] = array(
+ 'code' => $fallback['diagnostic_code'],
+ 'message' => $fallback['message'] ?? 'HTML element preserved as fallback metadata.',
+ 'source' => $transformerSource,
+ 'reason' => $fallback['reason'] ?? null,
+ 'severity' => $fallback['severity'] ?? null,
+ 'conversion_classification' => $fallback['conversion_classification'] ?? null,
+ 'loss_class' => $fallback['loss_class'] ?? null,
+ 'diagnostic_class' => $fallback['diagnostic_class'] ?? null,
+ 'preservation_strategy' => $fallback['preservation_strategy'] ?? null,
+ 'runtime_requirement' => $fallback['runtime_requirement'] ?? null,
+ 'recoverability' => $fallback['recoverability'] ?? null,
+ 'actionability' => $fallback['actionability'] ?? null,
+ 'suggested_repair_class' => $fallback['suggested_repair_class'] ?? null,
+ 'suggested_primitive' => $fallback['suggested_primitive'] ?? null,
+ 'materialization_hint' => $fallback['materialization_hint'] ?? null,
+ 'tag' => $fallback['tag'] ?? null,
+ 'selector' => $fallback['selector'] ?? null,
+ 'pattern_family' => $fallback['pattern_family'] ?? null,
+ 'pattern_family_detail' => $fallback['pattern_family_detail'] ?? null,
+ 'source_selector' => $fallback['source_selector'] ?? null,
+ 'source_selector_specificity' => $fallback['source_selector_specificity'] ?? null,
+ 'parent_reason' => $fallback['parent_reason'] ?? null,
+ 'ancestor_reason' => $fallback['ancestor_reason'] ?? null,
+ 'suggested_generic_repair_class' => $fallback['suggested_generic_repair_class'] ?? null,
+ );
+ }
+ }
+
+ foreach ( $runtimeIslands as $island ) {
+ $diagnostics[] = array_filter(array(
+ 'code' => 'preserved_runtime_island',
+ 'message' => 'Runtime-dependent source markup was preserved as a bounded runtime island.',
+ 'source' => $transformerSource,
+ 'severity' => 'info',
+ 'conversion_classification' => 'runtime_island_preserved',
+ 'loss_class' => 'runtime_island_preserved',
+ 'diagnostic_class' => 'runtime_island_preserved',
+ 'suggested_repair_class' => 'preserve_runtime_island',
+ 'preservation_strategy' => $island['preservation_strategy'] ?? 'bounded_raw_html_runtime_island',
+ 'runtime_requirement' => $island['runtime_requirement'] ?? null,
+ 'kind' => $island['kind'] ?? null,
+ 'reason' => $island['preservation_reason'] ?? null,
+ 'tag' => $island['tag'] ?? null,
+ 'selector' => $island['selector'] ?? null,
+ 'pattern_family' => $island['pattern_family'] ?? null,
+ 'pattern_family_detail' => $island['pattern_family_detail'] ?? null,
+ 'source_selector' => $island['source_selector'] ?? null,
+ 'source_selector_specificity' => $island['source_selector_specificity'] ?? null,
+ 'parent_reason' => $island['parent_reason'] ?? null,
+ 'ancestor_reason' => $island['ancestor_reason'] ?? null,
+ 'suggested_generic_repair_class' => $island['suggested_generic_repair_class'] ?? null,
+ ), static fn (mixed $value): bool => null !== $value && '' !== $value);
+ }
+
+ foreach ( $blockValidityReport['findings'] ?? array() as $finding ) {
+ if ( ! is_array($finding) ) {
+ continue;
+ }
+
+ $diagnostics[] = array(
+ 'code' => 'wp_block_validity_' . (string) ($finding['code'] ?? 'warning'),
+ 'message' => (string) ($finding['summary'] ?? 'Generated block serialization may trigger WordPress block invalidity warnings.'),
+ 'source' => Runtime::class,
+ 'severity' => $finding['severity'] ?? 'warning',
+ 'block_name' => $finding['block_name'] ?? null,
+ 'path' => $finding['path'] ?? null,
+ );
+ }
+
+ foreach ( $semanticParityReport['findings'] ?? array() as $finding ) {
+ if ( ! is_array($finding) ) {
+ continue;
+ }
+
+ $diagnostics[] = array(
+ 'code' => 'html_semantic_parity_' . (string) ($finding['code'] ?? 'warning'),
+ 'message' => (string) ($finding['summary'] ?? 'Generated blocks differ from source semantic structure.'),
+ 'source' => $transformerSource,
+ 'severity' => $finding['severity'] ?? 'warning',
+ 'selector' => $finding['selector'] ?? null,
+ );
+ }
+
+ return $diagnostics;
+ }
+}
diff --git a/php-transformer/src/HtmlToBlocks/HtmlTransformer.php b/php-transformer/src/HtmlToBlocks/HtmlTransformer.php
index a5cd5278..7df78626 100644
--- a/php-transformer/src/HtmlToBlocks/HtmlTransformer.php
+++ b/php-transformer/src/HtmlToBlocks/HtmlTransformer.php
@@ -6,6 +6,7 @@
use Automattic\BlocksEngine\PhpTransformer\Contract\ConversionReportProjection;
use Automattic\BlocksEngine\PhpTransformer\Contract\TransformationOptions;
use Automattic\BlocksEngine\PhpTransformer\Contract\TransformerResult;
+use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Diagnostics\DiagnosticsCollector;
use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\AccordionPattern;
use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\ButtonsPattern;
use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\DetailsPattern;
@@ -68,6 +69,8 @@ final class HtmlTransformer
private readonly PatternRecognizerRegistry $patternRecognizers;
+ private readonly DiagnosticsCollector $diagnosticsCollector;
+
/**
* @var array
*/
@@ -145,6 +148,7 @@ public function __construct(private readonly Runtime $runtime = new Runtime())
new AccordionPattern(),
new NavigationPattern(),
));
+ $this->diagnosticsCollector = new DiagnosticsCollector();
}
/**
@@ -239,112 +243,15 @@ public function transform(string $html, array $options = array()): TransformerRe
$serializedBlocks = $this->runtime->serializeBlocks($blocks);
$blockValidityReport = $this->runtime->validateBlockSerialization($blocks);
$semanticParityReport = $this->semanticParityReport($body, $blocks, $sourceProvenance, $html, (string) ($options['static_css'] ?? ''));
- $diagnostics = array(
- array(
- 'code' => 'html_to_blocks_core_slice',
- 'message' => 'Converted supported core text, layout, media, gallery, embed, file, table, button, shortcode, spacer, definition-list, details, navigation, safe inline SVG images, and wrapper elements; unsupported elements are reported as fallbacks.',
- 'source' => self::class,
- ),
+ $diagnostics = $this->diagnosticsCollector->collect(
+ self::class,
+ $this->scriptMetadata,
+ $fallbacks,
+ $this->runtimeIslands,
+ $blockValidityReport,
+ $semanticParityReport
);
- foreach ( $this->scriptMetadata as $metadata ) {
- $diagnostics[] = array(
- 'code' => 'html_static_script_metadata',
- 'message' => 'Static script data was preserved as bounded metadata and does not require client script execution.',
- 'source' => self::class,
- 'reason' => 'script_static_metadata',
- 'tag' => 'script',
- 'selector' => $metadata['selector'] ?? null,
- 'script_role' => $metadata['script_role'] ?? null,
- );
- }
-
- foreach ( $fallbacks as $fallback ) {
- if ( ! empty($fallback['diagnostic_code']) ) {
- $diagnostics[] = array(
- 'code' => $fallback['diagnostic_code'],
- 'message' => $fallback['message'] ?? 'HTML element preserved as fallback metadata.',
- 'source' => self::class,
- 'reason' => $fallback['reason'] ?? null,
- 'severity' => $fallback['severity'] ?? null,
- 'conversion_classification' => $fallback['conversion_classification'] ?? null,
- 'loss_class' => $fallback['loss_class'] ?? null,
- 'diagnostic_class' => $fallback['diagnostic_class'] ?? null,
- 'preservation_strategy' => $fallback['preservation_strategy'] ?? null,
- 'runtime_requirement' => $fallback['runtime_requirement'] ?? null,
- 'recoverability' => $fallback['recoverability'] ?? null,
- 'actionability' => $fallback['actionability'] ?? null,
- 'suggested_repair_class' => $fallback['suggested_repair_class'] ?? null,
- 'suggested_primitive' => $fallback['suggested_primitive'] ?? null,
- 'materialization_hint' => $fallback['materialization_hint'] ?? null,
- 'tag' => $fallback['tag'] ?? null,
- 'selector' => $fallback['selector'] ?? null,
- 'pattern_family' => $fallback['pattern_family'] ?? null,
- 'pattern_family_detail' => $fallback['pattern_family_detail'] ?? null,
- 'source_selector' => $fallback['source_selector'] ?? null,
- 'source_selector_specificity' => $fallback['source_selector_specificity'] ?? null,
- 'parent_reason' => $fallback['parent_reason'] ?? null,
- 'ancestor_reason' => $fallback['ancestor_reason'] ?? null,
- 'suggested_generic_repair_class' => $fallback['suggested_generic_repair_class'] ?? null,
- );
- }
- }
-
- foreach ( $this->runtimeIslands as $island ) {
- $diagnostics[] = array_filter(array(
- 'code' => 'preserved_runtime_island',
- 'message' => 'Runtime-dependent source markup was preserved as a bounded runtime island.',
- 'source' => self::class,
- 'severity' => 'info',
- 'conversion_classification' => 'runtime_island_preserved',
- 'loss_class' => 'runtime_island_preserved',
- 'diagnostic_class' => 'runtime_island_preserved',
- 'suggested_repair_class' => 'preserve_runtime_island',
- 'preservation_strategy' => $island['preservation_strategy'] ?? 'bounded_raw_html_runtime_island',
- 'runtime_requirement' => $island['runtime_requirement'] ?? null,
- 'kind' => $island['kind'] ?? null,
- 'reason' => $island['preservation_reason'] ?? null,
- 'tag' => $island['tag'] ?? null,
- 'selector' => $island['selector'] ?? null,
- 'pattern_family' => $island['pattern_family'] ?? null,
- 'pattern_family_detail' => $island['pattern_family_detail'] ?? null,
- 'source_selector' => $island['source_selector'] ?? null,
- 'source_selector_specificity' => $island['source_selector_specificity'] ?? null,
- 'parent_reason' => $island['parent_reason'] ?? null,
- 'ancestor_reason' => $island['ancestor_reason'] ?? null,
- 'suggested_generic_repair_class' => $island['suggested_generic_repair_class'] ?? null,
- ), static fn (mixed $value): bool => null !== $value && '' !== $value);
- }
-
- foreach ( $blockValidityReport['findings'] ?? array() as $finding ) {
- if ( ! is_array($finding) ) {
- continue;
- }
-
- $diagnostics[] = array(
- 'code' => 'wp_block_validity_' . (string) ($finding['code'] ?? 'warning'),
- 'message' => (string) ($finding['summary'] ?? 'Generated block serialization may trigger WordPress block invalidity warnings.'),
- 'source' => Runtime::class,
- 'severity' => $finding['severity'] ?? 'warning',
- 'block_name' => $finding['block_name'] ?? null,
- 'path' => $finding['path'] ?? null,
- );
- }
-
- foreach ( $semanticParityReport['findings'] ?? array() as $finding ) {
- if ( ! is_array($finding) ) {
- continue;
- }
-
- $diagnostics[] = array(
- 'code' => 'html_semantic_parity_' . (string) ($finding['code'] ?? 'warning'),
- 'message' => (string) ($finding['summary'] ?? 'Generated blocks differ from source semantic structure.'),
- 'source' => self::class,
- 'severity' => $finding['severity'] ?? 'warning',
- 'selector' => $finding['selector'] ?? null,
- );
- }
-
$metrics = $this->metrics($html, $blocks, $serializedBlocks, $fallbacks, $diagnostics, $startedAt);
$nativeTargetBlocks = $this->runtime->availableCoreBlockNames();
$sourceReports = array(