mosaic-headless 1.31.0 → 1.31.2
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 +11 -1
- 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/package.json +1 -1
- package/references/custom-fields.md +24 -1
- package/references/dynamic-content.md +44 -0
- package/references/failure-modes.md +11 -0
- package/tools/build_page.py +13 -0
package/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: |
|
|
|
5
5
|
license: "MIT"
|
|
6
6
|
metadata:
|
|
7
7
|
author: "moksa (https://moksaweb.com)"
|
|
8
|
-
version: "1.31.
|
|
8
|
+
version: "1.31.2"
|
|
9
9
|
homepage: "https://github.com/Moksa1123/mosaic-headless"
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -788,6 +788,10 @@ post — `build_all.py` resets first for that reason.
|
|
|
788
788
|
references/dialog-and-triggers.md
|
|
789
789
|
- **`scrollDepth`'s action slot is `reached` since 1.0.10** (plus `returned`). An
|
|
790
790
|
action stored under the old slot name `scrollDepth` is kept and never runs.
|
|
791
|
+
- **A local-context loop's `loopSource` is `@VAR('post/<loop>')`, never `@LOOP()`.**
|
|
792
|
+
Both render on the front end; `@LOOP` there throws in the editor
|
|
793
|
+
(`_loopEvaluatorFunction is not a function`) and the canvas never finishes.
|
|
794
|
+
`build_page.py` refuses it. `@LOOP(…)` inside text is fine.
|
|
791
795
|
- **A custom field is `@VAR('post/meta_<key>')`, and a multi-value one is a LOOP.**
|
|
792
796
|
ACF and Meta Box both, plus bare post meta. Derived properties hang off the
|
|
793
797
|
name with two underscores (`meta_k__label`, `__url`, `__id`); an ACF group is
|
|
@@ -813,6 +817,12 @@ post — `build_all.py` resets first for that reason.
|
|
|
813
817
|
default - it is a `<span>`, a label rather than a control. Measured all three.
|
|
814
818
|
`menu-link` and `wysiwyg-link` still render `<span>` without a `url`.
|
|
815
819
|
- **`text` renders as `<div>` by default** — set `tagName` for `<h1>`, `<p>` and so on.
|
|
820
|
+
- **`youtube` renders `<mosaic-youtube><iframe>` with no `title`** - the iframe gets
|
|
821
|
+
only `loading`, `src`, `allow` and an id, so an accessible name has to be added
|
|
822
|
+
some other way. `youtubeUrl` is `{"v": "https://www.youtube.com/watch?v=…"}`;
|
|
823
|
+
`lazyLoad` adds `loading="lazy"` and there is no click-to-load facade. Sized by
|
|
824
|
+
`width`/`height: 100%` inside an `aspect-ratio: 16/9` box. `privacy` alone still
|
|
825
|
+
emits nothing (NO_OUTPUT).
|
|
816
826
|
- **Eight types emit custom elements**: `<mosaic-dropdown>`, `<mosaic-navbar>`,
|
|
817
827
|
`<mosaic-tabs>`, `<mosaic-accordion>`, `<mosaic-vimeo>`, `<mosaic-youtube>` and
|
|
818
828
|
friends. A selector written against `div`/`nav` misses all of them.
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"description": "Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface (122 node types, 181 node properties, 98 style properties, 53 style states, 151 element classes, 74 dynamic variables, 114 REST routes, 23 tables) instead of guessing, with every node type placed on a live site one at a time and asserted against the delivered HTML, every style and node property swept against the compiled CSS and markup, and 569 responsive declarations checked against the stylesheet the site actually served.",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"author": "moksa (https://moksaweb.com)",
|
|
23
|
-
"version": "1.31.
|
|
23
|
+
"version": "1.31.2"
|
|
24
24
|
},
|
|
25
25
|
"loaderBehaviour": "Upload via Settings -> Skills -> Upload. Claude.ai parses SKILL.md frontmatter and surfaces the skill in your library. The extraction tool (extract-block-schema.php) needs a live WP-CLI connection and won't run in the sandbox; use it from a local terminal against your own site instead.",
|
|
26
26
|
"uploadSteps": [
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"description": "Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface (122 node types, 181 node properties, 98 style properties, 53 style states, 151 element classes, 74 dynamic variables, 114 REST routes, 23 tables) instead of guessing, with every node type placed on a live site one at a time and asserted against the delivered HTML, every style and node property swept against the compiled CSS and markup, and 569 responsive declarations checked against the stylesheet the site actually served.",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"author": "moksa (https://moksaweb.com)",
|
|
23
|
-
"version": "1.31.
|
|
23
|
+
"version": "1.31.2"
|
|
24
24
|
},
|
|
25
25
|
"loaderBehaviour": "Auto-loads on session start when SKILL.md frontmatter parses successfully.",
|
|
26
26
|
"verified": true,
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"description": "Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface (122 node types, 181 node properties, 98 style properties, 53 style states, 151 element classes, 74 dynamic variables, 114 REST routes, 23 tables) instead of guessing, with every node type placed on a live site one at a time and asserted against the delivered HTML, every style and node property swept against the compiled CSS and markup, and 569 responsive declarations checked against the stylesheet the site actually served.",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"author": "moksa (https://moksaweb.com)",
|
|
23
|
-
"version": "1.31.
|
|
23
|
+
"version": "1.31.2"
|
|
24
24
|
},
|
|
25
25
|
"loaderBehaviour": "Confirmed (2026-07-11): Codex CLI natively supports the SKILL.md spec. Place SKILL.md under .codex/skills/<name>/ (project) or ~/.codex/skills/<name>/ (personal) and Codex loads the name+description at session start, then the full body on demand. A parallel, broader convention .agents/skills/ (searched from cwd up to repo root, then ~/.agents/skills/) also exists across multiple tools - if your Codex CLI version prioritizes that path instead, mirror the same SKILL.md there.",
|
|
26
26
|
"verified": true,
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"description": "Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface (122 node types, 181 node properties, 98 style properties, 53 style states, 151 element classes, 74 dynamic variables, 114 REST routes, 23 tables) instead of guessing, with every node type placed on a live site one at a time and asserted against the delivered HTML, every style and node property swept against the compiled CSS and markup, and 569 responsive declarations checked against the stylesheet the site actually served.",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"author": "moksa (https://moksaweb.com)",
|
|
23
|
-
"version": "1.31.
|
|
23
|
+
"version": "1.31.2"
|
|
24
24
|
},
|
|
25
25
|
"loaderBehaviour": "CHANGED as of 2026-07-11: GitHub Copilot added a proper '.github/skills/' Agent Skills directory (December 2025), alongside the older single-file .github/copilot-instructions.md convention. This config targets the new skills-directory form. If your Copilot version predates this (pre Dec 2025), use the instructions-append fallback instead (see fallback below).",
|
|
26
26
|
"fallback": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"description": "Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface (122 node types, 181 node properties, 98 style properties, 53 style states, 151 element classes, 74 dynamic variables, 114 REST routes, 23 tables) instead of guessing, with every node type placed on a live site one at a time and asserted against the delivered HTML, every style and node property swept against the compiled CSS and markup, and 569 responsive declarations checked against the stylesheet the site actually served.",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"author": "moksa (https://moksaweb.com)",
|
|
23
|
-
"version": "1.31.
|
|
23
|
+
"version": "1.31.2"
|
|
24
24
|
},
|
|
25
25
|
"loaderBehaviour": "CHANGED as of 2026-07-11: Gemini CLI now natively supports the same SKILL.md standard as Claude Code and Codex CLI - the same directory-based skill works unmodified. Gemini CLI discovers skills in this precedence order: built-in, extension skills, ~/.gemini/skills/ (personal), .gemini/skills/ (project, shared via version control). At session start Gemini injects each discovered skill's name+description into the system prompt and calls activate_skill when a task matches.",
|
|
26
26
|
"verified": true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mosaic-headless",
|
|
3
|
-
"version": "1.31.
|
|
3
|
+
"version": "1.31.2",
|
|
4
4
|
"description": "AI-agent skill: build Mosaic Pro (Nextend) WordPress sites by writing the underlying data model directly - 23 custom tables, no visual editor, no DOM. Every node type, style property and node property swept against a live install and asserted on the delivered HTML and compiled CSS. Installs into Claude Code, Cursor, Codex CLI, Gemini CLI, Copilot, Continue, Windsurf and Claude.ai.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wordpress",
|
|
@@ -81,13 +81,36 @@ the source on the loop node:
|
|
|
81
81
|
```json
|
|
82
82
|
{"type": "loop",
|
|
83
83
|
"data": {"loopType": "localContext",
|
|
84
|
-
"localContextOptions": {"loopSource": {"v": "@
|
|
84
|
+
"localContextOptions": {"loopSource": {"v": "@VAR('post/loopacf_rel')"}}},
|
|
85
85
|
"children": [{"type": "loop-items", "children": [{"type": "loop-item", "children": [
|
|
86
86
|
{"type": "text", "data": {"tagName": "p"},
|
|
87
87
|
"children": [{"type": "wysiwyg-variable", "data": {"dynamicCode": "@VAR('item/value__label')"}}]}
|
|
88
88
|
]}]}]}
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
**The `loopSource` is `@VAR('<namespace>/<loop>')`, never `@LOOP(…)`.** This page
|
|
92
|
+
used to show `@LOOP('post/loopacf_rel')` here, and that form breaks the editor while
|
|
93
|
+
the front end renders it fine - the worst kind of split, because the page looks done:
|
|
94
|
+
|
|
95
|
+
- the front end (`LoopEvaluator.php`) takes the first string argument of whatever
|
|
96
|
+
function call it is given and ignores the function's name, so `@LOOP('post/x')`
|
|
97
|
+
and `@VAR('post/x')` resolve to the same loop;
|
|
98
|
+
- the editor (`evaluateLoop` in `AppMosaicEditor-bundle.js`, 1.0.10) builds its
|
|
99
|
+
expression evaluator with the loop lookup as the VARIABLE evaluator and `null` as
|
|
100
|
+
the loop evaluator, so `@VAR` resolves the loop and `@LOOP` calls `null`:
|
|
101
|
+
`TypeError: this._loopEvaluatorFunction is not a function`, the canvas renders
|
|
102
|
+
for a long time and never finishes. Mosaic's own callers write
|
|
103
|
+
`evaluateLoop("@VAR('<menu>/loop')")`.
|
|
104
|
+
|
|
105
|
+
Reported from a client build (a Meta Box `image_advanced` gallery as the source):
|
|
106
|
+
changing the source to `@VAR('post/loopwork_gallery')` cleared the editor error and
|
|
107
|
+
the front end still rendered all 35 images. `@LOOP(…)` *inside text* goes through a
|
|
108
|
+
different evaluator that is given both functions, and is fine in the editor too -
|
|
109
|
+
measured on the same build: a loop item's image `alt` of
|
|
110
|
+
`@concat(@VAR('post/title'), '|', @LOOP('post/work_category', 1, 'term_name'), '作品照片 ', @VAR('item/index'))`
|
|
111
|
+
opens with zero console errors and delivers `墨竹|住宅設計作品照片 1`. The table
|
|
112
|
+
above stays valid there.
|
|
113
|
+
|
|
91
114
|
`loopType:"localContext"` is the built-in source that evaluates an expression in the
|
|
92
115
|
page's context; the sweep's `loop` COMMIT_500 is what happens without it. The loop
|
|
93
116
|
rendered exactly the rows the field holds for every family above (2, 2, 1, 1, 2, 2,
|
|
@@ -66,6 +66,14 @@ Two different shapes for the same language, and which one applies is decided by
|
|
|
66
66
|
property's validator chain in `data/node-properties.csv`: **`ValidatorDynamicCode`
|
|
67
67
|
means a bare string, `ValidatorDynamicCodeObject` means `{"v": "…"}`**.
|
|
68
68
|
|
|
69
|
+
Getting it wrong on a URL is silent and looks like a broken link rather than a broken
|
|
70
|
+
expression. `menu-link`'s `url` chain is `ValidatorDynamicCodeObject → ValidatorURL →
|
|
71
|
+
ValidatorUrlObject`; given a bare `"@concat('/contact/?ref=', @VAR('post/title'), '#consult')"`
|
|
72
|
+
it commits without complaint and delivers
|
|
73
|
+
`href="http://concat('/contact/?ref=%27…"` - the leading `@` dropped and the rest taken
|
|
74
|
+
as a URL (measured on a client build on 1.0.10). As `{"v": "@concat(…)"}` it delivers the evaluated link.
|
|
75
|
+
|
|
76
|
+
|
|
69
77
|
## The variable namespace
|
|
70
78
|
|
|
71
79
|
`data/dynamic-variables.csv` — 74 variables across 11 namespaces, each row carrying
|
|
@@ -140,6 +148,42 @@ one. `build_page.py` resolves it by treating a loop container as standing in for
|
|
|
140
148
|
its parent's children: what `slider-loop-slides` accepts is what `slider-slides`
|
|
141
149
|
accepts.
|
|
142
150
|
|
|
151
|
+
## Filtering and ordering a post loop
|
|
152
|
+
|
|
153
|
+
A `wpPostType<Type>` loop takes `filters` (the condition structure of
|
|
154
|
+
`references/templates-and-conditions.md`: units in one `evaluationUnits` group are
|
|
155
|
+
AND-ed, groups are OR-ed) and `orderByClauses`. The filters are evaluated in the
|
|
156
|
+
context the loop sits in, so inside a single-post template `@VAR('post/…')` is the
|
|
157
|
+
post being viewed. Subject IDs are built from WordPress names, camel-cased after
|
|
158
|
+
every `-`, `_` or space (source: `FilterSubjectPostTypes.php`):
|
|
159
|
+
|
|
160
|
+
| subject | id | operators | value |
|
|
161
|
+
|---|---|---|---|
|
|
162
|
+
| the posts themselves | `postType<Type>` (`postTypeWork`) | `includes` / `excludes` | a list of post ids; dynamic |
|
|
163
|
+
| a taxonomy | `taxonomy<Taxonomy>` (`taxonomyWorkCategory`) | `includes` / `excludes` | term ids; dynamic |
|
|
164
|
+
| a meta key | `postType<Type>PostMeta`, `settings.key` | the comparator you pick: `text`, `number`, `date` | per comparator |
|
|
165
|
+
|
|
166
|
+
A dynamic filter value is `{"v": …}` - the field declares `SupportDynamic`, which the
|
|
167
|
+
reader validates with `ValidatorDynamicCodeObject`. "More work in the same category,
|
|
168
|
+
not this one", measured on a client build on 1.0.10:
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{"uuid": "<uuid>", "type": "filter", "filterOptions": {"subject": {
|
|
172
|
+
"type": "postTypeWork", "postTypeWorkOptions": {"settings": {}, "comparator": {
|
|
173
|
+
"type": "postTypeWork", "postTypeWorkOptions": {
|
|
174
|
+
"operator": "excludes", "settings": {"value": {"v": "@VAR('post/id')"}}}}}}}}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
with a second unit in the same group on `taxonomyWorkCategory`, operator `includes`,
|
|
178
|
+
value `{"v": "@LOOP('post/work_category', 1, 'term_id')"}`. A Meta Box checkbox
|
|
179
|
+
(stored `"1"`) filters as subject `postTypeWorkPostMeta` with
|
|
180
|
+
`settings: {"key": "work_featured"}` and a `text` comparator
|
|
181
|
+
`{"operator": "equals", "settings": {"value": "1"}}` - six posts back, in order.
|
|
182
|
+
|
|
183
|
+
`orderByClauses` accepts `random`: `{"type": "random", "uuid": "<uuid>",
|
|
184
|
+
"randomOptions": {"settings": {}}}`, a different order on every uncached render. Under
|
|
185
|
+
a page cache that is one order per cache lifetime, not per visitor.
|
|
186
|
+
|
|
143
187
|
## `loop-pagination`, and the key you must not use
|
|
144
188
|
|
|
145
189
|
```
|
|
@@ -476,3 +476,14 @@ rendered - server-side output was fine - but with no front-end runtime every dro
|
|
|
476
476
|
sat open and no modal could open. `find mosaic -type d -exec chmod 775 {} +` (files
|
|
477
477
|
664), matching the other plugin folders, and purge the page cache. A REST-level check
|
|
478
478
|
cannot see this; only fetching an asset can.
|
|
479
|
+
|
|
480
|
+
## A loop source written as `@LOOP()` renders on the front end and kills the editor
|
|
481
|
+
|
|
482
|
+
`loopType: "localContext"` with `loopSource: {"v": "@LOOP('post/<loop>')"}` renders
|
|
483
|
+
every row on the delivered page, and opening the page in the Mosaic editor throws
|
|
484
|
+
`TypeError: this._loopEvaluatorFunction is not a function` from
|
|
485
|
+
`evaluateLoop` / `createLoopContextFactory`, leaving the canvas loading. The front
|
|
486
|
+
end reads the loop name out of any function call; the editor only out of `@VAR`.
|
|
487
|
+
Write `@VAR('post/<loop>')`. No server-side check can see this - the page is 200 and
|
|
488
|
+
complete - so it is only caught by opening the editor. Details in
|
|
489
|
+
`references/custom-fields.md`.
|
package/tools/build_page.py
CHANGED
|
@@ -131,6 +131,19 @@ class Surface:
|
|
|
131
131
|
problems.append("code content contains `%s` - Mosaic's template parser reads "
|
|
132
132
|
"it as a function call and the page renders as HTTP 500; "
|
|
133
133
|
"write `%s (`" % (hit.group(0), hit.group(0)[:-1]))
|
|
134
|
+
# A local-context loop's source written as `@LOOP('post/x')` renders on the
|
|
135
|
+
# front end - LoopEvaluator.php reads the loop name out of any function call -
|
|
136
|
+
# and throws in the editor, whose evaluateLoop only resolves `@VAR`
|
|
137
|
+
# ("this._loopEvaluatorFunction is not a function", canvas never finishes).
|
|
138
|
+
# The page is 200 and complete, so nothing downstream of this can see it.
|
|
139
|
+
if not force:
|
|
140
|
+
opts = (node.get("data") or {}).get("localContextOptions") or {}
|
|
141
|
+
src = opts.get("loopSource")
|
|
142
|
+
code = src.get("v") if isinstance(src, dict) else src
|
|
143
|
+
if isinstance(code, str) and code.lstrip().upper().startswith("@LOOP("):
|
|
144
|
+
problems.append("loopSource %r breaks the Mosaic editor (front end renders, "
|
|
145
|
+
"editor throws _loopEvaluatorFunction is not a function); "
|
|
146
|
+
"write it as %r" % (code, "@VAR(" + code.lstrip()[6:]))
|
|
134
147
|
# node-verification.csv measured every type UNDER A PLAIN DIV. A type that broke
|
|
135
148
|
# there is not broken in general - most of them are family members that simply
|
|
136
149
|
# need their own parent. So the measured verdict only applies when the parent is
|