mosaic-headless 1.23.0 → 1.24.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/SKILL.md +3 -3
- 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/dialog-form-verification.csv +2 -2
- package/data/slider-verification.csv +30 -19
- package/package.json +1 -1
- package/references/dialog-and-triggers.md +12 -0
- package/references/slider.md +39 -3
- package/scripts/check-release.mjs +1 -1
- package/sites/_moksa.py +5 -1
- package/sites/moksa.json +5565 -719
- package/tools/build_page.py +20 -2
- package/tools/verify_slider.py +63 -9
package/tools/build_page.py
CHANGED
|
@@ -143,9 +143,27 @@ class Surface:
|
|
|
143
143
|
# them, and wysiwyg-variable inside a text node is verified
|
|
144
144
|
# to render (references/dynamic-content.md)
|
|
145
145
|
or (t.startswith("wysiwyg-")
|
|
146
|
-
and any(d.startswith("wysiwyg-") for d in parent_defaults))
|
|
146
|
+
and any(d.startswith("wysiwyg-") for d in parent_defaults))
|
|
147
|
+
# A family member under its own family head. The table
|
|
148
|
+
# cannot say this when the head's rule is `any` and its
|
|
149
|
+
# allowed_children column is therefore empty - `slider`
|
|
150
|
+
# accepts anything, so nothing records that
|
|
151
|
+
# `slider-slides` is exactly where it belongs.
|
|
152
|
+
or (parent_type and t.startswith(parent_type + "-")))
|
|
153
|
+
# The comment above covers types that need their own PARENT. The other half
|
|
154
|
+
# of the same story is a family HEAD, which needs its own CHILDREN: a bare
|
|
155
|
+
# `slider` is measured COMMIT_500 because it has no `slider-slides`, not
|
|
156
|
+
# because a div cannot hold it. So a node whose type prefixes other
|
|
157
|
+
# registered types - slider/slider-slides, accordion/accordion-item,
|
|
158
|
+
# form/form-input - and which actually carries one of them is not the thing
|
|
159
|
+
# that was measured, and the verdict does not apply to it.
|
|
160
|
+
family = [x for x in self.types if x.startswith(t + "-")]
|
|
161
|
+
kids = [c.get("type") for c in (node.get("children") or []) if isinstance(c, dict)]
|
|
162
|
+
nested_as_family = bool(family) and any(k in family for k in kids)
|
|
163
|
+
|
|
147
164
|
outcome = self.outcome.get(t)
|
|
148
|
-
if not declared_child and
|
|
165
|
+
if (not declared_child and not nested_as_family
|
|
166
|
+
and (outcome == "BROKE_PAGE" or (outcome or "").startswith("COMMIT_5"))):
|
|
149
167
|
problems.append("%s is measured %s under a plain container, and %s is not its declared parent"
|
|
150
168
|
% (t, outcome, parent_type or "<root>"))
|
|
151
169
|
# The wysiwyg family is inline CONTENT, not a child in the placement sense, and
|
package/tools/verify_slider.py
CHANGED
|
@@ -32,6 +32,7 @@ from __future__ import annotations
|
|
|
32
32
|
|
|
33
33
|
import argparse
|
|
34
34
|
import csv
|
|
35
|
+
import os
|
|
35
36
|
import sys
|
|
36
37
|
import time
|
|
37
38
|
|
|
@@ -80,7 +81,14 @@ READ = """id => {
|
|
|
80
81
|
bulletLabels: bullets.map(b => b.getAttribute('aria-label')),
|
|
81
82
|
bulletRole: bullets[0] ? bullets[0].getAttribute('role') : null,
|
|
82
83
|
bulletTab: bullets[0] ? bullets[0].getAttribute('tabindex') : null,
|
|
83
|
-
prev: arrow('left'), next: arrow('right')
|
|
84
|
+
prev: arrow('left'), next: arrow('right'),
|
|
85
|
+
// what the TRACK actually is. `isCarousel` is widely assumed to mean
|
|
86
|
+
// "show several at once"; this is the measurement that settles it.
|
|
87
|
+
trackAreas: track ? getComputedStyle(track).gridTemplateAreas : null,
|
|
88
|
+
trackCols: track ? getComputedStyle(track).gridTemplateColumns : null,
|
|
89
|
+
trackW: track ? Math.round(track.getBoundingClientRect().width) : null,
|
|
90
|
+
slideW: slides.map(el => Math.round(el.getBoundingClientRect().width)),
|
|
91
|
+
slideLeft: slides.map(el => Math.round(el.getBoundingClientRect().left))};
|
|
84
92
|
}"""
|
|
85
93
|
|
|
86
94
|
CLICK_NTH = """([id, kind, n]) => {
|
|
@@ -110,6 +118,11 @@ def main():
|
|
|
110
118
|
ap.add_argument("--autoplay", type=int, default=0,
|
|
111
119
|
help="declared autoplay delay in ms; 0 means autoplay is off")
|
|
112
120
|
ap.add_argument("--csv")
|
|
121
|
+
ap.add_argument("--label", help="name for this slider in the CSV's first column; "
|
|
122
|
+
"a page usually carries more than one")
|
|
123
|
+
ap.add_argument("--append", action="store_true",
|
|
124
|
+
help="add to the CSV instead of replacing it, so several "
|
|
125
|
+
"sliders on one page land in one table")
|
|
113
126
|
a = ap.parse_args()
|
|
114
127
|
|
|
115
128
|
rows, fails = [], []
|
|
@@ -184,12 +197,46 @@ def main():
|
|
|
184
197
|
now = pg.evaluate(READ, a.slider)
|
|
185
198
|
check("BULLET_GOES", now["current"] == 0,
|
|
186
199
|
"bullet 0 -> slide %s" % now["current"])
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
200
|
+
# A CAROUSEL has no end it cannot pass - it wraps - so neither arrow
|
|
201
|
+
# ever hides, and asserting the plain-slider rule against one reports a
|
|
202
|
+
# failure for correct behaviour. Branch on what the host declares.
|
|
203
|
+
is_carousel = str(now.get("carousel") or st.get("carousel")).lower() in (
|
|
204
|
+
"1", "true", "yes")
|
|
205
|
+
if is_carousel:
|
|
206
|
+
check("ARROW_ENDS",
|
|
207
|
+
bool(now["prev"]) and not now["prev"]["hidden"]
|
|
208
|
+
and bool(now["next"]) and not now["next"]["hidden"],
|
|
209
|
+
"carousel: it wraps, so neither arrow hides "
|
|
210
|
+
"(prev hidden=%s, next hidden=%s)"
|
|
211
|
+
% (now["prev"] and now["prev"]["hidden"],
|
|
212
|
+
now["next"] and now["next"]["hidden"]))
|
|
213
|
+
else:
|
|
214
|
+
check("ARROW_ENDS",
|
|
215
|
+
bool(now["prev"]) and now["prev"]["hidden"]
|
|
216
|
+
and bool(now["next"]) and not now["next"]["hidden"],
|
|
217
|
+
"on slide 0 prev hides itself (hidden=%s), next does not (hidden=%s)"
|
|
218
|
+
% (now["prev"] and now["prev"]["hidden"],
|
|
219
|
+
now["next"] and now["next"]["hidden"]))
|
|
220
|
+
# `isCarousel` does NOT widen the view - every slide is one track wide
|
|
221
|
+
# in both modes. What it does is keep a slide positioned BEFORE the
|
|
222
|
+
# first one, which is the same fact as "it wraps" and as "no arrow
|
|
223
|
+
# ever hides".
|
|
224
|
+
full = (now["trackW"] and now["slideW"]
|
|
225
|
+
and all(abs(w - now["trackW"]) <= 2 for w in now["slideW"]))
|
|
226
|
+
if is_carousel:
|
|
227
|
+
# A slide wrapped round to a NEGATIVE left is the visible sign of
|
|
228
|
+
# wrapping, but it is transient - it is only there while the
|
|
229
|
+
# rotation has carried one past an end, and on slide 0 at rest
|
|
230
|
+
# there is none. The claim that always holds, and the one being
|
|
231
|
+
# corrected, is about WIDTH: a carousel is not a wider view.
|
|
232
|
+
check("CAROUSEL_LAYOUT", bool(full),
|
|
233
|
+
"grid %s, every slide still one track wide (%s vs %s) - "
|
|
234
|
+
"isCarousel wraps the ends, it does not show more than one"
|
|
235
|
+
% (now["trackAreas"], sorted(set(now["slideW"])), now["trackW"]))
|
|
236
|
+
else:
|
|
237
|
+
check("TRACK_LAYOUT", bool(full),
|
|
238
|
+
"grid %s, every slide one track wide (%s vs %s)"
|
|
239
|
+
% (now["trackAreas"], sorted(set(now["slideW"])), now["trackW"]))
|
|
193
240
|
if now["next"]:
|
|
194
241
|
check("ARROW_ARIA",
|
|
195
242
|
now["next"]["role"] == "button" and now["next"]["tabindex"] == "0"
|
|
@@ -244,10 +291,17 @@ def main():
|
|
|
244
291
|
|
|
245
292
|
print("\n%d of %d checks passed" % (len(rows) - len(fails), len(rows)))
|
|
246
293
|
if a.csv:
|
|
294
|
+
label = a.label or a.slider
|
|
295
|
+
existing = []
|
|
296
|
+
if a.append and os.path.exists(a.csv):
|
|
297
|
+
with open(a.csv, newline="", encoding="utf-8") as fh:
|
|
298
|
+
existing = [r for r in csv.reader(fh)][1:]
|
|
299
|
+
existing = [r for r in existing if r and r[0] != label]
|
|
247
300
|
with open(a.csv, "w", newline="", encoding="utf-8") as fh:
|
|
248
301
|
w = csv.writer(fh)
|
|
249
|
-
w.writerow(["check", "result", "detail"])
|
|
250
|
-
w.writerows(
|
|
302
|
+
w.writerow(["slider", "check", "result", "detail"])
|
|
303
|
+
w.writerows(existing)
|
|
304
|
+
w.writerows([[label] + r for r in rows])
|
|
251
305
|
print("wrote %s" % a.csv)
|
|
252
306
|
sys.exit(1 if fails else 0)
|
|
253
307
|
|