Skip to content

Commit 0938106

Browse files
committed
Gutenberg: a source video plays and rests in the editor on the frame its script leaves it (from 1b83d90)
1 parent 2830e01 commit 0938106

6 files changed

Lines changed: 40 additions & 6 deletions

File tree

‎skills/html2wp/assets/scripts/block-schema.json‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,8 @@
828828
"lock": "lock",
829829
"metadata": "metadata",
830830
"className": "string",
831-
"anchor": "string"
831+
"anchor": "string",
832+
"h2wpSettledFrame": "x:settledFrame"
832833
},
833834
"children": false
834835
},
@@ -1443,6 +1444,9 @@
14431444
"youtube"
14441445
]
14451446
},
1447+
"x:settledFrame": {
1448+
"check": "x:settledFrame"
1449+
},
14461450
"x:slug": {
14471451
"check": "x:slug"
14481452
},

‎skills/html2wp/assets/scripts/gutenberg-plan.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,11 @@ def av_block(self, node):
12451245
attrs['playsInline'] = 'playsinline' in node.attrs
12461246
if node.attrs.get('poster'):
12471247
attrs['poster'] = self.media_url(node.attrs['poster'])
1248+
# WordPress wraps the <video> in a figure the source never had: the
1249+
# source classes move back to the <video> and the figure takes no
1250+
# box (render_block_core/video), so a source script's
1251+
# `.scroll-video` finds the player, not its figure.
1252+
attrs['className'] = (attrs.get('className', '') + ' h2wp-source-video').strip()
12481253
elif 'controls' not in node.attrs:
12491254
self.finding('media-controls', 'core/audio always renders controls')
12501255
if node.attrs.get('preload') in ('auto', 'metadata', 'none'):
@@ -3931,7 +3936,10 @@ def flash_scan(dist, script, kind):
39313936

39323937
# The editor canvas runs no source script, so content a listed script reveals
39333938
# (a scroll-in fade) stays in its hidden state there. Flash writes the settled
3934-
# state of those reveals to a canvas-only sheet (contract.editorStyles).
3939+
# state of those reveals to a canvas-only sheet (contract.editorStyles). It
3940+
# declares no video's settled frame (core/video h2wpSettledFrame, a value a
3941+
# review measures on the frontend): a video a listed script leaves on a frame
3942+
# keeps its poster in the canvas, a Flash limitation.
39353943
FLASH_SETTLED = 'assets/gutenberg-flash-settled.css'
39363944
REVEAL_PROPERTIES = ('opacity', 'visibility', 'transform', 'translate', 'scale', 'rotate', 'filter', 'clip-path')
39373945
CLASS_ADDED = re.compile(r'classList\.add\(([^)]*)\)|\.addClass\(\s*[\'"`]([^\'"`]*)[\'"`]')

‎skills/html2wp/assets/scripts/gutenberg-test-plan.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1095,7 +1095,7 @@ def test_media_and_table_mappings(self):
10951095
'<iframe class="yt" src="https://www.youtube.com/embed/dQw4w9WgXcQ?rel=0" width="640" height="480"></iframe>'
10961096
'<iframe src="https://player.vimeo.com/video/12345"></iframe>'
10971097
'<iframe src="https://maps.example.com/embed"></iframe>'
1098-
'<video autoplay muted loop playsinline poster="/assets/a.jpg"><source src="/assets/clip.mp4" type="video/mp4"></video>'
1098+
'<video class="clip" autoplay muted loop playsinline poster="/assets/a.jpg"><source src="/assets/clip.mp4" type="video/mp4"></video>'
10991099
'<table class="t"><caption>Rates</caption><thead><tr><th scope="col">Plan</th><th>Price</th></tr></thead>'
11001100
'<tbody><tr><td>Single</td><td colspan="2"><strong>$80</strong></td></tr></tbody></table>'
11011101
'<table><tr><td><div>block</div></td></tr></table>'
@@ -1106,6 +1106,7 @@ def test_media_and_table_mappings(self):
11061106
self.assertEqual((vimeo['attributes']['url'], vimeo['attributes']['providerNameSlug']), ('https://vimeo.com/12345', 'vimeo'))
11071107
self.assertEqual(video['name'], 'core/video')
11081108
self.assertEqual({k: video['attributes'][k] for k in ('src', 'poster', 'autoplay', 'muted', 'loop', 'playsInline', 'controls')}, {'src': 'asset:assets/clip.mp4', 'poster': 'asset:assets/a.jpg', 'autoplay': True, 'muted': True, 'loop': True, 'playsInline': True, 'controls': False})
1109+
self.assertEqual(video['attributes']['className'], 'clip h2wp-source-video') # the source classes go back on the <video>
11091110
self.assertEqual(table['name'], 'core/table')
11101111
self.assertEqual(table['attributes']['caption'], 'Rates')
11111112
self.assertEqual(table['attributes']['head'], [{'cells': [{'content': 'Plan', 'tag': 'th', 'scope': 'col'}, {'content': 'Price', 'tag': 'th'}]}])

‎skills/html2wp/assets/scripts/gutenberg_block_schema.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,14 @@ def _x_focalPoint(self, value, at, kind):
542542
if not _number(v) or not (0 <= v <= 1):
543543
_raise(f'{at}: focalPoint.{k} must be 0-1')
544544

545+
def _x_settledFrame(self, value, at, kind):
546+
if not isinstance(value, dict) or ','.join(sorted(_keys(value))) != 'from,seconds':
547+
_raise(f'{at}: settledFrame must be {{from,seconds}}')
548+
if value['from'] not in ('start', 'end'):
549+
_raise(f'{at}: settledFrame.from must be start or end')
550+
if not _number(value['seconds']) or not (0 <= value['seconds'] <= 86400):
551+
_raise(f'{at}: settledFrame.seconds must be 0-86400')
552+
545553
def _x_tableRows(self, value, at, kind):
546554
if not isinstance(value, list) or len(value) > 1000:
547555
_raise(f'{at}: expected table rows')

‎skills/html2wp/references/gutenberg.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,12 @@ reveal properties are written with the class taken as present
220220
element of the source markup. A reveal gated on a script's own class
221221
(`.js .reveal`) needs none: the canvas never has the gate. Whether an
222222
only-added class is the settled state is the scan's guess, not a review.
223+
Flash declares no video's settled frame (`core/video`
224+
`attributes.h2wpSettledFrame`, a value a review measures on the frontend,
225+
`references/repair.md` §6): a video a listed script leaves on a frame (a
226+
scroll video's last frame under reduced motion) keeps its poster in the
227+
canvas, and the page's editor rows show that box. That is a Flash limitation,
228+
not a converter gap.
223229
The summary's `revealsUnsettled` names what may stay hidden on the live site:
224230
per hidden selector a page renders (its markup, plus the classes the scripts
225231
it runs add), the reveal class none of those scripts adds or toggles, the

‎skills/html2wp/references/repair.md‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ merely moved is only the symptom.
112112
|---|---|---|
113113
| a class missing in WP (`.in`, `.is-visible`, `spa-in`), opacity 0 / translateY left | a source script that runs in the original never runs in WordPress | §6, the reveal incidents |
114114
| the editor only (the page's frontend rows green): a rule matches on the frontend that the editor draws otherwise, with the same classes in the source | a listed script wraps or re-parents text (a `spring-scale`, split-text or per-letter wrapper), and a rule such as `.strip span` matches that wrapper. The editor runs no script, so it never gets the wrapper | the script's DOM writes (`createElement`, `innerHTML`, `.wrap`). Lever: `contract.editorStyles` with the settled values (§6, the script-wrapped text incident). Stop (§5) if the page's frontend row is red too, or the box is one the runtime owns |
115+
| the editor only (the page's frontend rows green): a video box, the poster in the editor and another frame of the video on the frontend | a listed script seeks the player and leaves it there (a scroll video under reduced motion: `currentTime = duration - 0.05`, its last frame). The editor runs no script, so it shows the poster | the script's writes to `currentTime`, and `video.currentTime` / `video.duration` read on the frontend at rest under the gates' capture conditions (reduced motion). Lever: the settled-frame flag on the block's proposal, `h2wpSettledFrame` (§6, the scroll video incident). Stop (§5) if the page's frontend row is red too |
115116
| margin / padding / gap differs, same classes | cascade: a WordPress layout rule or a reset outranks the source rule (layers, `revert-layer`, flow-layout margins, `blockGap`) | the WordPress rule that wins: DevTools "computed → rule" on the WP side |
116117
| font-family / colour differs | a stylesheet missing or out of cascade order on that page (a per-page sheet, an inline `<style>` between links) | the page's `<head>` order on both sides |
117118
| an image blank on one side | not decoded (lazy image outside the viewport) or a request failed | the gate's `failedRequests` / `behavior.images` |
@@ -211,7 +212,9 @@ forbidden (§1) or a converter gap (§5).
211212
**Gutenberg block plan (`block-plan/`)**
212213
- Page proposals (`block-plan/pages/<key>.json`, the owning worker's file):
213214
block choice, `className`, `h2wp/field` `name`s, a page's own `styles[]`
214-
(at most 20, source order) and its own scripts.
215+
(at most 20, source order) and its own scripts; a `core/video`'s settled
216+
frame (`attributes.h2wpSettledFrame`, canvas only, §6 the scroll video
217+
incident).
215218
- `contract.scripts[]` (dist `.js`/`.mjs`) and `contract.headScripts[]`: only
216219
**reviewed source scripts**. The planner extracts every inline source script
217220
to `assets/gutenberg-script-<hash>.js` and names that file in the
@@ -390,12 +393,15 @@ fidelity, C4 menus, C5 collections, C6 shop.
390393
- **Causes:** invalid blocks (a proposal the compiler accepts but the editor
391394
re-serializes differently); content a page script reveals, which the editor
392395
shows in its hidden state; text a page script wraps on the frontend (a
393-
wrapper span a rule then matches); per-page sheets outside the source layer; canvas
396+
wrapper span a rule then matches); a video a page script leaves on a frame
397+
the editor never seeks to (it shows the poster); per-page sheets outside the source layer; canvas
394398
width against the content column; a box-less region (`display:contents`)
395399
measured by the union of its descendants.
396400
- **Lever:** `contract.editorStyles` for a settled script state (a reveal's
397401
end state, or the typography of script-wrapped text, measured on the
398-
frontend); the proposal for invalid blocks.
402+
frontend); for a video a script leaves on a frame, the settled-frame flag
403+
on its `core/video` proposal (`attributes.h2wpSettledFrame`, measured on the
404+
frontend, §6); the proposal for invalid blocks.
399405
- **Stop:** layering, width, height instability, the drop zone → the canvas
400406
runtime's to fix → report (§6).
401407
@@ -480,6 +486,7 @@ for the report's reader.
480486
| A component form's email input with neither `name` nor `id` (finalize `field-name`; HTML: an empty submission) | A field the browser does not name is never submitted. The form's only unnamed email/tel/url field is named by its type. Anything ambiguous stays a finding | HTML: stage 2.65 `normalize-form-fields.py --apply`. Gutenberg: set the `h2wp/field` `name` in the proposal (what a mail handler expects: `email`, `phone`, `message`…) and resolve with the name chosen | coordinator (auto-naming: df6f4df) |
481487
| A source's inline reveal script never runs in WordPress, so `.reveal` never gets `.in` and sections stay hidden (G-front, e.g. clara `/index_v5/` 1.4%) | A behaviour the source's OWN script performs does not happen unless that script is shipped. The planner extracts it and blocks on `source-runtime`. Review the file and list it; then the editor, which runs no script, needs the settled state as canvas CSS | `contract.scripts` += the reviewed `assets/gutenberg-script-<hash>.js`; `contract.editorStyles` += a sheet setting the revealed end state (e.g. `.reveal{opacity:1;transform:none}`); resolve `source-runtime` naming the file and what it does | coordinator |
482488
| A strip of names drawn in the editor as authored, not as the frontend showed it: the page's script wrapped each name in `<span class="spring-scale">`, which `.press-logos span` (uppercase, sans, letter-spaced) then matched on the frontend, while the canvas, which runs no script, showed the `:nth-child(even)` rule as written (italic serif, 3 px taller) (G-editor 3.9-9.9% on the front page only; G-front green) | A structure a listed script adds exists only on the frontend. The canvas gets its settled state as CSS only when all of these hold: the page's frontend rows are green (frontend matches source); the cause is traced to a script in `contract.scripts` and its named DOM write; the sheet expresses nothing but that settled state; it styles no box the runtime owns. Anything else is a converter gap (§5) | `contract.editorStyles` += the settled typography of the wrapped text, with values from `getComputedStyle` of the wrapper on the frontend at 1440, 820 and 390 (§2.4), never copied from the authoring sheet (`clamp()`, `var()`); resolve `source-runtime` naming the script, the wrapper it adds, the rule that matches on the frontend, the values on both sides and the width. Then prove it canvas-only: the page's frontend rows are unchanged and green, and `theme-report.json` has no stale-CSS warning for the sheet | coordinator |
489+
| A scroll video drawn as its poster in the editor while the frontend showed its last frame: under reduced motion the page's script seeks `.scroll-video` to `duration - 0.05` and leaves it there, and the canvas, which runs no script, keeps the poster (G-editor 1.1-1.3% on the front page only; G-front green) | The canvas seeks a video only to a frame the coordinator declares, and only when all of these hold: the page's frontend rows are green (frontend matches source); the cause is traced to a script in `contract.scripts` (or the page's `scripts`) and its named seek; the frame declared is the one the frontend rests on under the gates' capture conditions (reduced motion, loaded, not playing), never a frame mid-scrub or mid-play, even when the page shows those to a visitor without reduced motion. Anything else is a converter gap (§5) | in `block-plan/pages/<key>.json`, on the video's `core/video` node, `attributes.h2wpSettledFrame`: `{"from": "end", "seconds": s}` for a script that counts back from the end (s = `duration - currentTime`), `{"from": "start", "seconds": t}` for one that seeks to a time (t = `currentTime`; 0 is the first frame). Read both on the frontend at rest under reduced motion (Playwright `reduced_motion='reduce'`, `networkidle`, then `video.currentTime` and `video.duration` once `video.seeking` is false); write the script's own constant when it states one (`0.05`), else the measured value rounded to the millisecond. The canvas seeks to exactly that. Resolve `source-runtime` naming the script, its seek, the measured `currentTime`/`duration` and the value declared. Then prove it canvas-only: the frontend markup and rows are unchanged and green, and the page's editor rows are green | coordinator |
483490
| Reveal margins guessed after the first 16 components, so a late component waited as deep as another it resembled (G-front, a scroll step late) | Every component's reveal margin is measured. One with no margin reveals on entry | re-run stage -1 once the build carries it | fixed in the converter (e1fd04a) |
484491
| A fast scroll, an anchor jump or a 700 px capture step carried a reveal past the viewport between two frames, and it never played (B/G-front: an image 40 px low on one run in two) | A reveal the page has scrolled past, or one on screen at the end of the document, plays | none; a reveal stuck at its start offset on a current build → report | fixed in the converter (7da8bd5) |
485492
| Smooth scrolling (`scroll-behavior: smooth`) turned a gate's 700 px scroll-through into animations the next step retargeted: an image near its reveal depth photographed revealed on one run and 40 px low on the next (gate B) | Every scroll-through sets `scroll-behavior: auto` for its walk and restores it after | none in the gates; do the same in any scroll you script | fixed in the converter (2aa098c) |

0 commit comments

Comments
 (0)