Skip to content

fix(php-transformer): recognize the current capture disclosure and gallery runtimes - #2729

Merged
chubes4 merged 1 commit into
trunkfrom
fix/current-capture-runtimes
Oct 13, 2026
Merged

chubes4 merged 1 commit into
trunkfrom
fix/current-capture-runtimes

Conversation

@chubes4

@chubes4 chubes4 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Refs Automattic/data-liberation-agent#787 (follow-up). Follows #2725.

Summary

Blocks Engine recognized the producer's disclosure and gallery runtime bodies from before Data Liberation Agent v0.27.0. The producer's current bodies differ, so its pages kept their captured runtimes. This PR publishes the current bodies as the recognized ones. Each runtime has one recognized body. An earlier body is no longer recognized, and a page that carries it keeps its captured runtime.

Runtime Previous digest Published digest Producer change
Disclosure interpreter 3d350f55… b545f43a77b79d075373048431101fe11d7ff8cdfe7987de6d7971512763d88f DLA #779 (modal-menu ancestor state, class bindings as tokens)
Gallery helper 0a6bf62a… 456af76323601e51d851d3a4d6fe4ff6e3989c9969562f425fd72103c5c57c6b DLA #711 (src-swap galleries)

I took the bodies from DLA origin/main (DISCLOSURE_RUNTIME in src/lib/static-dialogs.ts, RUNTIME in src/lib/static-galleries.ts). The template literals were evaluated as emitted, and sha256(trim(body)) matches the producer's CAPTURE_RUNTIME_SHA256.

Semantic check: what changed in each body, and whether the native replacement does it

Disclosure. The only change from the previous body is the class branch of ancestor replay. The old body set class to the recorded string as a whole. The new body works on tokens: classTokens() removes the other state's tokens that the target state lacks, adds the target state's tokens, and keeps every other class. It also removes an emptied class attribute.

  • The native dialog view script (CapturedDialogBlockGenerator, replay()) already does exactly this token replay. It finds the ancestor by tag plus closed tokens, which is what Promote Static Site Importer architecture fixture to solved corpus #779's identifying tokens are recorded for.
  • The new classTokensOnly rows need nothing extra: they are tag/closed/opened rows that the token replay handles.
  • One small difference: when the emptied attribute is removed, the native script leaves class="" behind.
  • The modal ancestor state that Promote Static Site Importer architecture fixture to solved corpus #779 adds is not behind the digest. Modal panels in a document scope still keep the runtime because canReplaceScopedDisclosure requires dropdowns. Unscoped modals are projected from interaction states, and the native dialog replays their ancestor class bindings.
  • Proof: the new capture-runtime-ancestor-parity-browser.mjs drives the producer runtime and the native dialog with the same clicks (initial, open, close, reopen, Escape). It compares every recorded token and every authored token on each ancestor, covering re-ordered theme classes, token-only rows and an empty closed class on body. It passes with the current body. It fails with the previous body, whose whole-attribute replacement wipes unrecorded classes.

Gallery. The only change is the src-swap branch. When …-gallery-sequence is present, slot i with offset o gets sequence[(index+o) mod n] and loses its srcset. Direction controls wrap. Frame mode is byte-for-byte unchanged.

  • The native src-swap carousel from fix(php-transformer): project portable src-swap galleries #2670 (AuthoredCarouselBlockGenerator, syncSrcSwap) builds its slides in sequence order and starts at index 0. It wraps, uses the same slot formula, removes srcset, and strips the direction markers, so the runtime it replaces has nothing left to bind.
  • Proof: a one-off Chromium check ran the producer runtime against the native syncSrcSwap over 14 clicks in both directions on the converted slide order, and the slot src/srcset matched at every step. It is not committed because the native carousel runs on the Interactivity API and needs WordPress. The committed tests cover the conversion shape, the wrap and initial index, and the stripped controls.

Both bodies are therefore recognized.

Changes

  • CaptureEvidence::DISCLOSURE_RUNTIME_SHA256 / GALLERY_RUNTIME_SHA256 and the two fixtures are replaced, not extended.
  • docs/contracts/capture-evidence.md gets the new digests, the one-body-per-runtime rule and what each native replacement reproduces.
  • Tests:
    • New tests/unit/capture-runtime-ancestor-parity.php. A scoped dropdown carrying the current body is retired with a proof whose body_hash is the published digest, and every ancestor class binding reaches the native dialog. A body with one byte changed keeps its runtime and gets no native dialog.
    • New capture-runtime-ancestor-parity-browser.mjs, which is the Chromium parity check above. It is added to the visual-parity job.
    • captured-gallery-contract.php: the coexistence page now carries both current bodies, and both are replaced natively with the published digests. A gallery body with one byte changed keeps the disclosure runtime.
    • authored-carousel-block.php: the src-swap case carries the published gallery body and asserts that the native carousel owns the controls.
    • scoped-mixed-dropdown.php already loads the fixture, so it now exercises the current body.

Equivalence gate

The expectations were regenerated with the documented procedure: rename-checkout.php then baseline.php on a disposable checkout of the pre-move commit 44a1eb5e, with the swap now pointing at the current digests. Two things changed, and both are deliberate:

  1. engine_version is neutralized. It is a release stamp, like the *_duration_ms timings. The v0.38.2 release commit changed it after refactor: neutral capture vocabulary for Blocks Engine capture evidence #2725, and the release commit did not run this workflow. Trunk now fails the gate on 68 compile cases. I diffed one of them, and the release stamp was its only difference. With the stamp neutralized, the regenerated pre-move expectations match this branch on all 349 cases. Before neutralizing, the regenerated digests matched the committed CI expectations on 346 of 349 cases, so the pre-move outputs are the same across platforms.
  2. Three ArtifactCompiler::compile cases (17d97f2c, 2df01069, 52210e3a) carry the previous disclosure body. That body is no longer recognized. Their scoped dropdowns now keep the captured runtime (runtime_dom_contract_preserved) instead of becoming native dialogs. This is the "unknown body keeps the runtime" contract, and the pre-move source with the current digests gives the same output.

Known gaps (not new, apply to the previous body too)

These stay out of this PR because each changes gate outputs. Proposed follow-ups:

  • aria-* / data-* ancestor bindings are dropped natively. The producer records class|style|hidden|aria-*|data-*. CapturedDialogProjector::triggerAncestorState() keeps rows that only use class/style/hidden, so Promote Static Site Importer architecture fixture to solved corpus #779's header row {class, data-section-theme} loses both changes, while the runtime replays them.
  • A labelled <button> trigger becomes core/details. DetailsPattern::matchDisclosure converts the trigger and its projected in-place dropdown panel to core/details after the runtime is retired, which drops the ancestor bindings. An icon-only trigger stays a native dialog. The new parity fixture with a text trigger reproduces this.
  • The navigation-submenu path does not check the digest. It retires the disclosure runtime (native_navigation_submenu_replaces_capture_disclosure) without comparing the body. I observed this with a one-byte-altered body.
  • Gallery-helper retirement does not check the digest. It relies on the marker alone (native_carousel_and_dialog_replace_capture_gallery). On a page that mixes a frame gallery and a src-swap gallery, the src-swap gallery then depends on its native carousel conversion.

Verification (local, macOS, PHP 8.5)

  • composer install, composer validate --strict, composer lint, composer test:lint: pass.
  • composer test: pass. The gate reports 349/349, and the new parity, gallery-contract, scoped-mixed-dropdown and carousel tests pass.
  • Repo root php tests/contract/production-acceptance-matrix.php: pass.
  • Visual parity job: npm ci, npm run lint and npm test pass, as do layout-media-tracks (default and responsive, WordPress 7.0.4), every browser step in the job, and the new ancestor parity step.
  • I did not run the WordPress site plan integration or the Docker stylesheet-activation job locally. CI runs them.

AI disclosure: written by Claude in OpenCode with Chris Huber (direct implementation authorized by the operator); Claude implemented, tested and opened this PR.

…llery runtimes

Publish the producer's current runtime bodies as the recognized ones:
disclosure b545f43a (ancestor class bindings replayed as tokens) and
gallery 456af763 (src-swap sequence mode). One body per runtime; earlier
bodies are no longer recognized and keep their captured runtime.

The native replacements already do what the new bodies add: the native
dialog replays class bindings as token sets, and src-swap galleries
become native src-swap carousels. A Chromium parity test compares the
recognized disclosure runtime with the native dialog on re-ordered,
token-only and empty-class ancestor bindings.

The equivalence gate neutralizes the engine_version release stamp and its
expectations are regenerated from the pre-move commit with the current
digests. Three corpus cases change: they carry the previous disclosure
body, which now keeps its captured runtime.
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.

1 participant