mosaic-headless 1.8.0 → 1.9.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.
@@ -72,7 +72,14 @@ PROBE = r"""
72
72
  pointerEvents: cs.pointerEvents,
73
73
  clipPath: cs.clipPath,
74
74
  transform: cs.transform,
75
- counter: cs.getPropertyValue('--mk-n').trim(),
75
+ // Every registered custom property this element carries, not one name
76
+ // picked in advance. The counters are drawn by `::after` through
77
+ // `counter()`, so `textContent` is empty and computed `content` comes back
78
+ // as the literal `counter(s, decimal-leading-zero)` rather than the digits -
79
+ // the animated integer underneath is the only readable trace they leave.
80
+ // Hard-coding `--mk-n` meant a running clock counted as motionless.
81
+ counter: Array.from(cs).filter(p => p.startsWith('--'))
82
+ .map(p => p + '=' + cs.getPropertyValue(p).trim()).join(';'),
76
83
  text: (el.textContent || '').trim().slice(0, 24),
77
84
  w: Math.round(r.width), h: Math.round(r.height),
78
85
  };
@@ -115,7 +122,12 @@ PROBE = r"""
115
122
 
116
123
 
117
124
  def read_ids(spec, slug):
118
- """Every attrID on the page, split into veil and content."""
125
+ """Every attrID the visitor will see - the page tree AND the shell.
126
+
127
+ Walking only `page["tree"]` misses the header and footer entirely, because in
128
+ Mosaic those live on the master rather than on the template. The running clock
129
+ is in the header, so a check for "is anything still moving" could not see the
130
+ one element built specifically to always be moving."""
119
131
  ids = []
120
132
 
121
133
  def walk(node):
@@ -129,6 +141,9 @@ def read_ids(spec, slug):
129
141
  for v in node:
130
142
  walk(v)
131
143
 
144
+ shell = spec.get("shell") or {}
145
+ walk(shell.get("header"))
146
+ walk(shell.get("footer"))
132
147
  for page in spec["pages"]:
133
148
  if page["slug"] == slug:
134
149
  walk(page.get("tree"))
@@ -206,6 +221,8 @@ def main():
206
221
  ap.add_argument("--page")
207
222
  ap.add_argument("--veil-prefix", default="mk-boot",
208
223
  help="ids under this prefix are the intro, not the content")
224
+ ap.add_argument("--min-ambient", type=int, default=3,
225
+ help="how many elements must still be moving once settled")
209
226
  ap.add_argument("--deadline-ms", type=int, default=4200,
210
227
  help="by this point the intro must be over")
211
228
  ap.add_argument("--csv")
@@ -248,9 +265,13 @@ def main():
248
265
  "%d of %d intro elements changed state" % (len(veil_moved),
249
266
  len(veil))))
250
267
  # the counter is an animated integer, so it can be read rather than admired
251
- counters = [(r["t_ms"], (r["nodes"].get(a.veil_prefix + "-num") or {})
252
- .get("counter", "")) for r in readings]
253
- nums = [int(c) for _t, c in counters if c.isdigit()]
268
+ import re as _re
269
+ nums = []
270
+ for r in readings:
271
+ sig = (r["nodes"].get(a.veil_prefix + "-num") or {}).get("counter", "")
272
+ hit = _re.search(r"--mk-n=(-?\d+)", sig)
273
+ if hit:
274
+ nums.append(int(hit.group(1)))
254
275
  checks.append(("COUNTS", bool(nums) and max(nums) >= 99,
255
276
  "--mk-n reached %s" % (max(nums) if nums else "nothing")))
256
277
 
@@ -271,6 +292,29 @@ def main():
271
292
  cleared = bool(clicked) and not str(clicked).startswith(a.veil_prefix)
272
293
  checks.append(("CLEARS", cleared, "a real click landed on %r" % clicked))
273
294
 
295
+ # AMBIENT - does anything still move once the page has settled?
296
+ #
297
+ # Every other motion check here fires on an event: the document loading, an
298
+ # element entering the viewport. A page can pass all of them and still be
299
+ # completely static the moment you stop scrolling, which is a real quality a
300
+ # reader notices and nothing was measuring. So: take the last two readings,
301
+ # both well past the intro, and count how many elements differ between them.
302
+ settled = [r for r in readings if r["t_ms"] >= a.deadline_ms]
303
+ moving = set()
304
+ if len(settled) >= 2:
305
+ first, last_ = settled[-2], settled[-1]
306
+ for i in ids:
307
+ x, y = first["nodes"].get(i), last_["nodes"].get(i)
308
+ if not x or not y:
309
+ continue
310
+ if (x["transform"], x["opacity"], x["counter"], x["text"]) != (y["transform"], y["opacity"], y["counter"], y["text"]):
311
+ moving.add(i)
312
+ checks.append(("AMBIENT", len(moving) >= a.min_ambient,
313
+ "%d elements still moving between %dms and %dms with no input: %s"
314
+ % (len(moving), settled[-2]["t_ms"] if len(settled) >= 2 else 0,
315
+ settled[-1]["t_ms"] if settled else 0,
316
+ ", ".join(sorted(moving)[:6]) or "nothing")))
317
+
274
318
  # DEGRADES
275
319
  red, red_click, _ = run(url, ids, True, None)
276
320
  first = red[0]