From 7b4aa04a3510a753a9562195db2314f2ae50df28 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Wed, 24 Jun 2026 16:59:08 -0400 Subject: [PATCH] Add runtime parity repair diagnostics --- .../src/ArtifactCompiler/ArtifactCompiler.php | 2 +- .../RuntimeDependencyParityReport.php | 23 ++++++++++++++----- php-transformer/tests/contract/run.php | 7 ++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/php-transformer/src/ArtifactCompiler/ArtifactCompiler.php b/php-transformer/src/ArtifactCompiler/ArtifactCompiler.php index 60d80b4..f1ae567 100644 --- a/php-transformer/src/ArtifactCompiler/ArtifactCompiler.php +++ b/php-transformer/src/ArtifactCompiler/ArtifactCompiler.php @@ -74,7 +74,7 @@ public function compile(array $artifact): TransformerResult ); $sourceReports['compiled_site'] = $this->compiledSiteReport($normalized, $entryPath, $documents['documents'], $assets, $blockTypes, $serializedBlocks); $sourceReports['materialization_plan'] = ( new MaterializationPlanBuilder() )->fromCompiledSite($sourceReports['compiled_site']); - $sourceReports['runtime_dependency_parity'] = ( new RuntimeDependencyParityReport() )->fromArtifact($normalized['files'], $html, $serializedBlocks); + $sourceReports['runtime_dependency_parity'] = ( new RuntimeDependencyParityReport() )->fromArtifact($normalized['files'], $html, $serializedBlocks, $entryPath); $provenance = array( array( 'source_format' => 'artifact', diff --git a/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php b/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php index cbfa521..bab304d 100644 --- a/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php +++ b/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php @@ -14,9 +14,9 @@ final class RuntimeDependencyParityReport * @param array> $files * @return array */ - public function fromArtifact(array $files, string $sourceHtml, string $generatedHtml): array + public function fromArtifact(array $files, string $sourceHtml, string $generatedHtml, string $sourcePath = ''): array { - $sourceTargets = $this->sourceTargets($sourceHtml); + $sourceTargets = $this->sourceTargets($sourceHtml, $sourcePath); $generatedTargets = $this->htmlTargets($generatedHtml); $dependencies = array(); $findings = array(); @@ -39,9 +39,12 @@ public function fromArtifact(array $files, string $sourceHtml, string $generated $exists = $this->targetExists($dependency, $generatedTargets); $canvasApi = true === $dependency['canvas_api'] && 'canvas' === ($target['tag'] ?? ''); $dependencyRow = array_filter(array( + 'source_path' => $target['source_path'] ?? $sourcePath, 'script_path' => $scriptPath, 'script_kind' => $scriptKind, 'selector' => $selector, + 'target_id' => $target['id'] ?? '', + 'target_class' => $target['class'] ?? '', 'target_kind' => $target['tag'] ?? '', 'dependency_kind' => $dependency['kind'], 'events' => $dependency['events'], @@ -56,16 +59,24 @@ public function fromArtifact(array $files, string $sourceHtml, string $generated } $severity = 'telemetry' === $scriptKind ? 'info' : 'warning'; + $repairBucket = $canvasApi ? 'runtime_canvas_target_preservation' : 'runtime_dom_target_preservation'; $findings[] = array_filter(array( 'code' => 'runtime_dependency_target_missing', 'severity' => $severity, + 'source_path' => $target['source_path'] ?? $sourcePath, 'script_path' => $scriptPath, 'script_kind' => $scriptKind, 'selector' => $selector, + 'target_id' => $target['id'] ?? '', + 'target_class' => $target['class'] ?? '', 'target_kind' => $target['tag'] ?? '', 'dependency_kind' => $dependency['kind'], 'events' => $dependency['events'], 'canvas_api' => $canvasApi, + 'repair_bucket' => $repairBucket, + 'suggested_primitive' => $canvasApi ? 'runtime_canvas' : 'runtime_dom_target', + 'actionability' => $canvasApi ? 'preserve_canvas_markup_with_matching_script_runtime_or_rebuild_canvas_behavior' : 'preserve_or_recreate_the_referenced_dom_target_for_script_runtime', + 'materialization_hint' => $canvasApi ? 'preserve_canvas_id_class_and_markup_for_runtime_mapping' : 'preserve_id_class_or_wrapper_markup_required_by_first_party_script', 'message' => sprintf('Script %s references %s, but the generated block markup does not expose that DOM target.', $scriptPath, $selector), ), static fn (mixed $value): bool => null !== $value && '' !== $value && array() !== $value); } @@ -90,9 +101,9 @@ private function isScriptFile(array $file): bool } /** - * @return array + * @return array */ - private function sourceTargets(string $html): array + private function sourceTargets(string $html, string $sourcePath): array { $targets = array(); $document = new DOMDocument(); @@ -111,11 +122,11 @@ private function sourceTargets(string $html): array $tag = strtolower($element->tagName); $id = trim($element->hasAttribute('id') ? $element->getAttribute('id') : ''); if ( '' !== $id ) { - $targets['#' . $id] = array('tag' => $tag); + $targets['#' . $id] = array('tag' => $tag, 'source_path' => $sourcePath, 'id' => $id); } foreach ( preg_split('/\s+/', trim($element->hasAttribute('class') ? $element->getAttribute('class') : '')) ?: array() as $class ) { if ( '' !== $class ) { - $targets['.' . $class] = array('tag' => $tag); + $targets['.' . $class] = array('tag' => $tag, 'source_path' => $sourcePath, 'class' => $class); } } } diff --git a/php-transformer/tests/contract/run.php b/php-transformer/tests/contract/run.php index 76a25bd..e7185e2 100644 --- a/php-transformer/tests/contract/run.php +++ b/php-transformer/tests/contract/run.php @@ -612,10 +612,17 @@ function serialize_blocks(array $blocks): string $assert('blocks-engine/php-transformer/runtime-dependency-parity/v1' === ($runtimeDependencyReport['schema'] ?? ''), 'runtime dependency parity report exposes schema'); $assert($runtimeDependencyReport === $runtimeDependencyConversionReport, 'conversion report projects runtime dependency parity'); $assert('runtime_dependency_target_missing' === ($canvasFinding['code'] ?? ''), 'runtime dependency parity reports missing canvas DOM target'); +$assert('index.html' === ($canvasFinding['source_path'] ?? ''), 'runtime dependency parity reports source path for missing canvas DOM target'); +$assert('canvas' === ($canvasFinding['target_id'] ?? ''), 'runtime dependency parity reports missing canvas target id'); $assert('canvas' === ($canvasFinding['target_kind'] ?? ''), 'runtime dependency parity identifies canvas source target kind'); $assert(true === ($canvasFinding['canvas_api'] ?? null), 'runtime dependency parity flags canvas 2d API usage'); $assert('warning' === ($canvasFinding['severity'] ?? ''), 'first-party missing runtime dependency target is warning severity'); +$assert('runtime_canvas_target_preservation' === ($canvasFinding['repair_bucket'] ?? ''), 'runtime dependency parity reports repair bucket for missing canvas DOM target'); +$assert('runtime_canvas' === ($canvasFinding['suggested_primitive'] ?? ''), 'runtime dependency parity reports suggested primitive for missing canvas DOM target'); +$assert(isset($canvasFinding['actionability']) && '' !== $canvasFinding['actionability'], 'runtime dependency parity reports actionability for missing canvas DOM target'); +$assert(isset($canvasFinding['materialization_hint']) && '' !== $canvasFinding['materialization_hint'], 'runtime dependency parity reports materialization hint for missing canvas DOM target'); $assert(null !== $statusDependency, 'runtime dependency parity records preserved status container dependency'); +$assert('index.html' === ($statusDependency['source_path'] ?? ''), 'runtime dependency parity records source path for preserved DOM dependency'); $assert(true === ($statusDependency['generated_present'] ?? null), 'runtime dependency parity passes preserved div id target'); $assert(! empty($statusDependency['events'] ?? array()), 'runtime dependency parity records simple addEventListener usage'); $assert('info' === ($rumFinding['severity'] ?? ''), 'telemetry-like runtime dependency misses are info severity');