diff --git a/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php b/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php
index c1bfc4b8..bb1d47ef 100644
--- a/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php
+++ b/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php
@@ -674,6 +674,20 @@ private function withRuntimeIslandTargets(array $targets, array $runtimeIslands)
$targets['classes'][$class] = true;
}
}
+
+ $tag = is_string($island['tag'] ?? null) ? strtolower(trim($island['tag'])) : '';
+ foreach ( $attributes as $name => $value ) {
+ $attributeName = strtolower((string) $name);
+ if ( ! str_starts_with($attributeName, 'data-') || ! preg_match('/^data-[a-z][a-z0-9_-]*$/', $attributeName) ) {
+ continue;
+ }
+
+ $attributeSelector = '[' . $attributeName . ']';
+ $targets['selectors'][$attributeSelector] = true;
+ if ( '' !== $tag ) {
+ $targets['selectors'][$tag . $attributeSelector] = true;
+ }
+ }
}
return $targets;
@@ -1175,7 +1189,7 @@ private function targetExists(array $dependency, array $targets): bool
private function scriptKind(string $path, string $script): string
{
$haystack = strtolower($path . "\n" . substr($script, 0, 2000));
- if ( str_contains($haystack, 'netlify') || str_contains($haystack, 'rum') || str_contains($haystack, 'analytics') || str_contains($haystack, 'gtag') ) {
+ if ( preg_match('#(?:netlify|analytics|gtag|\brum\b|[-_./]rum[-_./])#', $haystack) ) {
return 'telemetry';
}
diff --git a/php-transformer/tests/contract/run.php b/php-transformer/tests/contract/run.php
index 05b8fd06..3306df7e 100644
--- a/php-transformer/tests/contract/run.php
+++ b/php-transformer/tests/contract/run.php
@@ -1713,6 +1713,7 @@ public function match(DOMElement $element, PatternContext $context): ?array
$assert(true === ($expandedRuntimeDependencies['canvas.preview']['canvas_api'] ?? null), 'compound canvas selector records canvas API usage');
$assert(str_contains($expandedRuntimeMarkup, ''), 'compound canvas selector preserves canvas markup');
$assert(str_contains($expandedRuntimeMarkup, 'data-tool'), 'data attribute runtime selector remains addressable in generated markup');
+$assert(true === ($expandedRuntimeDependencies['[data-tool]']['source_present'] ?? null), 'data attribute runtime selector is recorded as present in source markup');
$assert(str_contains($expandedRuntimeMarkup, 'mounted-app'), 'app root receiving appended children remains addressable in generated markup');
$assert(array() === ($expandedRuntimeReport['findings'] ?? array()), 'expanded runtime target selectors do not emit missing-target findings');
@@ -1774,6 +1775,32 @@ public function match(DOMElement $element, PatternContext $context): ?array
'runtime dependency parity does not fail entry output for selectors absent from that entry source'
);
+$sharedDrumScriptSite = $compiler->compile(
+ array(
+ 'entrypoint' => 'index.html',
+ 'files' => array(
+ 'index.html' => 'Drum machine
',
+ 'patterns.html' => '',
+ 'js/site.js' => 'document.querySelectorAll("[data-groove], [data-voice-demo]"); document.querySelectorAll(".is-playing").forEach(function (button) { button.classList.remove("is-playing"); });',
+ ),
+ )
+)->toArray();
+$sharedDrumScriptReport = $sharedDrumScriptSite['source_reports']['runtime_dependency_parity'] ?? array();
+$sharedDrumDependencies = $sharedDrumScriptReport['dependencies'] ?? array();
+$sharedDrumDependency = array_values(array_filter($sharedDrumDependencies, static fn (array $dependency): bool => '[data-voice-demo]' === ($dependency['selector'] ?? '')))[0] ?? null;
+$assert(
+ null !== $sharedDrumDependency,
+ 'runtime dependency parity records shared data-attribute selectors absent from the entry source'
+);
+$assert(
+ 'first_party' === ($sharedDrumDependency['script_kind'] ?? ''),
+ 'runtime dependency parity does not classify drum scripts as RUM telemetry'
+);
+$assert(
+ array() === array_values(array_filter($sharedDrumScriptReport['findings'] ?? array(), static fn (array $finding): bool => in_array($finding['selector'] ?? '', array('.is-playing', '[data-voice-demo]', '[data-groove]'), true))),
+ 'runtime dependency parity does not fail entry output for shared drum script selectors absent from that entry source'
+);
+
$hamburgerOverlaySite = $compiler->compile(
array(
'entrypoint' => 'index.html',