mosaic-headless 1.27.0 → 1.28.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 +4 -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/content-loop-verification.csv +11 -0
- package/data/mobile-audit.csv +3 -0
- package/data/navigation-verification.csv +18 -15
- package/package.json +1 -1
- package/references/dynamic-content.md +54 -0
- package/scripts/check-release.mjs +3 -2
- package/sites/_moksa.py +6 -0
- package/sites/moksa.json +1735 -105
- package/tools/build_page.py +13 -0
- package/tools/verify_components.py +60 -0
package/tools/build_page.py
CHANGED
|
@@ -195,6 +195,19 @@ class Surface:
|
|
|
195
195
|
problems.append("%s is a leaf and accepts no children (tried %s)" % (parent_type, t))
|
|
196
196
|
elif rule.get("rule") == "allow":
|
|
197
197
|
allowed = rule["allowed_children"].split("|") + self.default_children.get(parent_type, [])
|
|
198
|
+
# A loop container stands in for its parent's children: a
|
|
199
|
+
# `slider-loop-slides` sits inside `slider-slides` and repeats a
|
|
200
|
+
# `slider-slide`, so what it accepts is what `slider-slides`
|
|
201
|
+
# accepts. The plugin's own canBeParentFor says `slider-slides`
|
|
202
|
+
# here, which would nest the track inside its own loop - and the
|
|
203
|
+
# factory's default children say `slider-slide`, which is the
|
|
204
|
+
# structure the editor builds. Rather than hard-code the
|
|
205
|
+
# disagreement, take the rule from the grandparent, which is both
|
|
206
|
+
# types' actual authority.
|
|
207
|
+
# ancestors[-1] IS the parent; the grandparent is one further up
|
|
208
|
+
if t not in allowed and "-loop-" in parent_type and len(ancestors) > 1:
|
|
209
|
+
gp = self.rules.get(ancestors[-2], {})
|
|
210
|
+
allowed = allowed + (gp.get("allowed_children") or "").split("|")
|
|
198
211
|
if t not in allowed:
|
|
199
212
|
problems.append("%s accepts only %s, not %s" % (parent_type, "/".join(allowed), t))
|
|
200
213
|
# customStyles is the pre-1.0.8 name of customDeclarations; expand_shorthands maps it
|
|
@@ -46,6 +46,13 @@ def main():
|
|
|
46
46
|
ap.add_argument("--faq", default="cp-faq-q-%d/cp-faq-a-%d:4")
|
|
47
47
|
ap.add_argument("--form", default="cp-form")
|
|
48
48
|
ap.add_argument("--map", default="cp-map")
|
|
49
|
+
ap.add_argument("--loop-slider", default="ct-slider",
|
|
50
|
+
help="a slider whose slides come from a loop")
|
|
51
|
+
ap.add_argument("--loop-tabs", default="ct-tabs-menu")
|
|
52
|
+
ap.add_argument("--pagination", default="ct-arch-nums",
|
|
53
|
+
help="a loop-pagination-numbers whose children are page links")
|
|
54
|
+
ap.add_argument("--paged-item", default="ct-arch-title",
|
|
55
|
+
help="the repeated attrID whose text should change per page")
|
|
49
56
|
a = ap.parse_args()
|
|
50
57
|
|
|
51
58
|
rows, fails = [], []
|
|
@@ -158,6 +165,59 @@ def main():
|
|
|
158
165
|
check("FORM/required_blocks", bool(invalid),
|
|
159
166
|
"empty form reports invalid: %s" % invalid[:4])
|
|
160
167
|
|
|
168
|
+
# ---- components whose children come from a loop
|
|
169
|
+
#
|
|
170
|
+
# One authored template, repeated per row. The assertion is COUNT: a
|
|
171
|
+
# template that failed to loop renders exactly once and looks fine.
|
|
172
|
+
if not present(a.loop_slider):
|
|
173
|
+
skip("LOOP/slider", "not on this page")
|
|
174
|
+
else:
|
|
175
|
+
n = p.evaluate("""(id) => document.querySelectorAll(
|
|
176
|
+
'#' + id + ' mosaic-slider-slide').length""", a.loop_slider)
|
|
177
|
+
check("LOOP/slider", n > 1,
|
|
178
|
+
"%d slides from one authored slider-slide" % n)
|
|
179
|
+
if not present(a.loop_tabs):
|
|
180
|
+
skip("LOOP/tabs", "not on this page")
|
|
181
|
+
else:
|
|
182
|
+
n = p.evaluate("""(id) => document.querySelectorAll(
|
|
183
|
+
'#' + id + ' .m-tab').length""", a.loop_tabs)
|
|
184
|
+
labels = p.evaluate("""(id) => [...document.querySelectorAll(
|
|
185
|
+
'#' + id + ' .m-tab')].map(e => e.textContent.trim())""", a.loop_tabs)
|
|
186
|
+
check("LOOP/tabs", n > 1, "%d tabs from one authored tabs-tab" % n)
|
|
187
|
+
# @substr counts BYTES; a cut through a multi-byte character leaves
|
|
188
|
+
# U+FFFD, which is invisible in a count and obvious to a reader
|
|
189
|
+
check("LOOP/tab_labels_intact",
|
|
190
|
+
all("�" not in x for x in labels),
|
|
191
|
+
"no replacement characters - @substr counts bytes, so a length "
|
|
192
|
+
"that is not a multiple of the encoding cuts a glyph in half")
|
|
193
|
+
|
|
194
|
+
# ---- pagination actually pages
|
|
195
|
+
if not present(a.pagination):
|
|
196
|
+
skip("LOOP/pagination", "not on this page")
|
|
197
|
+
else:
|
|
198
|
+
def titles():
|
|
199
|
+
return p.evaluate("""(id) => [...document.querySelectorAll(
|
|
200
|
+
'[id=' + id + ']')].map(e => e.textContent.trim())""",
|
|
201
|
+
a.paged_item)
|
|
202
|
+
links = p.evaluate("""(id) => [...document.getElementById(id).children]
|
|
203
|
+
.map(e => e.getAttribute('href'))""", a.pagination)
|
|
204
|
+
check("LOOP/pagination_links", len(links) > 1 and all(links),
|
|
205
|
+
"%d page links: %s" % (len(links),
|
|
206
|
+
[l.split("?")[-1] for l in links if l][:3]))
|
|
207
|
+
# `p` is one of WordPress's own query vars - ?p=2 is a 301 to post 2 -
|
|
208
|
+
# so a paginationKey of "p" takes the visitor off the page entirely
|
|
209
|
+
bad_key = [l for l in links if l and ("?p=" in l or "&p=" in l)]
|
|
210
|
+
check("LOOP/pagination_key_is_safe", not bad_key,
|
|
211
|
+
"paginationKey does not collide with a WordPress query var"
|
|
212
|
+
if not bad_key else "uses ?p=, which WordPress reads as a post ID")
|
|
213
|
+
before = titles()
|
|
214
|
+
p.click("#%s > *:nth-child(2)" % a.pagination)
|
|
215
|
+
p.wait_for_timeout(2000)
|
|
216
|
+
after = titles()
|
|
217
|
+
check("LOOP/pagination_changes_rows",
|
|
218
|
+
bool(after) and set(after) != set(before),
|
|
219
|
+
"page 1 %s -> page 2 %s" % (len(before), len(after)))
|
|
220
|
+
|
|
161
221
|
# ---- map
|
|
162
222
|
if not present(a.map):
|
|
163
223
|
skip("MAP", "not on this page")
|