Skip to content

feat: flag empty JS-populated containers during html intake - #890

Open
faisalahammad wants to merge 1 commit into
Automattic:mainfrom
faisalahammad:fix/514-empty-js-populated-container-diagnostic
Open

feat: flag empty JS-populated containers during html intake#890
faisalahammad wants to merge 1 commit into
Automattic:mainfrom
faisalahammad:fix/514-empty-js-populated-container-diagnostic

Conversation

@faisalahammad

Copy link
Copy Markdown
Contributor

Summary

Direct-HTML intake (pasted HTML, public URL, or .html upload) now scans the raw body for empty container elements that carry client-side rendering signals, before conversion. When found, an informational empty_js_populated_container diagnostic is appended to the import report and classified as unsupported_loss. This declares the gap instead of silently producing an empty block.

Fixes #514

Changes

includes/class-static-site-importer-page-materializer.php

Adds find_empty_js_populated_containers() and empty_js_populated_container_diagnostic(), called from the 'html' body-format branch of source_page_content_blocks() before html_to_blocks().

Why: A CSR container ships as <div id="featuredGrid" class="wp-block-group grid"></div> that JS fills at runtime against a data fetch. The importer only sees the static snapshot, so that content is silently absent. This mirrors the existing html_to_blocks_empty_output precedent: the diagnostic flows into $report['diagnostics'] automatically, with no finding-packet or quality-gate wiring.

Detection is deliberately conservative to avoid false positives on legitimate empty spacer and layout divs. A container (div|section|main|article|ul|ol) is flagged only when it has no element or meaningful-text children (comments and whitespace ignored) and either:

  • carries a non-internal data-* attribute (any except the data-ssi-* namespace), or
  • pairs an app-shell id (root, app, __next, gatsby, mount) with a content-name token in id/class (grid, products, product-list, listing, featured, catalog, items, shelf, collection).

So a bare <div class="grid"> or <div class="spacer"> is never reported, and neither is the exact ticket example <div id="featuredGrid" class="wp-block-group grid"></div> (content-name without an app-shell id or a data hook).

The diagnostic is non-blocking (severity: info), never aborts the import, and is not counted in empty_conversion_count. Whole-page CSR shells are already rejected by the existing client_rendered_app_shell fetch guard (url-fetcher.php); this change covers the unhandled interior empty-container case.

tests/smoke-empty-js-populated-container.php (new)

Standalone PHP smoke with 13 assertions covering the flagged and ignored cases:

  • data-* carrier flagged, app-shell id + content class flagged
  • app-shell id alone and content-name alone not flagged (locks strictness and false-positive guard)
  • legitimate spacer/layout grid not flagged
  • non-empty container not flagged
  • data-ssi-* internal attributes excluded
  • section and list containers scanned too
  • comment-only sheet still flagged when it has a data hook
  • loss-class classification surfaces as unsupported_loss

Registered in test-manifest.json and its homeboy-test-manifest.json standalone projection.

Testing

Test 1: Detection

  1. Import pasted HTML containing <div data-slider-container></div> or <section id="products" data-products></section>
  2. Open the import report
    Result: an empty_js_populated_container diagnostic appears with severity: info and loss_class: unsupported_loss.

Test 2: No false positives

  1. Import HTML containing <div class="spacer"></div>, <div class="grid"></div>, and the ticket case <div id="featuredGrid" class="wp-block-group grid"></div>
    Result: no diagnostic is emitted.

Automated

  • php tests/smoke-empty-js-populated-container.php - 13 assertions pass
  • php tests/smoke-diagnostic-loss-classes.php - regression still passes
  • npm test - 55 passed, 0 failed, manifest inventory in sync
  • phpcs --standard=WordPress - zero new violations (7 remaining are all pre-existing on main)

…ic#514)

Direct-HTML intake (pasted HTML, URL, .html upload) now scans the raw
body for empty container elements that carry client-side rendering
signals before conversion. When found, an informational
empty_js_populated_container diagnostic is appended to the import
report and classified as unsupported_loss, so the gap is declared
instead of silently producing an empty block.

Detection is deliberately conservative to avoid false positives on
legitimate empty spacer and layout divs. A container is flagged only
when it has no element or meaningful-text children and either carries
a non-internal data-* attribute, or pairs an app-shell id (root, app,
__next, gatsby, mount) with a content-name token (grid, products,
listing, featured, catalog, items, shelf, collection).

The diagnostic is non-blocking (severity info), never aborts the
import, and is not counted in empty_conversion_count. Whole-page CSR
shells are already caught by the client_rendered_app_shell fetch
guard; this covers the unhandled interior empty-container case.

Adds a standalone PHP smoke covering the flagged and ignored cases,
including the exact ticket example which must not be flagged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Client-side-rendered content (empty containers filled by JS) is invisible to static import — products/listings vanish

1 participant