mosaic-headless 1.30.0 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +4 -2
- package/README.ko.md +4 -2
- package/README.md +5 -3
- package/README.zh-TW.md +4 -2
- package/SKILL.md +83 -39
- package/assets/templates/platforms/claude-ai.json +1 -1
- package/assets/templates/platforms/claude-code.json +1 -1
- package/assets/templates/platforms/codex-cli.json +1 -1
- package/assets/templates/platforms/copilot.json +1 -1
- package/assets/templates/platforms/gemini-cli.json +1 -1
- package/data/browser-verification/moksa.csv +3986 -0
- package/data/browser-verification/my-account.csv +148 -0
- package/data/component-wall-verification.csv +1 -1
- package/data/condition-comparators.csv +2 -2
- package/data/content-loop-verification.csv +4 -1
- package/data/data-class-hierarchy.csv +1 -0
- package/data/dialog-form-verification.csv +2 -2
- package/data/dialog-verification.csv +1 -1
- package/data/form-surface-verification.csv +3 -0
- package/data/interaction-types.csv +1 -0
- package/data/intro-verification.csv +16 -16
- package/data/mobile-audit.csv +22 -19
- package/data/navigation-verification.csv +16 -13
- package/data/node-properties.csv +10 -5
- package/data/node-types.csv +1 -0
- package/data/node-verification.csv +3 -0
- package/data/placement-rules.csv +1 -0
- package/data/pluggables.csv +16 -4
- package/data/popover-verification.csv +38 -0
- package/data/style-states.csv +1 -0
- package/data/token-verification.csv +2 -2
- package/package.json +3 -1
- package/references/dialog-and-triggers.md +199 -8
- package/references/dynamic-content.md +19 -0
- package/references/failure-modes.md +36 -4
- package/references/styling.md +9 -0
- package/references/upgrading.md +46 -1
- package/scripts/check-release.mjs +41 -11
- package/sites/_dialogs.py +18 -8
- package/sites/_moksa.py +69 -39
- package/sites/_popovers.py +570 -0
- package/tools/audit_mobile.py +1 -1
- package/tools/extract_default_children.py +1 -1
- package/tools/extract_node_types.py +1 -1
- package/tools/sweep_node_types.py +55 -6
- package/tools/verify_browser.py +20 -8
- package/tools/verify_conversion.py +6 -2
- package/tools/verify_dialog.py +3 -2
- package/tools/verify_dialog_lab.py +11 -11
- package/tools/verify_intro.py +5 -3
- package/tools/verify_popovers.py +348 -0
- package/data/browser-verification.csv +0 -4133
|
@@ -7,6 +7,20 @@
|
|
|
7
7
|
sweep.json needs: base, version, cookie, nonce, themeID. --setup fills in masterID,
|
|
8
8
|
templateID and parentNodeID and writes them back.
|
|
9
9
|
|
|
10
|
+
## Which page it reads
|
|
11
|
+
|
|
12
|
+
The template --setup creates is assigned to `archive-post.php` - the post archive -
|
|
13
|
+
so the probes render wherever WordPress serves that archive. That used to be `/`,
|
|
14
|
+
and the sweep read `/`. Then the site got a static front page (`show_on_front=page`),
|
|
15
|
+
`/` became an ordinary page with its own template, and the sweep went on committing
|
|
16
|
+
probes into a master nothing rendered: every type, a plain `div` included, came
|
|
17
|
+
back COMMITTED. Nothing failed; it simply measured the wrong page.
|
|
18
|
+
|
|
19
|
+
So the page is named rather than assumed: `sweepPath` in the config, default
|
|
20
|
+
`?post_type=post`, which serves the post archive whatever the front page is set to.
|
|
21
|
+
Before a sweep, a plain `div` is committed and looked for on that page; if it does
|
|
22
|
+
not render, the run stops instead of recording a column of false COMMITTEDs.
|
|
23
|
+
|
|
10
24
|
## Why one at a time
|
|
11
25
|
|
|
12
26
|
A batch sweep is worthless here. Mosaic accepts structurally impossible placements at
|
|
@@ -79,9 +93,14 @@ class Client:
|
|
|
79
93
|
)
|
|
80
94
|
|
|
81
95
|
def page(self, path=""):
|
|
82
|
-
"""Fetch a public page, cache-busted. `path` picks a page other than the home page
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
"""Fetch a public page, cache-busted. `path` picks a page other than the home page:
|
|
97
|
+
a slug (`my-page`) or a query (`?post_type=post`)."""
|
|
98
|
+
bust = uuid.uuid4().hex[:8]
|
|
99
|
+
if path.startswith("?"):
|
|
100
|
+
url = "%s/%s&sweep=%s" % (self.base, path, bust)
|
|
101
|
+
else:
|
|
102
|
+
url = "%s/%s?sweep=%s" % (self.base, path.strip("/") + "/" if path else "", bust)
|
|
103
|
+
req = urllib.request.Request(url)
|
|
85
104
|
try:
|
|
86
105
|
with urllib.request.urlopen(req, timeout=120) as r:
|
|
87
106
|
return r.read().decode("utf-8", "replace")
|
|
@@ -223,6 +242,34 @@ def unwrap(resp, what):
|
|
|
223
242
|
return resp["response"]
|
|
224
243
|
|
|
225
244
|
|
|
245
|
+
def sweep_path(cfg):
|
|
246
|
+
"""The public URL the sweep master renders on. See "Which page it reads"."""
|
|
247
|
+
return cfg.get("sweepPath", "?post_type=post")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def assert_probe_renders(client, cfg):
|
|
251
|
+
"""Commit a plain div and require it on the sweep page, then remove it.
|
|
252
|
+
|
|
253
|
+
A div renders on any healthy page. If it does not show up, the page being read is
|
|
254
|
+
not the one the sweep master renders on, and every row this run would write is
|
|
255
|
+
a false COMMITTED - so stop before writing any."""
|
|
256
|
+
master_id = cfg["masterID"]
|
|
257
|
+
key = "node/master/%s" % master_id
|
|
258
|
+
doc = unwrap(client.get("masterDocumentInstance/%s" % master_id), "masterDocumentInstance")
|
|
259
|
+
baseline_ids = {n["ID"] for n in doc[key]}
|
|
260
|
+
attr, _nid, recs = probe_records("div", cfg["parentNodeID"], master_id)
|
|
261
|
+
client.commit("masterDocumentInstance/%s" % master_id, envelopes(doc), {key: recs})
|
|
262
|
+
try:
|
|
263
|
+
html = client.page(sweep_path(cfg))
|
|
264
|
+
if ('id="%s"' % attr) not in html:
|
|
265
|
+
sys.exit("a plain div committed to the sweep master does not render on /%s - "
|
|
266
|
+
"that page is not the one the master renders on, so every row would "
|
|
267
|
+
"read COMMITTED. Set sweepPath in the config to a URL served by the "
|
|
268
|
+
"post archive (the default is ?post_type=post)." % sweep_path(cfg))
|
|
269
|
+
finally:
|
|
270
|
+
delete_subtree(client, master_id, baseline_ids, cfg["parentNodeID"])
|
|
271
|
+
|
|
272
|
+
|
|
226
273
|
def setup(client, cfg, config_path):
|
|
227
274
|
"""Create the master (which heals into a default node tree) and the template."""
|
|
228
275
|
tei = unwrap(client.get("adminMasterEditorInstance"), "adminMasterEditorInstance")
|
|
@@ -282,7 +329,7 @@ def setup(client, cfg, config_path):
|
|
|
282
329
|
json.dump(cfg, fh, indent=1)
|
|
283
330
|
print("masterID=%s\ntemplateID=%s\nparentNodeID=%s" % (master_id, template_id, parent))
|
|
284
331
|
|
|
285
|
-
html = client.page()
|
|
332
|
+
html = client.page(sweep_path(cfg))
|
|
286
333
|
print("baseline page: %d bytes%s" % (len(html), "" if len(html) >= MIN_HEALTHY_BYTES else " <-- ALREADY BROKEN"))
|
|
287
334
|
|
|
288
335
|
|
|
@@ -347,12 +394,14 @@ def delete_subtree(client, master_id, keep_ids, parent_id=None):
|
|
|
347
394
|
|
|
348
395
|
|
|
349
396
|
def sweep(client, cfg, types, out_path):
|
|
397
|
+
assert_probe_renders(client, cfg)
|
|
350
398
|
master_id = cfg["masterID"]
|
|
351
399
|
doc = unwrap(client.get("masterDocumentInstance/%s" % master_id), "masterDocumentInstance")
|
|
352
400
|
key = "node/master/%s" % master_id
|
|
353
401
|
baseline_ids = {n["ID"] for n in doc[key]}
|
|
354
402
|
|
|
355
|
-
|
|
403
|
+
path = sweep_path(cfg)
|
|
404
|
+
baseline_html = client.page(path)
|
|
356
405
|
if len(baseline_html) < MIN_HEALTHY_BYTES:
|
|
357
406
|
sys.exit("baseline page is already broken (%d bytes); reset the theme first" % len(baseline_html))
|
|
358
407
|
print("baseline: %d bytes, %d nodes\n" % (len(baseline_html), len(baseline_ids)))
|
|
@@ -373,7 +422,7 @@ def sweep(client, cfg, types, out_path):
|
|
|
373
422
|
err = "PHP fatal / gateway error during commit"
|
|
374
423
|
tag, classes, echoed, size = "", "", "", ""
|
|
375
424
|
else:
|
|
376
|
-
html = client.page()
|
|
425
|
+
html = client.page(path)
|
|
377
426
|
size = len(html)
|
|
378
427
|
if size < MIN_HEALTHY_BYTES:
|
|
379
428
|
outcome, tag, classes, echoed = "BROKE_PAGE", "", "", ""
|
package/tools/verify_browser.py
CHANGED
|
@@ -213,7 +213,7 @@ def tidy(text):
|
|
|
213
213
|
report every hairline on the page as overridden."""
|
|
214
214
|
t = re.sub(r"\s*,\s*", ",", (text or "").strip().lower())
|
|
215
215
|
t = re.sub(r"\s+", " ", t)
|
|
216
|
-
return re.sub(r"(?<![\d.])\.(\d)", r"0
|
|
216
|
+
return re.sub(r"(?<![\d.])\.(\d)", r"0.\1", t)
|
|
217
217
|
|
|
218
218
|
|
|
219
219
|
# The comparison knows properties by their Mosaic key, but customDeclarations hands it raw
|
|
@@ -609,7 +609,8 @@ def main():
|
|
|
609
609
|
ap.add_argument("--config",
|
|
610
610
|
help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
|
|
611
611
|
ap.add_argument("--site", required=True)
|
|
612
|
-
ap.add_argument("--csv", help="the computed-value table"
|
|
612
|
+
ap.add_argument("--csv", help="a DIRECTORY: the computed-value table is "
|
|
613
|
+
"written one file per page, <dir>/<slug>.csv")
|
|
613
614
|
ap.add_argument("--audit", help="the design-audit findings")
|
|
614
615
|
ap.add_argument("--ack", default=os.path.join(
|
|
615
616
|
os.path.dirname(os.path.abspath(__file__)), "..", "data",
|
|
@@ -754,13 +755,24 @@ def main():
|
|
|
754
755
|
# count in the release gate - should not have to find the blank line between them.
|
|
755
756
|
# The audit file is written even when it is empty: a header with no rows says
|
|
756
757
|
# "this ran and found nothing", where a missing file says nothing at all.
|
|
758
|
+
#
|
|
759
|
+
# The computed-value table is one file per page, named by its slug, with no url
|
|
760
|
+
# column: a single file carried the site's address 4,000 times over and passed
|
|
761
|
+
# the plugin directory's 256 KiB per-file limit. The slug is the filename, and
|
|
762
|
+
# the address is whatever --config pointed at.
|
|
757
763
|
if a.csv:
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
+
os.makedirs(a.csv, exist_ok=True)
|
|
765
|
+
per_page = {}
|
|
766
|
+
for r in rows:
|
|
767
|
+
per_page.setdefault(r[0].rstrip("/").rsplit("/", 1)[-1], []).append(r[1:])
|
|
768
|
+
for slug, page_rows in per_page.items():
|
|
769
|
+
out = os.path.join(a.csv, slug + ".csv")
|
|
770
|
+
with open(out, "w", newline="", encoding="utf-8") as fh:
|
|
771
|
+
w = csv.writer(fh)
|
|
772
|
+
w.writerow(["breakpoint", "attrID", "property", "declared",
|
|
773
|
+
"computed", "status"])
|
|
774
|
+
w.writerows(page_rows)
|
|
775
|
+
print("\nwrote %s (%d rows)" % (out, len(page_rows)))
|
|
764
776
|
if a.audit:
|
|
765
777
|
with open(a.audit, "w", newline="", encoding="utf-8") as fh:
|
|
766
778
|
w = csv.writer(fh)
|
|
@@ -29,6 +29,7 @@ import argparse
|
|
|
29
29
|
import csv
|
|
30
30
|
import html as htmllib
|
|
31
31
|
import json
|
|
32
|
+
import os
|
|
32
33
|
import re
|
|
33
34
|
import sys
|
|
34
35
|
import time
|
|
@@ -129,7 +130,7 @@ def main():
|
|
|
129
130
|
ap.add_argument("--report", help="the CSV from_elementor.py wrote")
|
|
130
131
|
ap.add_argument("--csv")
|
|
131
132
|
ap.add_argument("--width", type=int, default=1440)
|
|
132
|
-
ap.add_argument("--browser", help="browser-verification CSV from verify_browser.py")
|
|
133
|
+
ap.add_argument("--browser", help="browser-verification CSV, or the per-page directory, from verify_browser.py")
|
|
133
134
|
ap.add_argument("--audit", help="design-audit CSV from verify_browser.py")
|
|
134
135
|
ap.add_argument("--rwd", help="rwd-verification CSV from verify_rwd.py")
|
|
135
136
|
ap.add_argument("--prefix", default="el",
|
|
@@ -230,7 +231,10 @@ def main():
|
|
|
230
231
|
"own media query" % len(rr) if not bad else
|
|
231
232
|
"%d of %d not verified" % (len(bad), len(rr)))
|
|
232
233
|
if a.browser:
|
|
233
|
-
|
|
234
|
+
# a file, or the per-page directory verify_browser.py writes
|
|
235
|
+
files = ([os.path.join(a.browser, f) for f in sorted(os.listdir(a.browser))
|
|
236
|
+
if f.endswith(".csv")] if os.path.isdir(a.browser) else [a.browser])
|
|
237
|
+
br = [r for f in files for r in csv.DictReader(open(f, encoding="utf-8"))]
|
|
234
238
|
ok = sum(1 for r in br if r.get("status") == "ok")
|
|
235
239
|
over = [r for r in br if r.get("status") == "OVERRIDDEN"]
|
|
236
240
|
check("COMPUTED", bool(br) and not over,
|
package/tools/verify_dialog.py
CHANGED
|
@@ -68,14 +68,15 @@ DIALOG_STATE = """id => {
|
|
|
68
68
|
const el = document.getElementById(id);
|
|
69
69
|
if (!el) return null;
|
|
70
70
|
const cs = getComputedStyle(el);
|
|
71
|
-
return {tag: el.tagName.toLowerCase(), open: !!el.open,
|
|
71
|
+
return {tag: el.tagName.toLowerCase(), open: !!(el.open || el.matches(':popover-open') || el.getAttribute('data-mosaic-dialog-state') === 'open'),
|
|
72
72
|
display: cs.display, visible: cs.display !== 'none',
|
|
73
73
|
role: el.getAttribute('role'), ariaModal: el.getAttribute('aria-modal'),
|
|
74
74
|
ariaLabel: el.getAttribute('aria-label'),
|
|
75
75
|
closedby: el.getAttribute('data-mosaic-modal-closedby'),
|
|
76
76
|
hasOverlay: !!el.querySelector('.m-modal-overlay'),
|
|
77
77
|
hasWindow: !!el.querySelector('.m-modal-window'),
|
|
78
|
-
inTopLayer: typeof el.matches === 'function'
|
|
78
|
+
inTopLayer: typeof el.matches === 'function'
|
|
79
|
+
&& (el.matches(':modal') || el.matches(':popover-open'))};
|
|
79
80
|
}"""
|
|
80
81
|
|
|
81
82
|
# An exit-intent trigger listens for the pointer leaving through the top of the
|
|
@@ -38,10 +38,10 @@ def row(check, result, detail=""):
|
|
|
38
38
|
print("%-34s %-5s %s" % (check, result, detail))
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
OPEN = "(id) => { const e = document.getElementById(id); return e ? !!e.open : null; }"
|
|
41
|
+
OPEN = "(id) => { const e = document.getElementById(id); return e ? !!(e.open || e.matches(':popover-open') || e.getAttribute('data-mosaic-dialog-state') === 'open') : null; }"
|
|
42
42
|
BOX = """(id) => {
|
|
43
43
|
const e = document.getElementById(id);
|
|
44
|
-
if (!e || !e.open) return null;
|
|
44
|
+
if (!e || !(e.open || e.matches(':popover-open') || e.getAttribute('data-mosaic-dialog-state') === 'open')) return null;
|
|
45
45
|
const w = e.querySelector('.m-modal-window');
|
|
46
46
|
const r = w.getBoundingClientRect();
|
|
47
47
|
const hr = e.getBoundingClientRect();
|
|
@@ -59,8 +59,8 @@ BOX = """(id) => {
|
|
|
59
59
|
|
|
60
60
|
def shut_all(p):
|
|
61
61
|
p.evaluate("""() => document.querySelectorAll('dialog.m-modal').forEach(d => {
|
|
62
|
-
if (d.open) { try { d.mosaicDialog ? d.mosaicDialog.close() : d.close(); }
|
|
63
|
-
catch(e) {
|
|
62
|
+
if ((d.open || d.matches(':popover-open') || d.getAttribute('data-mosaic-dialog-state') === 'open')) { try { (d.mosaicDialog ? (d.mosaicDialog.requestClose || d.mosaicDialog.close).call(d.mosaicDialog) : d.matches(':popover-open') ? d.hidePopover() : d.close()); }
|
|
63
|
+
catch(e) {} } })""")
|
|
64
64
|
p.wait_for_timeout(250)
|
|
65
65
|
|
|
66
66
|
|
|
@@ -122,10 +122,10 @@ def main():
|
|
|
122
122
|
# version of this verifier passed 36/36 on a page where all ten were
|
|
123
123
|
# permanently on screen.
|
|
124
124
|
p.evaluate("""() => document.querySelectorAll('dialog.m-modal')
|
|
125
|
-
.forEach(d => { if (d.open) { try { d.close(); } catch (e) {} } })""")
|
|
125
|
+
.forEach(d => { if ((d.open || d.matches(':popover-open') || d.getAttribute('data-mosaic-dialog-state') === 'open')) { try { (d.mosaicDialog ? (d.mosaicDialog.requestClose || d.mosaicDialog.close).call(d.mosaicDialog) : d.matches(':popover-open') ? d.hidePopover() : d.close()); } catch (e) {} } })""")
|
|
126
126
|
p.wait_for_timeout(400)
|
|
127
127
|
shown = p.evaluate("""() => [...document.querySelectorAll('dialog.m-modal')]
|
|
128
|
-
.filter(d => !d.open)
|
|
128
|
+
.filter(d => !(d.open || d.matches(':popover-open') || d.getAttribute('data-mosaic-dialog-state') === 'open'))
|
|
129
129
|
.filter(d => { const r = d.getBoundingClientRect();
|
|
130
130
|
return r.width > 0 && r.height > 0; })
|
|
131
131
|
.map(d => d.id + ' (display:' + getComputedStyle(d).display + ')')""")
|
|
@@ -241,7 +241,7 @@ def main():
|
|
|
241
241
|
p2.goto(URL + "?v=%s%d" % (order[0], i), wait_until="load")
|
|
242
242
|
p2.wait_for_timeout(2200)
|
|
243
243
|
p2.evaluate("""() => document.querySelectorAll('dialog.m-modal')
|
|
244
|
-
.forEach(d => { if (d.open) d.close(); })""")
|
|
244
|
+
.forEach(d => { if ((d.open || d.matches(':popover-open') || d.getAttribute('data-mosaic-dialog-state') === 'open')) (d.mosaicDialog ? (d.mosaicDialog.requestClose || d.mosaicDialog.close).call(d.mosaicDialog) : d.matches(':popover-open') ? d.hidePopover() : d.close()); })""")
|
|
245
245
|
steps = []
|
|
246
246
|
for which in order:
|
|
247
247
|
p2.click("#dl-open-%s" % which)
|
|
@@ -249,7 +249,7 @@ def main():
|
|
|
249
249
|
opened = [k for k in ("aba", "abb") if p2.evaluate(OPEN, "dl-" + k)]
|
|
250
250
|
steps.append("%s>%s" % (which, "+".join(opened) or "none"))
|
|
251
251
|
p2.evaluate("""() => document.querySelectorAll('dialog.m-modal')
|
|
252
|
-
.forEach(d => { if (d.open) d.close(); })""")
|
|
252
|
+
.forEach(d => { if ((d.open || d.matches(':popover-open') || d.getAttribute('data-mosaic-dialog-state') === 'open')) (d.mosaicDialog ? (d.mosaicDialog.requestClose || d.mosaicDialog.close).call(d.mosaicDialog) : d.matches(':popover-open') ? d.hidePopover() : d.close()); })""")
|
|
253
253
|
p2.wait_for_timeout(200)
|
|
254
254
|
seen[",".join(steps)] += 1
|
|
255
255
|
if i == 0:
|
|
@@ -289,7 +289,7 @@ def main():
|
|
|
289
289
|
# document underneath measures clean.
|
|
290
290
|
FIT = """(id) => {
|
|
291
291
|
const e = document.getElementById(id);
|
|
292
|
-
if (!e || !e.open) return null;
|
|
292
|
+
if (!e || !(e.open || e.matches(':popover-open') || e.getAttribute('data-mosaic-dialog-state') === 'open')) return null;
|
|
293
293
|
const w = e.querySelector('.m-modal-window');
|
|
294
294
|
const r = w.getBoundingClientRect(), h = e.getBoundingClientRect();
|
|
295
295
|
let over = 0, worst = null;
|
|
@@ -309,7 +309,7 @@ def main():
|
|
|
309
309
|
pm.goto(URL, wait_until="load")
|
|
310
310
|
pm.wait_for_timeout(2000)
|
|
311
311
|
pm.evaluate("() => document.querySelectorAll('dialog')"
|
|
312
|
-
".forEach(d => d.open && d.close())")
|
|
312
|
+
".forEach(d => (d.open || d.matches(':popover-open') || d.getAttribute('data-mosaic-dialog-state') === 'open') && (d.mosaicDialog ? (d.mosaicDialog.requestClose || d.mosaicDialog.close).call(d.mosaicDialog) : d.matches(':popover-open') ? d.hidePopover() : d.close()))")
|
|
313
313
|
for key in ("centre", "sheet", "drawer", "takeover", "gate", "aba"):
|
|
314
314
|
try:
|
|
315
315
|
pm.click("#dl-open-%s" % key, timeout=5000)
|
|
@@ -328,7 +328,7 @@ def main():
|
|
|
328
328
|
% (f["scrollW"], f["clientW"], f["over"],
|
|
329
329
|
(" <%s>" % f["worst"]) if f["over"] > 1 else "", f["doc"]))
|
|
330
330
|
pm.evaluate("() => document.querySelectorAll('dialog')"
|
|
331
|
-
".forEach(d => d.open && d.close())")
|
|
331
|
+
".forEach(d => (d.open || d.matches(':popover-open') || d.getAttribute('data-mosaic-dialog-state') === 'open') && (d.mosaicDialog ? (d.mosaicDialog.requestClose || d.mosaicDialog.close).call(d.mosaicDialog) : d.matches(':popover-open') ? d.hidePopover() : d.close()))")
|
|
332
332
|
pm.wait_for_timeout(200)
|
|
333
333
|
cm.close()
|
|
334
334
|
|
package/tools/verify_intro.py
CHANGED
|
@@ -53,8 +53,8 @@ import time
|
|
|
53
53
|
|
|
54
54
|
# When to read. Dense through the sequence, then two well past its end - the last
|
|
55
55
|
# two are what turn "it looked right" into "it finished".
|
|
56
|
-
SAMPLES_MS = [120,
|
|
57
|
-
|
|
56
|
+
SAMPLES_MS = [120, 400, 700, 1000, 1300, 1600, 1900, 2200, 2500, 2800,
|
|
57
|
+
3100, 3400, 3800, 5000, 7000]
|
|
58
58
|
|
|
59
59
|
# What to read at each sample. Anything whose id starts with the veil prefix is
|
|
60
60
|
# treated as part of the intro; everything else is content that must end up visible.
|
|
@@ -258,7 +258,9 @@ def main():
|
|
|
258
258
|
help="ids under this prefix are the intro, not the content")
|
|
259
259
|
ap.add_argument("--min-ambient", type=int, default=3,
|
|
260
260
|
help="how many elements must still be moving once settled")
|
|
261
|
-
|
|
261
|
+
# 3400, not the 8900 it used to be: the entrance was compressed to a 2.15s veil
|
|
262
|
+
# plus its .9s exit, because nine seconds before the headline was the defect.
|
|
263
|
+
ap.add_argument("--deadline-ms", type=int, default=3400,
|
|
262
264
|
help="by this point the intro must be over")
|
|
263
265
|
ap.add_argument("--csv")
|
|
264
266
|
ap.add_argument("--frames")
|