@wootsup/yt-builder-mcp 1.11.0 → 1.12.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.md +10 -10
- package/bin/yt-builder-mcp.js +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.js +1 -1
- package/dist/catalog/tool-catalog-markdown.d.ts.map +1 -1
- package/dist/catalog/tool-catalog-markdown.js +13 -1
- package/dist/catalog/tool-catalog-markdown.js.map +1 -1
- package/dist/cli/doctor-command.d.ts.map +1 -1
- package/dist/cli/doctor-command.js +22 -2
- package/dist/cli/doctor-command.js.map +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/clients/index.d.ts +1 -1
- package/dist/clients/index.js +1 -1
- package/dist/diagnostics/ca-reexec.js +2 -2
- package/dist/diagnostics/diagnose-network-error.d.ts +1 -1
- package/dist/diagnostics/doctor.d.ts +34 -2
- package/dist/diagnostics/doctor.d.ts.map +1 -1
- package/dist/diagnostics/doctor.js +63 -2
- package/dist/diagnostics/doctor.js.map +1 -1
- package/dist/diagnostics/startup-self-probe.js +1 -1
- package/dist/gateway/advanced-read-tool.js +1 -1
- package/dist/gateway/advanced-read-tool.js.map +1 -1
- package/dist/gateway/advanced-tool/discovery.d.ts.map +1 -1
- package/dist/gateway/advanced-tool/discovery.js +12 -1
- package/dist/gateway/advanced-tool/discovery.js.map +1 -1
- package/dist/gateway/essentials.d.ts +4 -4
- package/dist/gateway/essentials.d.ts.map +1 -1
- package/dist/gateway/essentials.js +12 -7
- package/dist/gateway/essentials.js.map +1 -1
- package/dist/install-skill.js +1 -1
- package/dist/net/extra-headers.d.ts +1 -1
- package/dist/net/extra-headers.js +1 -1
- package/dist/net/internal-host.js +2 -2
- package/dist/net/proxy-dispatcher.js +1 -1
- package/dist/net/proxy-dispatcher.js.map +1 -1
- package/dist/net/site-basic-auth.d.ts +2 -2
- package/dist/net/site-basic-auth.js +2 -2
- package/dist/proxy/bridge.d.ts +2 -2
- package/dist/proxy/bridge.js +3 -3
- package/dist/proxy/bridge.js.map +1 -1
- package/dist/setup-cli.d.ts +2 -2
- package/dist/setup-cli.js +2 -2
- package/dist/setup-cli.js.map +1 -1
- package/dist/setup-npx-spec.d.ts +1 -1
- package/dist/setup-npx-spec.js +1 -1
- package/dist/setup-wizard-handshake.js +1 -1
- package/dist/setup-wizard-types.d.ts +1 -1
- package/dist/setup-wizard.js +3 -3
- package/dist/setup-wizard.js.map +1 -1
- package/dist/sites/client-pool.d.ts +1 -1
- package/dist/sites/client-pool.js +1 -1
- package/dist/sites/tools/sites-list.d.ts.map +1 -1
- package/dist/sites/tools/sites-list.js +4 -2
- package/dist/sites/tools/sites-list.js.map +1 -1
- package/dist/sites/tools/sites-test.js +1 -1
- package/dist/sites/tools/sites-test.js.map +1 -1
- package/dist/sites/tools/use-site.js +2 -2
- package/dist/sites/tools/use-site.js.map +1 -1
- package/dist/tools/budgeted-table.d.ts +21 -0
- package/dist/tools/budgeted-table.d.ts.map +1 -0
- package/dist/tools/budgeted-table.js +137 -0
- package/dist/tools/budgeted-table.js.map +1 -0
- package/dist/tools/elements/builders.d.ts.map +1 -1
- package/dist/tools/elements/builders.js +48 -24
- package/dist/tools/elements/builders.js.map +1 -1
- package/dist/tools/elements/handlers-write.d.ts +19 -0
- package/dist/tools/elements/handlers-write.d.ts.map +1 -1
- package/dist/tools/elements/handlers-write.js +100 -3
- package/dist/tools/elements/handlers-write.js.map +1 -1
- package/dist/tools/elements/handlers.d.ts +13 -8
- package/dist/tools/elements/handlers.d.ts.map +1 -1
- package/dist/tools/elements/handlers.js +28 -18
- package/dist/tools/elements/handlers.js.map +1 -1
- package/dist/tools/elements/schema-validation.d.ts +129 -7
- package/dist/tools/elements/schema-validation.d.ts.map +1 -1
- package/dist/tools/elements/schema-validation.js +459 -23
- package/dist/tools/elements/schema-validation.js.map +1 -1
- package/dist/tools/format/elements-format.d.ts.map +1 -1
- package/dist/tools/format/elements-format.js +56 -12
- package/dist/tools/format/elements-format.js.map +1 -1
- package/dist/tools/format/health-format.d.ts +16 -0
- package/dist/tools/format/health-format.d.ts.map +1 -1
- package/dist/tools/format/health-format.js +9 -0
- package/dist/tools/format/health-format.js.map +1 -1
- package/dist/tools/format/inspection-format.d.ts +7 -0
- package/dist/tools/format/inspection-format.d.ts.map +1 -1
- package/dist/tools/format/inspection-format.js +18 -0
- package/dist/tools/format/inspection-format.js.map +1 -1
- package/dist/tools/format/pages-format.d.ts +1 -1
- package/dist/tools/format/pages-format.d.ts.map +1 -1
- package/dist/tools/format/pages-format.js +7 -0
- package/dist/tools/format/pages-format.js.map +1 -1
- package/dist/tools/health.d.ts.map +1 -1
- package/dist/tools/health.js +67 -6
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +7 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inspection.d.ts +34 -0
- package/dist/tools/inspection.d.ts.map +1 -1
- package/dist/tools/inspection.js +600 -65
- package/dist/tools/inspection.js.map +1 -1
- package/dist/tools/library.d.ts +6 -2
- package/dist/tools/library.d.ts.map +1 -1
- package/dist/tools/library.js +36 -21
- package/dist/tools/library.js.map +1 -1
- package/dist/tools/local-content/builders.js +4 -4
- package/dist/tools/local-content/builders.js.map +1 -1
- package/dist/tools/local-content/handlers.d.ts +3 -1
- package/dist/tools/local-content/handlers.d.ts.map +1 -1
- package/dist/tools/local-content/handlers.js +8 -4
- package/dist/tools/local-content/handlers.js.map +1 -1
- package/dist/tools/multi-items/builders.d.ts.map +1 -1
- package/dist/tools/multi-items/builders.js +2 -4
- package/dist/tools/multi-items/builders.js.map +1 -1
- package/dist/tools/navigation/builders.d.ts +18 -0
- package/dist/tools/navigation/builders.d.ts.map +1 -0
- package/dist/tools/navigation/builders.js +47 -0
- package/dist/tools/navigation/builders.js.map +1 -0
- package/dist/tools/navigation/handlers.d.ts +30 -0
- package/dist/tools/navigation/handlers.d.ts.map +1 -0
- package/dist/tools/navigation/handlers.js +41 -0
- package/dist/tools/navigation/handlers.js.map +1 -0
- package/dist/tools/navigation/index.d.ts +15 -0
- package/dist/tools/navigation/index.d.ts.map +1 -0
- package/dist/tools/navigation/index.js +14 -0
- package/dist/tools/navigation/index.js.map +1 -0
- package/dist/tools/navigation/schemas.d.ts +46 -0
- package/dist/tools/navigation/schemas.d.ts.map +1 -0
- package/dist/tools/navigation/schemas.js +41 -0
- package/dist/tools/navigation/schemas.js.map +1 -0
- package/dist/tools/pages/builders.d.ts.map +1 -1
- package/dist/tools/pages/builders.js +148 -49
- package/dist/tools/pages/builders.js.map +1 -1
- package/dist/tools/pages/handlers-audit.d.ts +14 -3
- package/dist/tools/pages/handlers-audit.d.ts.map +1 -1
- package/dist/tools/pages/handlers-audit.js +11 -1
- package/dist/tools/pages/handlers-audit.js.map +1 -1
- package/dist/tools/pages/handlers-read.d.ts +43 -5
- package/dist/tools/pages/handlers-read.d.ts.map +1 -1
- package/dist/tools/pages/handlers-read.js +176 -44
- package/dist/tools/pages/handlers-read.js.map +1 -1
- package/dist/tools/pages/handlers-write.d.ts +2 -2
- package/dist/tools/pages/handlers-write.d.ts.map +1 -1
- package/dist/tools/pages/handlers-write.js +3 -2
- package/dist/tools/pages/handlers-write.js.map +1 -1
- package/dist/tools/pages/header-transparency.d.ts +64 -0
- package/dist/tools/pages/header-transparency.d.ts.map +1 -0
- package/dist/tools/pages/header-transparency.js +119 -0
- package/dist/tools/pages/header-transparency.js.map +1 -0
- package/dist/tools/pages/schemas.d.ts +95 -9
- package/dist/tools/pages/schemas.d.ts.map +1 -1
- package/dist/tools/pages/schemas.js +201 -19
- package/dist/tools/pages/schemas.js.map +1 -1
- package/dist/tools/response-budget.d.ts +77 -0
- package/dist/tools/response-budget.d.ts.map +1 -0
- package/dist/tools/response-budget.js +134 -0
- package/dist/tools/response-budget.js.map +1 -0
- package/dist/tools/shared-schemas.d.ts +23 -0
- package/dist/tools/shared-schemas.d.ts.map +1 -1
- package/dist/tools/shared-schemas.js +28 -0
- package/dist/tools/shared-schemas.js.map +1 -1
- package/dist/tools/sources/builders.d.ts.map +1 -1
- package/dist/tools/sources/builders.js +13 -15
- package/dist/tools/sources/builders.js.map +1 -1
- package/dist/tools/sources/handlers-bind.d.ts +3 -3
- package/dist/tools/sources/handlers-bind.d.ts.map +1 -1
- package/dist/tools/sources/handlers-bind.js +4 -2
- package/dist/tools/sources/handlers-bind.js.map +1 -1
- package/dist/tools/sources/handlers.d.ts +4 -2
- package/dist/tools/sources/handlers.d.ts.map +1 -1
- package/dist/tools/sources/handlers.js +11 -7
- package/dist/tools/sources/handlers.js.map +1 -1
- package/dist/tools/sparse-fields.d.ts +49 -8
- package/dist/tools/sparse-fields.d.ts.map +1 -1
- package/dist/tools/sparse-fields.js +90 -10
- package/dist/tools/sparse-fields.js.map +1 -1
- package/dist/tools/sublayout/builders.d.ts.map +1 -1
- package/dist/tools/sublayout/builders.js +1 -2
- package/dist/tools/sublayout/builders.js.map +1 -1
- package/dist/tools/tool-builder/results.d.ts +19 -6
- package/dist/tools/tool-builder/results.d.ts.map +1 -1
- package/dist/tools/tool-builder/results.js +19 -6
- package/dist/tools/tool-builder/results.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +2 -2
- package/skills/yt-builder-mcp/SKILL.md +1202 -83
|
@@ -12,15 +12,15 @@ description: Drive the YOOtheme Pro Page Builder on WordPress or Joomla 5/6. Dis
|
|
|
12
12
|
> WordPress and Joomla 5/6.
|
|
13
13
|
|
|
14
14
|
This skill helps AI assistants drive the YOOtheme Pro Page Builder through the
|
|
15
|
-
`@wootsup/yt-builder-mcp` server. The server catalogues
|
|
16
|
-
idempotent tools (
|
|
15
|
+
`@wootsup/yt-builder-mcp` server. The server catalogues 53 typed, scoped,
|
|
16
|
+
idempotent tools (55 callable end-to-end with the two gateways). 42 of them
|
|
17
17
|
advertised as first-class entries in `tools/list`
|
|
18
|
-
(
|
|
18
|
+
(38 essential L1 forwards + 2 direct L3 entries + 2 gateways), and 13 advanced
|
|
19
19
|
tools reachable through the gateways. The write-capable
|
|
20
|
-
`yootheme_builder_advanced` gateway routes all
|
|
20
|
+
`yootheme_builder_advanced` gateway routes all 13; the read-only
|
|
21
21
|
`yootheme_builder_advanced_read` gateway routes only the read-only subset
|
|
22
22
|
(page_get_schema, inspect_sublayout, layout_query, local_content_types,
|
|
23
|
-
local_content_read, pages_audit) and is itself
|
|
23
|
+
local_content_read, pages_audit, heading_outline, navigation) and is itself
|
|
24
24
|
read-only-annotated, so MCP hosts can auto-allow pure inspection without a
|
|
25
25
|
per-call write-approval prompt. This 3-lane split keeps `tools/list` well below
|
|
26
26
|
the ~40-tool Cursor cap while every catalogued tool stays fully reachable.
|
|
@@ -54,8 +54,8 @@ Some clients (notably Claude Desktop with the `.mcpb` bundle) expose **two**
|
|
|
54
54
|
entries when the user types "YT Builder MCP" into the picker:
|
|
55
55
|
|
|
56
56
|
- **`YT Builder MCP for YOOtheme Pro (unofficial)`**: the MCP **server**.
|
|
57
|
-
Provides the
|
|
58
|
-
The `yootheme_builder_advanced` gateway routes all
|
|
57
|
+
Provides the 42 first-class tools (38 essential, 2 direct, 2 gateways).
|
|
58
|
+
The `yootheme_builder_advanced` gateway routes all 13 advanced tools; the
|
|
59
59
|
read-only `yootheme_builder_advanced_read` gateway routes the read-only
|
|
60
60
|
subset without a write-approval prompt.
|
|
61
61
|
- **`Von YT Builder MCP for YOOtheme Pro`**: the bundled **skill** (this
|
|
@@ -82,9 +82,9 @@ The server exposes:
|
|
|
82
82
|
- **2 direct top-level tools**, always callable, always in `tools/list`:
|
|
83
83
|
`yootheme_builder_health` and `yootheme_builder_diagnose`. These are
|
|
84
84
|
the "the gateway itself might be broken" escape hatch.
|
|
85
|
-
- **
|
|
85
|
+
- **38 essential forwarded tools**: common reads + the most-used writes
|
|
86
86
|
(pages_list, pages_resolve, pages_create, pages_delete, page_dynamize, scaffold_section,
|
|
87
|
-
get_etag, element_list / add / update_settings / get / move / clone / delete,
|
|
87
|
+
get_etag, element_list / add / update_settings / set_status / get / move / clone / delete,
|
|
88
88
|
page_get_layout, sources_list, source_get_schema, element_types_list, element_type_get_schema,
|
|
89
89
|
template_summary, inspect_multi_items_binding, library_browse, library_get, library_insert,
|
|
90
90
|
image_search, image_import,
|
|
@@ -99,7 +99,7 @@ The server exposes:
|
|
|
99
99
|
binding entry-point — introspect a source's query methods + relations +
|
|
100
100
|
the current-term archive-context convention before binding — is a direct
|
|
101
101
|
call too. `render_preview`, `element_set_source`, `rename_source` and
|
|
102
|
-
`template_set_type` (
|
|
102
|
+
`template_set_type` (customer HANDOFF-4/5) are L1 so the core write/refactor +
|
|
103
103
|
render-verify tools an agent reaches for after a taxonomy/CPT rename — set a
|
|
104
104
|
precomposed source at a pointer, render-verify the saved page is not silently
|
|
105
105
|
blank, rename a source/field template-wide, re-type an orphaned archive/single
|
|
@@ -125,11 +125,14 @@ The server exposes:
|
|
|
125
125
|
step that turns a placeholder-filled skeleton into a finished-looking
|
|
126
126
|
page (see "Fill image slots with real on-topic photos" under Layout
|
|
127
127
|
Library).
|
|
128
|
-
- **
|
|
128
|
+
- **13 advanced captured tools**: everything else (page_get_schema,
|
|
129
129
|
element_unbind_source, clean_implode_directives,
|
|
130
130
|
`inspect_sublayout`, `layout_query`, `library_save_as`, the two Local
|
|
131
131
|
Content Read tools `local_content_types` / `local_content_read`,
|
|
132
|
-
`pages_audit`,
|
|
132
|
+
`pages_audit`, `library_delete` — the INVERSE of `library_save_as` —
|
|
133
|
+
`heading_outline`, a real doc-order H1-H6 outline of one page,
|
|
134
|
+
`navigation`, the site's menus as a parent -> children tree, and
|
|
135
|
+
`header_transparency_set`, the transparent-header inspect/repair).
|
|
133
136
|
Reachable through one gateway tool:
|
|
134
137
|
`yootheme_builder_advanced({ tool: "<name>", arguments: { ... } })`. For
|
|
135
138
|
example, to inspect a panel/grid_item `< SUBLAYOUT`:
|
|
@@ -156,8 +159,31 @@ The server exposes:
|
|
|
156
159
|
`yootheme_builder_advanced_read({ tool: "yootheme_builder_pages_audit", arguments: { checks: ["seo", "structure"] } })`
|
|
157
160
|
(the write gateway routes it too, if you prefer one entry point:
|
|
158
161
|
`yootheme_builder_advanced({ tool: "yootheme_builder_pages_audit" })`).
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
And to get a real, doc-order H1-H6 outline of ONE page (each heading with
|
|
163
|
+
its source element and TEXT, plus the alt-status of every image) —
|
|
164
|
+
read-only STRUCTURE inspection, not findings:
|
|
165
|
+
`yootheme_builder_advanced_read({ tool: "yootheme_builder_heading_outline", arguments: { template_id } })`
|
|
166
|
+
(or via the write gateway: `yootheme_builder_advanced({ tool: "yootheme_builder_heading_outline" })`).
|
|
167
|
+
And to see the site's Haupt- + Sub-Menüstruktur as a parent -> children
|
|
168
|
+
TREE (not a flat list) — for "which page sits under which menu item"
|
|
169
|
+
(product pages usually live in the main menu), each item carrying a
|
|
170
|
+
`page_role` facet (home/landing/product/legal/thankyou/blog/mockup/
|
|
171
|
+
template/generic, the SAME vocabulary `pages_list` uses) and, when
|
|
172
|
+
derivable at zero extra cost, a `target_page` (`page:<id>` /
|
|
173
|
+
`article:<id>`) ready for `page_get_layout`:
|
|
174
|
+
`yootheme_builder_advanced_read({ tool: "yootheme_builder_navigation" })`
|
|
175
|
+
(or via the write gateway: `yootheme_builder_advanced({ tool: "yootheme_builder_navigation" })`).
|
|
176
|
+
Pass `menu: "Footer"` to narrow to one menu by name.
|
|
177
|
+
And to inspect or repair the transparent header — the navigation pulled over
|
|
178
|
+
the first section — a WRITE, so the write gateway only. `dry_run` defaults to
|
|
179
|
+
TRUE, so this call reports the current state, the theme-declared
|
|
180
|
+
`--uk-inverse`, `risk_class`, `confidence` and `would_change` without
|
|
181
|
+
touching anything:
|
|
182
|
+
`yootheme_builder_advanced({ tool: "yootheme_builder_header_transparency_set", arguments: { template_id } })`
|
|
183
|
+
Repeat with `mode: "auto", dry_run: false, etag` to write the theme-declared
|
|
184
|
+
colour. See "Transparent header (the exception — never a default)".
|
|
185
|
+
|
|
186
|
+
The core write/refactor + render-verify tools the customer HANDOFF-4/5 ask for
|
|
161
187
|
are now **L1 — call them directly by name**, not through a gateway:
|
|
162
188
|
- To render-check a saved page over the authenticated channel (F5+N5 — does
|
|
163
189
|
it render, with how many items, or is it silently blank? where an external
|
|
@@ -198,7 +224,7 @@ The server exposes:
|
|
|
198
224
|
into delete/unbind, so hosts gate it) and `yootheme_builder_advanced_read`
|
|
199
225
|
(read-only; routes ONLY the read-only advanced subset — page_get_schema,
|
|
200
226
|
inspect_sublayout, layout_query, local_content_types, local_content_read,
|
|
201
|
-
pages_audit —
|
|
227
|
+
pages_audit, heading_outline, navigation —
|
|
202
228
|
and is read-only-annotated so hosts auto-allow it). Prefer the read gateway
|
|
203
229
|
for pure inspection:
|
|
204
230
|
`yootheme_builder_advanced_read({ tool: "yootheme_builder_page_get_schema", arguments: { template_id } })`.
|
|
@@ -209,9 +235,9 @@ The server exposes:
|
|
|
209
235
|
`yootheme_builder_advanced({ tool: "yootheme_builder_local_content_types" })` and
|
|
210
236
|
`yootheme_builder_advanced({ tool: "yootheme_builder_local_content_read", arguments: { kind: "entities", type: "post" } })`.)
|
|
211
237
|
|
|
212
|
-
`tools/list` therefore advertises
|
|
213
|
-
essentials + 2 L3 direct + 2 gateways. The total callable surface is
|
|
214
|
-
(
|
|
238
|
+
`tools/list` therefore advertises 42 names (38 + 2 + 2). That's 38 L1
|
|
239
|
+
essentials + 2 L3 direct + 2 gateways. The total callable surface is 55
|
|
240
|
+
(42 advertised + 13 advanced reachable through the gateways). If the AI
|
|
215
241
|
client reports "tool not found", you are almost certainly calling an
|
|
216
242
|
advanced tool by its raw name. Wrap it in
|
|
217
243
|
`yootheme_builder_advanced({ tool, arguments })` (or the read gateway for
|
|
@@ -233,6 +259,23 @@ guess where the agent is pointing:
|
|
|
233
259
|
template"): find the matching row, return `frontend_url` (resolved) or
|
|
234
260
|
`frontend_url_template` (with placeholders the user fills in).
|
|
235
261
|
|
|
262
|
+
### `available_endpoints` is request-ready, on both platforms
|
|
263
|
+
|
|
264
|
+
`yootheme_builder_health` also returns `available_endpoints`: every REST path the
|
|
265
|
+
host plugin actually serves. On **both** WordPress and Joomla this list is
|
|
266
|
+
derived from the **live route registration** — WordPress enumerates
|
|
267
|
+
`rest_get_server()->get_routes()`, Joomla reads the ApiRouter that matched the
|
|
268
|
+
request — so it can never drift from what the server accepts.
|
|
269
|
+
|
|
270
|
+
Use it as-is when documenting or probing a site's surface: **every path is
|
|
271
|
+
request-ready as printed**, relative to the platform's REST root
|
|
272
|
+
(`/wp-json` on WordPress, `/api/index.php` on Joomla). Captures appear as
|
|
273
|
+
readable placeholders (`<templateId>`, `<path>`) that you substitute.
|
|
274
|
+
|
|
275
|
+
Do not hand-write a Joomla endpoint list from memory: the Joomla paths carry a
|
|
276
|
+
`yt-builder-mcp` segment (`/v1/yt-builder-mcp/pages/audit`, not `/v1/pages/audit`)
|
|
277
|
+
and the router rejects the short form.
|
|
278
|
+
|
|
236
279
|
Treat `frontend_url: null` as "host plugin could not resolve a public URL for
|
|
237
280
|
this template". Surface that honestly rather than fabricating one.
|
|
238
281
|
|
|
@@ -280,6 +323,26 @@ serves_context_ids: [11] }`. Use `pages_resolve` when you just want the answer;
|
|
|
280
323
|
use the `serves_context_*` columns when you already hold `pages_list` and want to
|
|
281
324
|
match locally.
|
|
282
325
|
|
|
326
|
+
**`page_role` on `pages_list` rows — a coarse content-role facet for filtering.**
|
|
327
|
+
Every row also carries `page_role`, one of `home`, `landing`, `product`, `legal`,
|
|
328
|
+
`thankyou`, `blog`, `mockup`, `template`, `generic`. It answers "which of these 80
|
|
329
|
+
rows are actually PRODUCT pages?" without you guessing from names — filter the
|
|
330
|
+
rows you already have by `page_role === "product"` instead of pattern-matching
|
|
331
|
+
labels yourself. It is derived HEURISTICALLY from fields already on the row
|
|
332
|
+
(URL slug, the raw YOOtheme type condition, the row's name) — a legal/thank-you/
|
|
333
|
+
product/landing match comes from the URL path (`/impressum`, `/datenschutz`,
|
|
334
|
+
`/danke`, `/produkt/…`); `home` comes from the site's authoritative front-page
|
|
335
|
+
resolution OR the row's own URL being the bare site root; `blog` from a native
|
|
336
|
+
WP post / Joomla article type; `mockup` from a designer's own "demo"/"mockup"/
|
|
337
|
+
"placeholder" wording in the row's name; `template` from a bare internal
|
|
338
|
+
condition. **`generic` is the honest default, not a bug** — exactly like an
|
|
339
|
+
empty `serves_context_ids`, it means no signal fired, not that classification
|
|
340
|
+
failed. Treat `page_role` as a filter/identification AID, not a guarantee: it
|
|
341
|
+
never re-reads anything (zero extra calls), so it can occasionally miss a page
|
|
342
|
+
whose role isn't visible from its URL/type/name — cross-check with
|
|
343
|
+
`page_get_layout` when precision genuinely matters (e.g. before a destructive
|
|
344
|
+
bulk action).
|
|
345
|
+
|
|
283
346
|
### Address a page WITHOUT its URL — the `target` object
|
|
284
347
|
|
|
285
348
|
`pages_resolve` and `pages_audit` take the **same** `target` object, so one
|
|
@@ -359,7 +422,9 @@ never a transport failure — report it verbatim rather than retrying blindly.
|
|
|
359
422
|
| `no_target` | 400 | A `target` WAS supplied but none of its members is a usable address — every member must be a **non-empty scalar**. `target[special][x]=y` (a nested value) and a templated `target[title]=` that rendered empty both land here. | Re-send with exactly one non-empty scalar key. Never re-send without a `target` "to see what happens": that is a whole-site sweep whose findings you would attribute to one page. |
|
|
360
423
|
| `too_many_targets` | 400 | Two or more address keys, or `url` alongside `target`. | Pick one. Use `title` or `pages_list` when you only know the page by name. |
|
|
361
424
|
| `types_conflicts_with_target` | 400 | `types` was sent with a `target`. | Drop `types` — a target already names exactly one page, so there is nothing for a type filter to narrow. |
|
|
362
|
-
| `unknown_check_category` | 400 | A `checks` value that is not one of the
|
|
425
|
+
| `unknown_check_category` | 400 | A `checks` value that is not one of the five categories (`seo`, `structure`, `content`, `links`, `design`) — a typo, a finding CODE passed where a category belongs, or a case variant: the vocabulary is **case-sensitive**, so `SEO` is not `seo`. | Re-send with a real category (the message names both the unknown members and the valid set). This is refused rather than filtered because an unrecognised value selects ZERO checks, and zero checks over a normally-scanned site returns `findings: []` — a confident CLEAN verdict for a typo. **Never read such an answer as clean.** |
|
|
426
|
+
| `unknown_audit_mode` | 400 | A `mode` value that is not `builder` or `rendered` (a typo, e.g. `render`). | Re-send with one of the two valid values. Refused rather than silently falling back to `builder`, for the same reason as `unknown_check_category`: nothing was scanned, so the previous answer is NOT a clean result. |
|
|
427
|
+
| `unknown_heading_outline_mode` | 400 | The SAME `mode` vocabulary (`builder`/`rendered`), on `yootheme_builder_heading_outline` instead of `pages_audit` — a dedicated code so the two are never confused. | Re-send with `builder` or `rendered`. |
|
|
363
428
|
| `no_template_matched_types` | 400 | A site-sweep `types` filter matched NO template on this site — a typo or, most often, a case variant (`PAGE` is not `page`). Unlike `checks`, the `types` vocabulary is **open and site-dependent** (`page`, `article`, `single-post`, `com_content.category`, …), so it cannot be validated against a fixed list — only a zero match is knowable, and it is knowable only after the sweep. | Re-send with one of the types the message lists (it names what this site actually has), or omit `types` to sweep everything. Refused for the same reason as `unknown_check_category`: a filter that selected nothing answered HTTP 200 with `findings: []` and an all-zero summary, which reads as CLEAN. **Never read such an answer as clean.** |
|
|
364
429
|
| `template_not_found` | 404 | The `template_id` names no template carrying a YOOtheme layout on this site. | Re-read the id from `pages_list`; ids are site-specific and change when a page is recreated. |
|
|
365
430
|
| `post_not_found` | 404 | The `post_id` names no WP post / Joomla article with a per-page layout. | Same: confirm via `pages_list`. A post that exists but has no per-page layout is rendered by a THEME template — find that template instead. |
|
|
@@ -714,6 +779,164 @@ section as the last child of the template root.
|
|
|
714
779
|
|
|
715
780
|
---
|
|
716
781
|
|
|
782
|
+
## Transparent header (the exception — never a default)
|
|
783
|
+
|
|
784
|
+
A transparent header pulls the page's **first** section up behind the site
|
|
785
|
+
header and recolours the navigation over it. It only works over a hero medium
|
|
786
|
+
that was made for it. **The default is: do not pull the section behind the
|
|
787
|
+
header.**
|
|
788
|
+
|
|
789
|
+
**Never turn `header_transparent` on by your own initiative** — not while
|
|
790
|
+
scaffolding a hero, not while inserting a Library layout, not "because it looks
|
|
791
|
+
more modern". Turn it on only when the customer asks for it, or when the
|
|
792
|
+
template you inserted already ships a hero medium built for it. **When in
|
|
793
|
+
doubt, leave it off.** That is always the safe answer, never a compromise.
|
|
794
|
+
Whoever turns it on picks the colour deliberately; with no deliberate choice
|
|
795
|
+
the colour is `""`, never `"light"`.
|
|
796
|
+
|
|
797
|
+
### The three props (they live on a `section`)
|
|
798
|
+
|
|
799
|
+
| Prop | Values | Read by YOOtheme only when |
|
|
800
|
+
|---|---|---|
|
|
801
|
+
| `header_transparent` | `true` / `false` | — (this is the master switch) |
|
|
802
|
+
| `header_transparent_noplaceholder` | `true` / `false` | `header_transparent` is true |
|
|
803
|
+
| `header_transparent_text_color` | `""` / `"light"` / `"dark"` | `header_transparent` is true |
|
|
804
|
+
|
|
805
|
+
### Three traps that break pages silently
|
|
806
|
+
|
|
807
|
+
1. **Only the FIRST section counts.** YOOtheme applies the transparent header
|
|
808
|
+
from the first section of the layout and ignores the props on every later
|
|
809
|
+
section — no error, no builder warning. "I set it and nothing happened" is
|
|
810
|
+
almost always this. Put the props on the first section, or remove them.
|
|
811
|
+
2. **Setting the colour without the master switch does nothing.**
|
|
812
|
+
`header_transparent_text_color` / `header_transparent_noplaceholder` on a
|
|
813
|
+
section whose `header_transparent` is off are dead props. They read as
|
|
814
|
+
configuration and are never applied.
|
|
815
|
+
3. **`"light"` is not a safe default — it is white navigation text.** The
|
|
816
|
+
recolour fires even when the section has no image, no video and no `style`,
|
|
817
|
+
so `"light"` over a light theme background is literally white-on-white. The
|
|
818
|
+
THEME already states which colour fits each section style (its compiled
|
|
819
|
+
`theme.css` declares `--uk-inverse` per style, e.g. `dark` for
|
|
820
|
+
`.uk-section-default`). Read that, or — where no image or video is in the way
|
|
821
|
+
— leave the colour at `""` and let the theme decide. Never assume `"light"`.
|
|
822
|
+
|
|
823
|
+
**Over a hero image or video, no colour choice is provable from the layout
|
|
824
|
+
alone.** The image hides the background the theme's declaration describes, and
|
|
825
|
+
which part of a `cover` image ends up under the header depends on the viewport.
|
|
826
|
+
|
|
827
|
+
Do not hand-write a colour there — and do **not** "play it safe" by pushing an
|
|
828
|
+
existing one back to `""`. `""` is the theme's own default colour: a real
|
|
829
|
+
rendered value, not an abstention. Over a dark hero on `.uk-section-default`
|
|
830
|
+
(which declares `dark`) reverting to `""` puts dark navigation on a dark photo,
|
|
831
|
+
which is the very break you were trying to avoid.
|
|
832
|
+
|
|
833
|
+
Let the tool leave it alone — `mode: "auto"` writes NO colour over a medium —
|
|
834
|
+
then LOOK at the rendered page (see "Look at what you built") at the customer's
|
|
835
|
+
viewport — unscrolled AND after scrolling, because a
|
|
836
|
+
sticky header drops its transparency on scroll. Then write what you SAW with
|
|
837
|
+
`mode: "explicit"`. Do not guess a colour, and do
|
|
838
|
+
not reason about "the top N% of the image".
|
|
839
|
+
|
|
840
|
+
### Use `header_transparency_set` — do not hand-write these props
|
|
841
|
+
|
|
842
|
+
`yootheme_builder_header_transparency_set` (advanced gateway) is the tool for
|
|
843
|
+
all of this. It **always targets the first section**, touches only the three
|
|
844
|
+
props, and **`dry_run` defaults to `true`** — so the first call is an
|
|
845
|
+
inspection that writes nothing.
|
|
846
|
+
|
|
847
|
+
```jsonc
|
|
848
|
+
// 1. INSPECT (writes nothing — dry_run defaults true, no etag needed).
|
|
849
|
+
// Returns the current state, the theme-declared --uk-inverse, risk_class,
|
|
850
|
+
// confidence, a recommendation and would_change/changes.
|
|
851
|
+
yootheme_builder_header_transparency_set({ template_id: "home" })
|
|
852
|
+
|
|
853
|
+
// 2. REPAIR with the theme's own declared colour.
|
|
854
|
+
yootheme_builder_header_transparency_set({
|
|
855
|
+
template_id: "home", mode: "auto", dry_run: false, etag: "<latest>"
|
|
856
|
+
})
|
|
857
|
+
|
|
858
|
+
// 3. Turn it ON deliberately, without asserting a colour.
|
|
859
|
+
// mode "auto" derives it; over a hero medium it writes NO colour and tells
|
|
860
|
+
// you to look at the page instead.
|
|
861
|
+
yootheme_builder_header_transparency_set({
|
|
862
|
+
template_id: "home", mode: "auto", transparent: true, dry_run: false, etag: "<latest>"
|
|
863
|
+
})
|
|
864
|
+
|
|
865
|
+
// 4. A colour you MEASURED over a hero medium (only mode "explicit" takes one).
|
|
866
|
+
yootheme_builder_header_transparency_set({
|
|
867
|
+
template_id: "home", mode: "explicit", text_color: "light",
|
|
868
|
+
dry_run: false, etag: "<latest>"
|
|
869
|
+
})
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
`mode: "explicit"` refuses a colour the theme's declaration contradicts when no
|
|
873
|
+
image or video hides that background (HTTP 409,
|
|
874
|
+
`error_code: header_contrast_contradicts_theme`). Override with
|
|
875
|
+
`acknowledge_contrast_risk: true` only after LOOKING at the rendered page.
|
|
876
|
+
|
|
877
|
+
To undo it, call with `transparent: false` — the tool removes all three props,
|
|
878
|
+
so no dead configuration is left behind. It is never refused —
|
|
879
|
+
**turning transparency OFF is always safe and never needs a justification.**
|
|
880
|
+
|
|
881
|
+
### Writing the props by hand — what `element_update_settings` refuses
|
|
882
|
+
|
|
883
|
+
You *can* write the props by hand with `element_update_settings` (`merge: true`,
|
|
884
|
+
on `/children/0`). That path now refuses the one mistake it can prove on its own,
|
|
885
|
+
and quietly does the safe thing for the other:
|
|
886
|
+
|
|
887
|
+
- **Dead props are rejected.** A `header_transparent_text_color` or
|
|
888
|
+
`header_transparent_noplaceholder` that this write leaves without a true
|
|
889
|
+
`header_transparent` comes back as
|
|
890
|
+
`error_code: header_transparent_dead_prop` and nothing is written. A prop
|
|
891
|
+
YOOtheme never reads is dead whatever the theme declares, so this needs no
|
|
892
|
+
theme knowledge. Note the merge semantics: `element_update_settings` REPLACES
|
|
893
|
+
props unless you pass `merge: true`, so a full-replace write that omits
|
|
894
|
+
`header_transparent` switches the header off.
|
|
895
|
+
- **Turning it on without a colour writes `""`** — the theme's own default —
|
|
896
|
+
and tells you so. It is never defaulted to `"light"`. Under `merge: true` the
|
|
897
|
+
stored colour cannot be read from here, so nothing is seeded and you are
|
|
898
|
+
asked to state the colour yourself rather than have a guess clobber a value
|
|
899
|
+
you measured.
|
|
900
|
+
- **Over a hero image or video the write is ALLOWED**, with a
|
|
901
|
+
`header_contrast_unverifiable_over_media` warning. Nothing is corrected there
|
|
902
|
+
— only your eyes can settle it (see "Look at what you built"). That warning
|
|
903
|
+
**only fires when THIS write states the `image`/`video` itself.** A medium
|
|
904
|
+
already stored on the section is not readable from here, so under
|
|
905
|
+
`merge: true` — the shape recommended just above — such a write goes through
|
|
906
|
+
**silently**. Read no warning as "there is no medium": the usual hand-write
|
|
907
|
+
(`merge: true` with only the header props, on a section that already has its
|
|
908
|
+
hero) is exactly that silent case. `pages_audit` and
|
|
909
|
+
`header_transparency_set` read the stored layout and do see it.
|
|
910
|
+
|
|
911
|
+
**What that hand-write path still cannot check for you:** the **first-section
|
|
912
|
+
rule** — a single element write does not know where its section sits in the
|
|
913
|
+
layout — and the **theme declaration**, because the compiled `theme.css` is not
|
|
914
|
+
readable from the MCP server. A colour that contradicts `--uk-inverse` therefore
|
|
915
|
+
goes through untouched there. `header_transparency_set` and `pages_audit` are
|
|
916
|
+
where both of those are checked.
|
|
917
|
+
|
|
918
|
+
**You do not have to spot this by eye.** `pages_audit` carries two `design`
|
|
919
|
+
codes for it. Both read the first section's props plus the theme's own
|
|
920
|
+
`--uk-inverse` declaration for that section style out of the compiled
|
|
921
|
+
`theme.css`:
|
|
922
|
+
|
|
923
|
+
- **`header-contrast-risk`** (severity `warning`) — the layout forces a colour
|
|
924
|
+
the theme's declaration contradicts, and **no image or video covers** the
|
|
925
|
+
background that declaration describes. Provable from the props alone: the
|
|
926
|
+
recommended colour is the theme's own declared value, so it never guesses a colour.
|
|
927
|
+
- **`header-contrast-unverifiable`** (severity `info`) — a transparent header
|
|
928
|
+
over a hero image or video, **whatever the colour**. The medium covers exactly
|
|
929
|
+
the background the declaration describes, so nothing is provable there and the
|
|
930
|
+
code recommends no colour at all: go and look at the page.
|
|
931
|
+
|
|
932
|
+
The split is "is a medium in the way?", never "is the colour right?". Behind a
|
|
933
|
+
hero medium no value is verifiable and none is safer than another — not even
|
|
934
|
+
`""`, which is simply the theme's default colour. When `theme.css` cannot be
|
|
935
|
+
read, both say nothing at all rather than judging on a hunch. See Workflow 6's
|
|
936
|
+
remediation catalog.
|
|
937
|
+
|
|
938
|
+
---
|
|
939
|
+
|
|
717
940
|
## Multi-column layouts (columns that sit side-by-side)
|
|
718
941
|
|
|
719
942
|
**The single most common layout mistake: a `row` with several `column`
|
|
@@ -914,6 +1137,38 @@ version on a layout you read-modify-write is preserved, never overwritten.
|
|
|
914
1137
|
|
|
915
1138
|
---
|
|
916
1139
|
|
|
1140
|
+
## Per-element render quirks (the single-element tail)
|
|
1141
|
+
|
|
1142
|
+
The rules that apply to a whole CLASS of props ride in the schema itself:
|
|
1143
|
+
`element_type_get_schema` returns `renders_only_if`, `placeholder_fallback`,
|
|
1144
|
+
`blanked_by_parent`, `media_autoswap`, per-field `value_hint` / `no_effect` /
|
|
1145
|
+
`empty_means_inherit`, and a `rules` id list resolved by a `rule_notes` map
|
|
1146
|
+
(the `enable`-vs-render disagreements, the equal-height model, the `striptags`
|
|
1147
|
+
on link toggles, positional dependence, the collapse cascade, iframe video, the
|
|
1148
|
+
Maps API key, and link attributes under a lightbox). **Read those from the
|
|
1149
|
+
response, not from here.** The response also states which build they were read
|
|
1150
|
+
on — see `claims_verified_on` above.
|
|
1151
|
+
|
|
1152
|
+
What follows is the tail: quirks that belong to ONE element and would cost more
|
|
1153
|
+
in schema bytes than they are worth. Each was read in the YOOtheme **5.0.37**
|
|
1154
|
+
source at the cited location — so on a site running a different build, treat
|
|
1155
|
+
this section the way `claims_verified_on: "differs"` asks you to treat the
|
|
1156
|
+
schema's own claims: a report to check, not a fact about your build.
|
|
1157
|
+
|
|
1158
|
+
| Element / prop | The quirk |
|
|
1159
|
+
|---|---|
|
|
1160
|
+
| `grid.grid_divider` | The divider class is emitted only when the grid has **more than one child** (`grid/templates/template.php:44`, `=> count($children) > 1`). A one-item grid silently has no divider. |
|
|
1161
|
+
| `grid.grid_parallax_justify` | Setting it while `grid_parallax` is empty **forces `grid_parallax` to `'0'`** (`grid/templates/template.php:10-11`). You cannot have justify without parallax being on. |
|
|
1162
|
+
| `grid.panel_style: 'tile-checked'` | A **parent-only** value: the checked pattern comes from `uk-grid-checked` on the grid container, driven by the container's own `panel_style` (`grid/templates/template.php:56`; the render transform branches on it at `grid/element.php:118`). Setting it on an item does nothing. |
|
|
1163
|
+
| `grid_item.tags` | Inert unless the **parent grid's `filter`** is on: the whole tag-collection block runs inside `if (!empty($node->props['filter']))` (`grid/element.php:74`). Tags are also `strip_tags()`'d and their keys have **spaces replaced by dashes** (`:80-83`), so "New York" becomes the key `New-York`. |
|
|
1164
|
+
| `grid_item.title_color` vs `title_decoration` | `title_color: 'background'` and `title_decoration: 'line'` are decided by one `if`/`elseif` (`grid_item/templates/template-content.php:112-114`) — with `background` set, `line` never renders. Not additive. |
|
|
1165
|
+
| `grid.image_vertical_align` | Stops centering once `panel_expand` is active: the class carries `{@!panel_expand}` (`grid_item/templates/template.php:129`). |
|
|
1166
|
+
| `section` without `width` | Still emits a container when the section has a **video or an overlay** (`section/templates/template.php:158`), and that container then also gets `uk-panel` (`:167`) — which zeroes the last child's bottom margin. A width-less section is not always container-less. |
|
|
1167
|
+
| `slideshow` nav (not `thumbnav`) | Borrows the **slide's `title`** as the link text (`slideshow/templates/template-nav.php`: `$thumbnail ?: $child->props['title']`). An untitled slide gets an empty nav link. |
|
|
1168
|
+
| `accordion_item` | Declares **no `title_element`** at all (`grep -c title_element` = 0 in both `accordion_item/element.php` and `accordion/element.php`), so accordion titles can never carry a heading tag and never enter the heading outline. Do not try to write one. |
|
|
1169
|
+
|
|
1170
|
+
---
|
|
1171
|
+
|
|
917
1172
|
## Workflow 2: Bind a dynamic source to a grid
|
|
918
1173
|
|
|
919
1174
|
**Goal:** Wire an existing Grid (or other multi-item element) to a
|
|
@@ -1109,6 +1364,12 @@ CMS-level template duplication (in wp-admin or Joomla administrator).
|
|
|
1109
1364
|
REPLACES the entire `props` object on the element unless you pass
|
|
1110
1365
|
`merge: true`. Read the existing shape via `yootheme_builder_element_get`
|
|
1111
1366
|
first if you only want to tweak a subset and prefer not to use merge.
|
|
1367
|
+
- **Reading a prop's value never needs a second call.** `element_get`'s
|
|
1368
|
+
output, both the JSON and the printed summary, lists every prop KEY
|
|
1369
|
+
together with its VALUE (e.g. `title_element: "h1"`), each value capped
|
|
1370
|
+
at roughly 200 characters with a visible `…(+N chars)` marker when
|
|
1371
|
+
truncated, never a silent drop. There's no need to cross-check via
|
|
1372
|
+
`element_list` just to see what a specific prop is currently set to.
|
|
1112
1373
|
- **Clone-then-update path drift.** The clone returns a path that's
|
|
1113
1374
|
correct at the moment of the call. If you fire off many ops in
|
|
1114
1375
|
parallel, a concurrent edit may shift indices. Refresh via
|
|
@@ -1169,7 +1430,8 @@ guessing, and without rotating the user's key unnecessarily.
|
|
|
1169
1430
|
auth) and then `/etag` (Bearer auth). Returns
|
|
1170
1431
|
`{ plugin_reachable, plugin_version, yootheme_loaded, yootheme_version,
|
|
1171
1432
|
endpoint_count, bearer_valid, bearer_error?, site_url?, home_url?,
|
|
1172
|
-
summary? }`.
|
|
1433
|
+
summary?, capability_ok?, capability_warning?, effective_capabilities? }`.
|
|
1434
|
+
Call this **before** any other tool when you see
|
|
1173
1435
|
auth errors. (Takes no arguments. The schema is `{}`.)
|
|
1174
1436
|
2. **Interpret the result:**
|
|
1175
1437
|
- `plugin_reachable: false` → the WordPress / Joomla install is down
|
|
@@ -1191,6 +1453,14 @@ guessing, and without rotating the user's key unnecessarily.
|
|
|
1191
1453
|
the tool's required scope (`write` for mutations, `admin` for
|
|
1192
1454
|
destructive operations). Ask the user to regenerate the key with
|
|
1193
1455
|
a higher scope and restart the AI client.
|
|
1456
|
+
- `plugin_reachable: true, bearer_valid: true, capability_ok: false` →
|
|
1457
|
+
the Bearer key's scope IS write, but the CMS user it resolves to
|
|
1458
|
+
(`effective_capabilities.can_edit`, `can_create`, `can_delete`, all
|
|
1459
|
+
false) has no write capability at all, so writes will still 403
|
|
1460
|
+
despite the write-scoped token. `capability_warning` carries the
|
|
1461
|
+
exact remediation sentence. Re-mint the key under a user with edit
|
|
1462
|
+
rights, or grant that user the capability. A write-scoped token
|
|
1463
|
+
alone is not enough.
|
|
1194
1464
|
3. **Walk the user through key rotation if needed:**
|
|
1195
1465
|
- WordPress: "wp-admin → Tools → YT Builder MCP → Bearer Keys."
|
|
1196
1466
|
Joomla: "Components → YT Builder MCP → Bearer Keys."
|
|
@@ -1393,11 +1663,23 @@ exposes. Never analyze findings by hand and never bulk-apply a fix.
|
|
|
1393
1663
|
`title-collision` never carry a `fix_hint` — they need a human
|
|
1394
1664
|
decision (rename, merge, or intentionally keep as-is), not an
|
|
1395
1665
|
automatic patch.
|
|
1396
|
-
3. `yootheme_builder_template_summary({ template_id })`
|
|
1397
|
-
page a finding names. It carries the same
|
|
1398
|
-
`images_missing_alt`, and `disabled_elements` counters
|
|
1399
|
-
used,
|
|
1400
|
-
|
|
1666
|
+
3. `yootheme_builder_template_summary({ template_id, include_nodes: true })`
|
|
1667
|
+
drills into one page a finding names. It carries the same
|
|
1668
|
+
`heading_levels`, `images_missing_alt`, and `disabled_elements` counters
|
|
1669
|
+
the audit used, plus (with `include_nodes:true` — opt-in, omit it for
|
|
1670
|
+
the smaller default) a per-node `content_nodes` breakdown (`path`,
|
|
1671
|
+
`type`, `status`, `effective_visible`, `hidden_on_mobile`, `headings`,
|
|
1672
|
+
`alts`), so you can see exactly WHICH node is affected, whether it
|
|
1673
|
+
is disabled or just hidden on mobile, in one call instead of pulling
|
|
1674
|
+
the raw `page_get_layout`. Page a large `content_nodes` list with
|
|
1675
|
+
`limit`/`cursor`; drill into several findings' pages in ONE call with
|
|
1676
|
+
`template_ids:[...]` instead of `template_id` (returns `summaries` keyed
|
|
1677
|
+
by id + `not_found`). `heading_levels` rides alongside a
|
|
1678
|
+
`heading_levels_note` sibling stating exactly which sources feed the
|
|
1679
|
+
census: every element whose title/meta channel resolves to a heading
|
|
1680
|
+
level, PLUS real `<h1>`-`<h6>` tags found inside `html`/`text`/`code`
|
|
1681
|
+
element markup. It does NOT count `module`/widget output — a WordPress
|
|
1682
|
+
widget or Joomla module has no content field this counter can parse.
|
|
1401
1683
|
4. Fix that ONE page via the write tool named in `fix_hint.tool`, with
|
|
1402
1684
|
`fix_hint.args_template` as a starting point (`element_update_settings`,
|
|
1403
1685
|
`element_add`, etc), ETag-locked like every other write in this
|
|
@@ -1407,6 +1689,38 @@ exposes. Never analyze findings by hand and never bulk-apply a fix.
|
|
|
1407
1689
|
with the same per-page codes, so you can confirm the fix landed
|
|
1408
1690
|
without a second full sweep.
|
|
1409
1691
|
|
|
1692
|
+
**Disabling instead of editing.** Sometimes the right remediation is to
|
|
1693
|
+
exclude an element entirely rather than edit its content (a stray leftover
|
|
1694
|
+
section, a duplicate block). `yootheme_builder_element_set_status({
|
|
1695
|
+
template_id, element_path, status: false, etag })` writes `props.status:
|
|
1696
|
+
'disabled'` — the SAME field YOOtheme's own native "Disable element"
|
|
1697
|
+
checkbox writes, so the element is excluded from BOTH the live frontend
|
|
1698
|
+
render AND the NEXT `pages_audit`/`template_summary` (both read the same
|
|
1699
|
+
`props.status`/`disabled_elements`/`content_nodes.status` fields, so the fix
|
|
1700
|
+
is visible without a DB round-trip). Pass `status: true` to re-enable it,
|
|
1701
|
+
which restores the YOOtheme default (no status field means active) rather
|
|
1702
|
+
than persisting a fabricated `props.status`.
|
|
1703
|
+
|
|
1704
|
+
**Need a real document outline instead of findings?** `pages_audit` tells you
|
|
1705
|
+
WHAT is wrong (a missing h1, a skipped level); it never shows you the whole
|
|
1706
|
+
heading tree in one look. `yootheme_builder_heading_outline({ template_id })`
|
|
1707
|
+
(read-only, reached via `yootheme_builder_advanced_read`) is a companion
|
|
1708
|
+
STRUCTURE view for ONE page: every H1-H6 heading in true document order, each
|
|
1709
|
+
carrying its TEXT and the element that carries it (`element_type`/`path` —
|
|
1710
|
+
`headline`, or the `html`/`text`/`code` element for a raw-HTML heading), plus
|
|
1711
|
+
the alt-status of every image on the page (`alt: null` for missing, plus
|
|
1712
|
+
`is_generic_alt` for a lazy "Logo"/"Bild"-style alt). Use it BEFORE editing a
|
|
1713
|
+
page's heading hierarchy by hand, or to double-check a `heading-skip` finding
|
|
1714
|
+
in context rather than as an isolated hit:
|
|
1715
|
+
`yootheme_builder_advanced_read({ tool: "yootheme_builder_heading_outline", arguments: { template_id: "page:4816" } })`
|
|
1716
|
+
returns `{outline, images, h1_count, has_level_skip, images_missing_alt}` —
|
|
1717
|
+
`has_level_skip` mirrors the `heading-skip` check's EXACT rule (a level more
|
|
1718
|
+
than one above the highest seen so far, including a document that opens below
|
|
1719
|
+
h1). Pass `mode: "rendered"` to read the ACTUAL rendered DOM instead of the
|
|
1720
|
+
saved layout tree (the same trade-off `pages_audit`'s own `mode` makes: sees
|
|
1721
|
+
module/widget output, but `element_type` degrades to `""` since there is no
|
|
1722
|
+
builder node to point at).
|
|
1723
|
+
|
|
1410
1724
|
**Common pitfalls:**
|
|
1411
1725
|
|
|
1412
1726
|
- **Promising a full SEO audit.** `pages_audit` covers Builder structure
|
|
@@ -1418,6 +1732,51 @@ exposes. Never analyze findings by hand and never bulk-apply a fix.
|
|
|
1418
1732
|
It does not read or evaluate Yoast/RankMath/any SEO-plugin meta
|
|
1419
1733
|
title, meta description, or Open Graph tags. Say this scope boundary
|
|
1420
1734
|
out loud and point the user at their SEO plugin for that layer.
|
|
1735
|
+
- **Assuming builder props are the only content scanned.** Headings and
|
|
1736
|
+
image alt text are also detected inside raw HTML: an `html`/`text`
|
|
1737
|
+
element's markup content, not just builder props like
|
|
1738
|
+
`title`/`image_alt`. A sticky-bar `<h1>` or an alt-less `<img>` buried
|
|
1739
|
+
in raw markup is caught, not silently missed, unless the whole
|
|
1740
|
+
`content` prop is dynamically bound (a bound value is a stale editor
|
|
1741
|
+
preview and is skipped). Since T-A1, `code` joined `html`/`text` as a raw-HTML
|
|
1742
|
+
carrier, and anchor text + heading length/order are ALSO detected the same
|
|
1743
|
+
way: an empty/generic `<a>` link, a heading level skip, or an over-long
|
|
1744
|
+
heading buried in raw markup is caught by `generic-alt-text`/
|
|
1745
|
+
`anchor-text-quality`/`heading-skip`/`long-heading`, not just by
|
|
1746
|
+
`missing-h1`/`multiple-h1`/`no-headings`/`images-missing-alt`/`dead-anchor`.
|
|
1747
|
+
`module`/`module_position` (WordPress "Widget"/"Widget Area", Joomla
|
|
1748
|
+
"Module"/"Module Position") are NOT scanned - verified live that neither
|
|
1749
|
+
carries a markup-shaped `content` field on either platform; their actual
|
|
1750
|
+
content lives in an external WP widget instance / Joomla `#__modules` row,
|
|
1751
|
+
outside the layout JSON entirely.
|
|
1752
|
+
- **`mode:"rendered"` sees what the builder tree structurally cannot** (T-A2,
|
|
1753
|
+
2026-07-24) — pass `mode:"rendered"` (default is `"builder"`, the tree read
|
|
1754
|
+
above) to instead render each page first (the SAME engine
|
|
1755
|
+
`yootheme_builder_render_preview` uses) and audit the ACTUAL rendered DOM.
|
|
1756
|
+
This is the one way to catch a `module`/widget's own runtime output — a
|
|
1757
|
+
newsletter-signup widget that itself renders an `<h1>`, dynamically-bound
|
|
1758
|
+
content, anything a plugin injects only at render time — none of which the
|
|
1759
|
+
builder-JSON walk above can see AT ALL. Only a curated subset of checks runs
|
|
1760
|
+
in this mode (`no-headings`, `missing-h1`, `multiple-h1`, `heading-skip`,
|
|
1761
|
+
`images-missing-alt`, `generic-alt-text`, `dead-anchor`, `anchor-text-quality`,
|
|
1762
|
+
`long-heading`, plus the three `design-*` markup codes — every check whose
|
|
1763
|
+
evidence is honestly derivable from a flat rendered document);
|
|
1764
|
+
`thin-content`/`duplicate-layout`/`empty-page`/`heading-hidden-on-mobile`/
|
|
1765
|
+
`non-pretty-url`/`stale-content` and both `header-contrast-*` codes still
|
|
1766
|
+
need the builder tree and do not run here (`header_transparent*` are section
|
|
1767
|
+
PROPS — they are gone by render time, so a rendered sweep can never produce
|
|
1768
|
+
those two findings). **Locators carry NO builder element
|
|
1769
|
+
path in this mode** — `breadcrumb`/`element_type` are empty strings BY
|
|
1770
|
+
DESIGN (there is no builder node to point at, not a resolution failure) —
|
|
1771
|
+
locate the hit by its `content_snippet` (the actual rendered text) or the
|
|
1772
|
+
page's `builder_url`/`frontend_url` instead. A site-wide rendered sweep is
|
|
1773
|
+
capped (rendering is expensive — each page runs the full YOOtheme Builder
|
|
1774
|
+
pipeline, not a JSON walk): the response always carries
|
|
1775
|
+
`render_scan: {attempted, rendered, capped}`, even when the cap never
|
|
1776
|
+
engaged, so you can always see whether every page was actually rendered.
|
|
1777
|
+
A page whose render failed is excluded from findings (never reported as a
|
|
1778
|
+
false "no headings") and listed in `render_errors` instead — check both
|
|
1779
|
+
fields before trusting a clean rendered-mode result.
|
|
1421
1780
|
- **Confusing the CONTENT count with `pages_list.elements_count`.**
|
|
1422
1781
|
`pages_list.elements_count` counts every node, including
|
|
1423
1782
|
`section`/`row`/`column`/`fragment` layout wrappers. `pages_audit`
|
|
@@ -1587,6 +1946,88 @@ exactly one page, so there is nothing for a type filter to narrow). A `title`
|
|
|
1587
1946
|
that matches several pages returns `title_ambiguous` with a candidate list —
|
|
1588
1947
|
show it, let the user choose.
|
|
1589
1948
|
|
|
1949
|
+
### Design-Consistency-Check (`checks: ["design"]`)
|
|
1950
|
+
|
|
1951
|
+
A fifth audit category, alongside `seo`/`structure`/`content`/`links`, in the
|
|
1952
|
+
DEFAULT-ALL set (no opt-in flag needed — just call `pages_audit` normally, or
|
|
1953
|
+
narrow to it with `checks: ["design"]`). It flags markup that breaks OUT of
|
|
1954
|
+
the site's UIkit design system — most commonly a **plugin's own output**
|
|
1955
|
+
(Formidable, ACF, a shortcode-rendered form) carrying none of YOOtheme's
|
|
1956
|
+
`uk-*` classes, e.g. a form submit button with no `uk-button`. Native
|
|
1957
|
+
YOOtheme builder elements (grid_item, panel, button, …) are never checked —
|
|
1958
|
+
the theme always renders them with the right `uk-*` class; there is nothing
|
|
1959
|
+
to flag there.
|
|
1960
|
+
|
|
1961
|
+
Three finding codes, all `warning` severity, none carrying a `fix_hint` (see
|
|
1962
|
+
below for why):
|
|
1963
|
+
|
|
1964
|
+
- **`design-missing-uikit-class`** — a contract-bearing element
|
|
1965
|
+
(button/select/textarea/input/`a[role=button]`/form/label-in-a-form) with
|
|
1966
|
+
no class at all, or one that already carries a DIFFERENT real `uk-*` class
|
|
1967
|
+
(on-system, just incomplete) — its expected class is simply absent.
|
|
1968
|
+
- **`design-foreign-class`** — the SAME element universe, but it carries
|
|
1969
|
+
class(es) and NONE of them are real UIkit classes — positive evidence a
|
|
1970
|
+
DIFFERENT framework (Bootstrap's `btn btn-primary`, a plugin's own
|
|
1971
|
+
`form-control`) is actively styling it instead of UIkit.
|
|
1972
|
+
- **`design-inline-style`** — ANY element (no tag restriction) with a
|
|
1973
|
+
non-empty `style=` attribute inside embedded content — bypasses the theme's
|
|
1974
|
+
design system entirely.
|
|
1975
|
+
|
|
1976
|
+
A FOURTH and FIFTH code share the `design` category but are not about markup at
|
|
1977
|
+
all — they read section props, not embedded HTML, so unlike the three above they
|
|
1978
|
+
are **builder-mode only** and never run in `mode: "rendered"` (the props do not
|
|
1979
|
+
exist in a rendered document):
|
|
1980
|
+
|
|
1981
|
+
- **`header-contrast-risk`** (`warning`) — the first section pulls the
|
|
1982
|
+
navigation over itself in a colour the theme's own `--uk-inverse` declaration
|
|
1983
|
+
contradicts, with no image or video hiding the background that declaration
|
|
1984
|
+
describes. Unlike the three above it DOES carry a `fix_hint`.
|
|
1985
|
+
- **`header-contrast-unverifiable`** (`info`) — the same transparent header over
|
|
1986
|
+
a hero medium, whatever the colour: nothing there is provable from the props,
|
|
1987
|
+
so no colour is recommended and readability is only verifiable by looking.
|
|
1988
|
+
|
|
1989
|
+
See their remediation-catalog entries below.
|
|
1990
|
+
|
|
1991
|
+
**Run the three markup codes in `mode: "rendered"` for the highest-value
|
|
1992
|
+
result.** A Formidable/ACF form's markup exists ONLY at render time — it is
|
|
1993
|
+
invisible to a builder-tree walk unless it happens to be typed static markup
|
|
1994
|
+
inside an `html`/`text`/`code` element. `mode: "rendered"` renders each page
|
|
1995
|
+
first (the SAME engine `render_preview` uses) and audits the ACTUAL output, so
|
|
1996
|
+
a plugin shortcode's real markup is exactly what gets checked. (The two
|
|
1997
|
+
`header-contrast-*` codes do NOT run in that mode — use the default
|
|
1998
|
+
`mode: "builder"` when you want them.)
|
|
1999
|
+
|
|
2000
|
+
```jsonc
|
|
2001
|
+
yootheme_builder_advanced_read({
|
|
2002
|
+
tool: "yootheme_builder_pages_audit",
|
|
2003
|
+
arguments: { mode: "rendered", checks: ["design"], target: { special: "contact" } }
|
|
2004
|
+
})
|
|
2005
|
+
```
|
|
2006
|
+
|
|
2007
|
+
**No `fix_hint` on any of the three codes — this is deliberate, not a gap.**
|
|
2008
|
+
The offending markup lives inside an opaque `props.content` string (an
|
|
2009
|
+
html/text/code element) or a plugin's own rendered output — there is no
|
|
2010
|
+
structured prop `element_update_settings` could merge a class into without
|
|
2011
|
+
string-surgery on customer HTML, which this server's read-only-analysis
|
|
2012
|
+
policy forbids on the write path. Report the locator's `content_snippet`
|
|
2013
|
+
(the offending element's OWN markup, e.g. `<button class="btn btn-primary">`
|
|
2014
|
+
— unlike every other finding's snippet, this one deliberately keeps the
|
|
2015
|
+
class/style attribute instead of stripping it, since that attribute IS the
|
|
2016
|
+
evidence) and tell the user to add the class by hand, or fix it in the
|
|
2017
|
+
plugin's own settings/template.
|
|
2018
|
+
|
|
2019
|
+
**Design-consistency fixes: ground the exact class via Context7 when it is
|
|
2020
|
+
available.** A `design-missing-uikit-class` or `design-foreign-class` finding
|
|
2021
|
+
asks you to name a real UIkit class by hand; recalling one purely from
|
|
2022
|
+
training data risks a subtly wrong suggestion, since class names can drift
|
|
2023
|
+
across UIkit versions. When the user's client has Context7 (or an equivalent
|
|
2024
|
+
documentation-grounding MCP) configured, query it (library id
|
|
2025
|
+
`/websites/getuikit`) to confirm the exact class before naming it in the fix.
|
|
2026
|
+
Without Context7, degrade cleanly: keep working from the tool's own output
|
|
2027
|
+
(the locator's `content_snippet`) plus the class names already catalogued
|
|
2028
|
+
above, and never invent a class name you have not confirmed one way or the
|
|
2029
|
+
other.
|
|
2030
|
+
|
|
1590
2031
|
### Remediation catalog — what each finding means and how to fix it
|
|
1591
2032
|
|
|
1592
2033
|
The audit reports; YOU explain. Every finding below carries its fixed
|
|
@@ -1644,6 +2085,25 @@ second, independent heading channel), which is exactly what the raw path cannot.
|
|
|
1644
2085
|
N times through an iterating container or a Multi-Items binding, so the fix is
|
|
1645
2086
|
one edit on the item, not N edits; the locator's `×repeat` shows the multiplier.
|
|
1646
2087
|
|
|
2088
|
+
#### `heading-hidden-on-mobile` - structure · warning
|
|
2089
|
+
|
|
2090
|
+
**Why it matters.** Google indexes mobile-first: a heading that renders on
|
|
2091
|
+
desktop but is hidden at the base (phone) UIkit breakpoint is effectively
|
|
2092
|
+
invisible for ranking, even though it exists in the markup and a desktop
|
|
2093
|
+
review would never catch it.
|
|
2094
|
+
**How to fix.** No `fix_hint`. Hiding a heading on mobile is sometimes
|
|
2095
|
+
intentional (a duplicate mobile-only title elsewhere). Check the node's
|
|
2096
|
+
`props.visibility` (own or an ancestor's) and either remove the
|
|
2097
|
+
`hidden@`/`visible@` class or confirm an equivalent heading is visible on
|
|
2098
|
+
mobile some other way.
|
|
2099
|
+
**Severity.** Warning. Strict subset of ONLY the headline-TYPE portion of the
|
|
2100
|
+
pool `heading-skip` reads (since T-A1, `heading-skip` ALSO covers raw-HTML
|
|
2101
|
+
h1-h6 tags inside `html`/`text`/`code` elements - this check does not; a
|
|
2102
|
+
raw-HTML heading's mobile-visibility is not tracked): `hidden@<bp>` hides FROM
|
|
2103
|
+
that breakpoint upward (mobile stays visible), `visible@<bp>` is the mirror
|
|
2104
|
+
(hidden below it, mobile included), a bare `hidden` hides everywhere. Only the
|
|
2105
|
+
last two ever fire this check.
|
|
2106
|
+
|
|
1647
2107
|
#### `heading-skip` — structure · info
|
|
1648
2108
|
|
|
1649
2109
|
**Why it matters.** Jumping h1 → h3 breaks the document outline: assistive tech
|
|
@@ -1652,9 +2112,12 @@ reports a missing level, and the hierarchy stops describing the real structure.
|
|
|
1652
2112
|
the jumped-to heading (`element_update_settings`, `props.title_element`) or add
|
|
1653
2113
|
the missing intermediate level. The evidence lists each skip as
|
|
1654
2114
|
`{ path, from, to }`.
|
|
1655
|
-
**Severity.** Info: it is a real hierarchy defect but the page remains usable
|
|
1656
|
-
|
|
1657
|
-
|
|
2115
|
+
**Severity.** Info: it is a real hierarchy defect but the page remains usable.
|
|
2116
|
+
Reads BOTH `headline` elements AND raw-HTML h1-h6 tags inside `html`/`text`/
|
|
2117
|
+
`code` elements' `content` (interleaved in one true document-order sequence,
|
|
2118
|
+
since T-A1) - but container-STARTED hierarchies (e.g. a page whose only
|
|
2119
|
+
headings are 3× h3 grid-item titles with no h1/h2 anywhere) remain invisible
|
|
2120
|
+
to it. Do not present it as complete heading-hierarchy coverage.
|
|
1658
2121
|
|
|
1659
2122
|
#### `images-missing-alt` — content · warning
|
|
1660
2123
|
|
|
@@ -1750,7 +2213,12 @@ prop is `title`. For an icon-only or image-only link, the right fix is
|
|
|
1750
2213
|
`props.link_aria_label`, not invented visible text. Only link-bearing elements
|
|
1751
2214
|
whose visible-text prop is known are checked — `button`, `button_item`,
|
|
1752
2215
|
`grid_item`, `panel`, `headline`, `list_item`, `nav_item`, `subnav_item` — so
|
|
1753
|
-
this is an allowlist, not a sweep over every element carrying a `link`.
|
|
2216
|
+
this is an allowlist, not a sweep over every element carrying a `link`. Since
|
|
2217
|
+
T-A1, a raw `<a href>` tag inside an `html`/`text`/`code` element's `content` is
|
|
2218
|
+
ALSO checked - that path judges every anchor with a set `href` (any
|
|
2219
|
+
destination, not just in-page), exempting an empty one only when it carries an
|
|
2220
|
+
`aria-label` or a nested `<img alt>` (the raw-HTML mirror of the allowlist's
|
|
2221
|
+
icon/image/aria-label exemption).
|
|
1754
2222
|
**Severity.** Warning. The locator's `reason` says which rule fired: `empty` or
|
|
1755
2223
|
`generic` (an **exact match** against the DE+EN generic list — `mehr`, `hier`,
|
|
1756
2224
|
`weiterlesen`, `click here`, `read more`, `learn more`, …). Exact match only, so
|
|
@@ -1779,7 +2247,9 @@ the image actually shows ("SunWest Büro in Manresa").
|
|
|
1779
2247
|
the EMPTY case). Only an **exact match** against the generic list (`image`,
|
|
1780
2248
|
`photo`, `picture`, `logo`, `icon`, `bild`, `foto`, `grafik`) fires, so
|
|
1781
2249
|
`"ACME Logo"` is already a good alt text and is never flagged. Bound alt/image
|
|
1782
|
-
props are exempt.
|
|
2250
|
+
props are exempt. Since T-A1, a raw `<img alt="...">` inside an `html`/`text`/
|
|
2251
|
+
`code` element's `content` is checked the SAME way, judged against the
|
|
2252
|
+
identical generic list.
|
|
1783
2253
|
|
|
1784
2254
|
#### `long-heading` — structure · info
|
|
1785
2255
|
|
|
@@ -1790,8 +2260,9 @@ keyword focus, get truncated in search results and wrap badly on mobile.
|
|
|
1790
2260
|
detail into the body text below it. The locator carries the measured `length`.
|
|
1791
2261
|
**Severity.** Info — a long heading is a quality signal, never a defect. The
|
|
1792
2262
|
threshold is **70 characters**, measured multibyte-safely over the
|
|
1793
|
-
markup-stripped text, on unbound static text only. Scope
|
|
1794
|
-
|
|
2263
|
+
markup-stripped text, on unbound static text only. Scope: `headline` plus the
|
|
2264
|
+
standalone title carriers (`alert`, `overlay`, `panel`), PLUS (since T-A1) every
|
|
2265
|
+
h1-h6 tag's own text inside an `html`/`text`/`code` element's raw `content`.
|
|
1795
2266
|
Multi-Items containers keep their title text on the paired `*_item` children and
|
|
1796
2267
|
are not measured — do not claim full heading-length coverage.
|
|
1797
2268
|
|
|
@@ -1799,6 +2270,157 @@ are not measured — do not claim full heading-length coverage.
|
|
|
1799
2270
|
checks, so they also ride in the `quick_check` block on `page_publish` /
|
|
1800
2271
|
`page_save` alongside the other per-page codes.
|
|
1801
2272
|
|
|
2273
|
+
#### `design-missing-uikit-class` — design · warning
|
|
2274
|
+
|
|
2275
|
+
**Why it matters.** A contract-bearing element (a form's submit button, its
|
|
2276
|
+
text inputs, its labels) with no `uk-*` class at all renders unstyled — it
|
|
2277
|
+
visually breaks out of the site's design system, most visibly on a
|
|
2278
|
+
plugin-rendered form (Formidable, ACF) dropped onto an otherwise polished
|
|
2279
|
+
page.
|
|
2280
|
+
**How to fix.** No `fix_hint` — see "Design-Consistency-Check" above for why.
|
|
2281
|
+
Add the missing class by hand (`uk-button` on a submit button, `uk-input` on
|
|
2282
|
+
a text input, `uk-form-label` on a label inside a form, `uk-form-stacked` or
|
|
2283
|
+
`uk-form-horizontal` on the form itself) — via the plugin's own class/CSS
|
|
2284
|
+
settings when it is a plugin form, or by editing the raw markup when it is a
|
|
2285
|
+
custom html/text/code element.
|
|
2286
|
+
**Severity.** Warning — a visual/consistency defect, not a functional one.
|
|
2287
|
+
Fires when the element carries NO class at all, or already carries a
|
|
2288
|
+
DIFFERENT real UIkit class (on-system, just incomplete) — never confuse this
|
|
2289
|
+
with `design-foreign-class` below.
|
|
2290
|
+
|
|
2291
|
+
#### `design-foreign-class` — design · warning
|
|
2292
|
+
|
|
2293
|
+
**Why it matters.** The SAME contract-bearing universe as
|
|
2294
|
+
`design-missing-uikit-class`, but here the element DOES carry class(es) and
|
|
2295
|
+
NONE of them are real UIkit classes — positive evidence that a DIFFERENT
|
|
2296
|
+
framework (Bootstrap's `btn btn-primary`, a plugin's own `form-control`) is
|
|
2297
|
+
actively styling it instead of UIkit. This is a REPLACEMENT problem, not a
|
|
2298
|
+
silence: the fix is a class swap, not an addition.
|
|
2299
|
+
**How to fix.** No `fix_hint`. Replace the foreign class with UIkit's
|
|
2300
|
+
equivalent (`btn btn-primary` → `uk-button uk-button-primary`), typically in
|
|
2301
|
+
the plugin's own template/class settings rather than in YOOtheme.
|
|
2302
|
+
**Severity.** Warning. The locator's `content_snippet` shows the element's
|
|
2303
|
+
own markup WITH its class attribute — the exact evidence, never tag-stripped
|
|
2304
|
+
like every other snippet in this audit.
|
|
2305
|
+
|
|
2306
|
+
#### `design-inline-style` — design · warning
|
|
2307
|
+
|
|
2308
|
+
**Why it matters.** A `style=` attribute inside embedded content (a
|
|
2309
|
+
`<div style="color:red">` in an html/text/code element, or in a plugin's
|
|
2310
|
+
rendered output) hard-codes styling that bypasses the theme's design system
|
|
2311
|
+
entirely — it cannot be themed, cannot respond to a style/color-mode change,
|
|
2312
|
+
and is invisible to any CSS audit that only looks at stylesheets.
|
|
2313
|
+
**How to fix.** No `fix_hint`. Move the styling into a UIkit class or the
|
|
2314
|
+
theme's own CSS, or remove it if it was accidental (a rich-text editor
|
|
2315
|
+
sometimes injects one). ANY tag can carry this finding — there is no
|
|
2316
|
+
tag restriction, unlike the two class-based checks above.
|
|
2317
|
+
**Severity.** Warning.
|
|
2318
|
+
|
|
2319
|
+
All three are `appliesTo: per-page` but — unlike `anchor-text-quality` /
|
|
2320
|
+
`generic-alt-text` / `long-heading` above — they deliberately do NOT ride the
|
|
2321
|
+
`quick_check` block on `page_publish` / `page_save`: `quick_check` is a
|
|
2322
|
+
publish-time advisory scoped to what a single-template pass can see without a
|
|
2323
|
+
live vocabulary, and design detection needs the site's actual compiled UIkit
|
|
2324
|
+
CSS. Run `pages_audit` (ideally `mode: "rendered"`) to see these three markup
|
|
2325
|
+
findings. The two `header-contrast-*` codes below skip `quick_check` for the
|
|
2326
|
+
same reason but are **builder-mode only** — `mode: "rendered"` never emits them.
|
|
2327
|
+
|
|
2328
|
+
#### `header-contrast-risk` — design · warning
|
|
2329
|
+
|
|
2330
|
+
**Why it matters.** The page's FIRST section pulls the navigation over itself
|
|
2331
|
+
(`header_transparent`) and forces a `header_transparent_text_color` the site's
|
|
2332
|
+
own theme does not declare for that section style. The forced colour wins —
|
|
2333
|
+
`.uk-inverse-light` / `.uk-inverse-dark` carry `!important` and beat the
|
|
2334
|
+
style's own declaration — so this is not a taste question: it is white
|
|
2335
|
+
navigation on a white hero, or dark navigation on a dark one. Nothing in the
|
|
2336
|
+
builder warns about it. See "Transparent header (the exception — never a
|
|
2337
|
+
default)" for the whole trap family.
|
|
2338
|
+
|
|
2339
|
+
**One class only, and it is decidable.** The page entry carries `risk_class`,
|
|
2340
|
+
`confidence`, `text_color`, `recommended_text_color`, `style` and
|
|
2341
|
+
`media_backdrop`:
|
|
2342
|
+
|
|
2343
|
+
- **`risk_class: "B"`, `confidence: "high"`** — no image or video covers the
|
|
2344
|
+
section background, so the contradiction is decidable from the layout alone.
|
|
2345
|
+
`recommended_text_color` is the theme's OWN declared inverse for that style
|
|
2346
|
+
(READ from the compiled `theme.css` — not a guess, not a computed contrast
|
|
2347
|
+
ratio).
|
|
2348
|
+
|
|
2349
|
+
**`pages_audit` never emits this code over a hero image or video.** A medium
|
|
2350
|
+
covers exactly the background `--uk-inverse` describes, so there is nothing to
|
|
2351
|
+
contradict and nothing to recommend; that state is the
|
|
2352
|
+
`header-contrast-unverifiable` info finding below, at one severity whatever the
|
|
2353
|
+
colour. A transparent header you do not see reported here is therefore *not*
|
|
2354
|
+
endorsed — look for the info finding.
|
|
2355
|
+
|
|
2356
|
+
**How to fix.** `fix_hint` → `header_transparency_set` with `mode: "auto"` and
|
|
2357
|
+
`dry_run: true`. The dry-run reports the current state, the theme-declared
|
|
2358
|
+
inverse, `risk_class`, `confidence` and exactly what would change; repeat with
|
|
2359
|
+
`dry_run: false` + an `etag` to write it. The tool resolves the FIRST section
|
|
2360
|
+
itself — you do not pass an `element_path`, and the repair therefore cannot
|
|
2361
|
+
land on a section whose props YOOtheme never reads. `mode: "auto"` writes the
|
|
2362
|
+
theme's own declared value, so no colour is ever guessed. Turning the
|
|
2363
|
+
transparent header off entirely is always the safe direction and is never
|
|
2364
|
+
refused (`transparent: false`).
|
|
2365
|
+
|
|
2366
|
+
**Severity.** Warning. It never blocks a write, and it is per-page but does NOT
|
|
2367
|
+
ride `quick_check` (same reason as the three design codes above: the verdict
|
|
2368
|
+
needs the site's compiled `theme.css`, which the publish-time pass does not
|
|
2369
|
+
read). **When that CSS cannot be read, or the theme declares nothing for that
|
|
2370
|
+
style, there is no finding at all** — an unreadable theme yields silence, never
|
|
2371
|
+
a guess. It is also **builder-mode-only**: `header_transparent*` are section
|
|
2372
|
+
props, so `mode: "rendered"` never emits this code — a clean rendered audit is
|
|
2373
|
+
NOT evidence that the header is fine.
|
|
2374
|
+
|
|
2375
|
+
**What it does NOT report.** A dead prop (a colour set while
|
|
2376
|
+
`header_transparent` is off) and props on a non-first section are not contrast
|
|
2377
|
+
risks and are not reported here — see the transparent-header section for those
|
|
2378
|
+
two traps.
|
|
2379
|
+
|
|
2380
|
+
#### `header-contrast-unverifiable` — design · info
|
|
2381
|
+
|
|
2382
|
+
**Why it matters.** The page's FIRST section pulls the navigation over itself
|
|
2383
|
+
AND carries a hero image or video —
|
|
2384
|
+
**whatever `header_transparent_text_color` holds**.
|
|
2385
|
+
Nothing here is provable: the theme's `--uk-inverse` declaration
|
|
2386
|
+
describes the section's BACKGROUND COLOUR, and the medium hides exactly that
|
|
2387
|
+
background, so "is the navigation readable?" cannot be answered from the layout
|
|
2388
|
+
at all. Which part of a `cover` image ends up under the header depends on the
|
|
2389
|
+
viewport.
|
|
2390
|
+
|
|
2391
|
+
`risk_class: "C"`, `confidence: "low"`, `recommended_text_color: null`.
|
|
2392
|
+
|
|
2393
|
+
**`null`, not `""` — this code recommends no colour at all.**
|
|
2394
|
+
`""` is not the humble answer: it is the theme's default colour, a concrete
|
|
2395
|
+
rendered value, so writing it back over a colour someone set deliberately (or
|
|
2396
|
+
measured in a browser) is a change, not an abstention. Over a dark hero photo
|
|
2397
|
+
on a `--uk-inverse: dark` style, `""` renders dark navigation on a dark image.
|
|
2398
|
+
|
|
2399
|
+
**One severity for the whole media case, deliberately.** An earlier revision
|
|
2400
|
+
graded it — `""` info, any other colour a warning — and that punished the
|
|
2401
|
+
correct action: the colour a browser measurement finds readable over a dark
|
|
2402
|
+
hero is usually the one the theme does NOT declare, so an eyes-verified page
|
|
2403
|
+
scored a warning whose remedy was to revert it. Behind a medium there is no
|
|
2404
|
+
baseline to deviate from, so there is nothing to grade.
|
|
2405
|
+
|
|
2406
|
+
**How to fix.** No `fix_hint`, because there is nothing to write. **Look at
|
|
2407
|
+
it** — render the page (see "Look at what you built") at the customer's
|
|
2408
|
+
viewport, unscrolled AND after scrolling, and read the navigation. If it is
|
|
2409
|
+
unreadable, set the colour that IS readable over that medium with
|
|
2410
|
+
`header_transparency_set({ mode: "explicit", text_color: … })` —
|
|
2411
|
+
a value you have seen, never one
|
|
2412
|
+
you inferred. `mode: "auto"` deliberately writes no colour here, so it cannot
|
|
2413
|
+
help you: it has not seen the page either. If it reads fine, say so and move on.
|
|
2414
|
+
|
|
2415
|
+
**The finding stays at `info` afterwards, and that is correct.** A transparent
|
|
2416
|
+
header over a medium is never provable from props, so the honest end state is
|
|
2417
|
+
"no warning, still only verifiable by eye" — not "clean". Report it as verified
|
|
2418
|
+
and move on; do not go looking for a colour that makes it disappear.
|
|
2419
|
+
|
|
2420
|
+
**Severity.** Info — an open question, not a defect. Same fail-open rule as the
|
|
2421
|
+
warning above: no readable `theme.css`, no finding. Same builder-mode-only
|
|
2422
|
+
restriction, same absence from `quick_check`.
|
|
2423
|
+
|
|
1802
2424
|
### The report contract — how to present an audit
|
|
1803
2425
|
|
|
1804
2426
|
The raw response is a machine payload. What you hand the user follows this
|
|
@@ -1812,8 +2434,8 @@ shape every time, so two audits of two sites read the same way:
|
|
|
1812
2434
|
2. **Order by severity: critical → warning → info.** The server already sorts
|
|
1813
2435
|
that way; never re-rank findings by how easy they look to fix.
|
|
1814
2436
|
3. **Group page → category → finding.** One block per affected page, its
|
|
1815
|
-
findings grouped by `category` (`structure`, `content`, `seo`, `links
|
|
1816
|
-
the user can fix one page in one pass instead of hopping between pages per
|
|
2437
|
+
findings grouped by `category` (`structure`, `content`, `seo`, `links`,
|
|
2438
|
+
`design`), so the user can fix one page in one pass instead of hopping between pages per
|
|
1817
2439
|
code.
|
|
1818
2440
|
4. **Render every hit from its locator**, in this shape:
|
|
1819
2441
|
|
|
@@ -1832,7 +2454,13 @@ shape every time, so two audits of two sites read the same way:
|
|
|
1832
2454
|
`element_type`) plus the `breadcrumb` that locates it in the builder. A **bare
|
|
1833
2455
|
path list is NOT acceptable output** — `/children/0/children/2` tells the user
|
|
1834
2456
|
nothing about which box on their page to click. Add the page's `builder_url`
|
|
1835
|
-
as the "open in builder" link
|
|
2457
|
+
as the "open in builder" link and its `frontend_url` as the "view live" link,
|
|
2458
|
+
whenever the response carries them — every finding page carries both today
|
|
2459
|
+
(previously only the nine element-located checks carried `builder_url`, and
|
|
2460
|
+
only `non-pretty-url` carried `frontend_url`). A theme/archive/internal
|
|
2461
|
+
template with no public route or no resolvable admin base legitimately omits
|
|
2462
|
+
one or both — that is not a bug, and it is not the agent's job to look the
|
|
2463
|
+
URL up separately via pages_list/pages_resolve when it is missing here.
|
|
1836
2464
|
6. **State what did and did not run.** On a site sweep, say the site-wide checks
|
|
1837
2465
|
ran (`duplicate-layout`, `title-collision` compare pages against each other).
|
|
1838
2466
|
On a single-page audit, say they did not — reuse the response's own `note`
|
|
@@ -2087,6 +2715,493 @@ to re-read the entire layout after a wrong-path error.
|
|
|
2087
2715
|
> `{given, nearest_path, nearest_path_label, available_paths}` envelope is
|
|
2088
2716
|
> byte-identical across WordPress and Joomla.
|
|
2089
2717
|
|
|
2718
|
+
## Response budgets — how a read tool shortens an oversized answer
|
|
2719
|
+
|
|
2720
|
+
Every high-volume read tool takes `max_chars`. When a response does not fit,
|
|
2721
|
+
it is **shortened by dropping whole entries, never by cutting the JSON**, and
|
|
2722
|
+
it tells you so. Read the verdict before you report a result as complete:
|
|
2723
|
+
|
|
2724
|
+
- `truncated: true` — this response is not the whole answer.
|
|
2725
|
+
- `omitted_count: N` — exactly how many entries were left out.
|
|
2726
|
+
- `next_cursor` — present whenever the resume point is derivable; pass it back
|
|
2727
|
+
as `cursor` to read the entries that were dropped. It is **omitted rather
|
|
2728
|
+
than guessed**: no cursor means the tool could not derive one, not that
|
|
2729
|
+
nothing is missing. Use `omitted_count` in that case. A cursor that could not
|
|
2730
|
+
be rewound past the dropped entries is **removed, never left stale**, so a
|
|
2731
|
+
`next_cursor` you receive is always safe to follow.
|
|
2732
|
+
- `omitted_keys: [...]` — on a single-object response, which fields were shed
|
|
2733
|
+
(the small identifying ones like `template_id` / `etag` are kept last).
|
|
2734
|
+
- `retry_hint` — the concrete lever: a higher `max_chars`, a narrower
|
|
2735
|
+
`fields[]` projection, or a smaller `limit`.
|
|
2736
|
+
|
|
2737
|
+
The payload always parses — on the plugin's own `/mcp` endpoint the text leg
|
|
2738
|
+
itself is reduced entry by entry, and through this Node server a shortened text
|
|
2739
|
+
preview always travels beside a complete `structuredContent` leg carrying the
|
|
2740
|
+
full result. So read `structuredContent` when the text leg looks cut. If you
|
|
2741
|
+
ever receive a response where **neither** leg parses, that is a bug — report it
|
|
2742
|
+
rather than working around it.
|
|
2743
|
+
|
|
2744
|
+
Both transports carry that structured leg, but **they do not put the same thing
|
|
2745
|
+
in it**. Over stdio it is the complete result. The plugin's own `/mcp` endpoint
|
|
2746
|
+
puts the **same shortened payload in both legs** — reading its `structuredContent`
|
|
2747
|
+
gives you exactly what its text leg already said, no more. That is a constraint,
|
|
2748
|
+
not a preference: on WordPress the host plugin's MCP adapter builds both legs
|
|
2749
|
+
from one returned value, so a bounded text leg beside a complete structured one
|
|
2750
|
+
cannot exist there. Decide whether an answer is whole from `truncated` /
|
|
2751
|
+
`omitted_count`, never from which leg you happened to read.
|
|
2752
|
+
|
|
2753
|
+
The reverse is also true: **absence of `truncated` means the answer is
|
|
2754
|
+
complete.** Do not hedge a full response with "there may be more".
|
|
2755
|
+
|
|
2756
|
+
Two levers beat raising `max_chars`, because they reduce what is produced
|
|
2757
|
+
rather than what survives: ask for fewer fields (`fields[]`) and fewer rows
|
|
2758
|
+
(`limit` + `cursor`). Reach for `max_chars` last.
|
|
2759
|
+
|
|
2760
|
+
Every default is derived from measured response sizes, not chosen, and the same
|
|
2761
|
+
default applies on both transports — the proxy and the plugin's own `/mcp` are
|
|
2762
|
+
two paths to one tool (see `docs/mcp-response-budgets.md`). A derived class is
|
|
2763
|
+
applied **raise-only**: measurement may lift a default, never lower it, so no
|
|
2764
|
+
upgrade silently returns less than the version before it.
|
|
2765
|
+
|
|
2766
|
+
### Ask for the fields you need — `element_type_get_schema`
|
|
2767
|
+
|
|
2768
|
+
A big element type is the sharpest case: `grid` declares **154** field
|
|
2769
|
+
descriptors. Do not read all of them and do not raise the cap — **name what you
|
|
2770
|
+
want**:
|
|
2771
|
+
|
|
2772
|
+
- `field_names: ["image_expand","grid_masonry"]` — exact names, case-insensitive.
|
|
2773
|
+
- `name_contains: "height"` — substring of the field's **name OR label**, so it
|
|
2774
|
+
also finds `image_expand`, whose label is "Height".
|
|
2775
|
+
- Both together **UNION** — each argument names what you want; adding one never
|
|
2776
|
+
removes what the other selected.
|
|
2777
|
+
|
|
2778
|
+
A selected descriptor comes back **COMPLETE** — its `enable` condition, `enum`
|
|
2779
|
+
and `default` are all there, which is exactly what a truncated read destroyed.
|
|
2780
|
+
Every response carries `field_count` (what the type declares) and
|
|
2781
|
+
`returned_count` (what you received), so a narrowed answer is never silent and
|
|
2782
|
+
you always know the size of what you did not ask for. A pattern that matches
|
|
2783
|
+
nothing returns an **empty list plus the counts, not an error**.
|
|
2784
|
+
|
|
2785
|
+
`field_names`/`name_contains` pick WHICH descriptors; `fields[]` picks the
|
|
2786
|
+
sub-keys of each one. They compose — selection runs first, so
|
|
2787
|
+
`name_contains:"height", fields:["name","enable"]` is a legitimate call.
|
|
2788
|
+
|
|
2789
|
+
### Read a field's MEANING, not just its `label` — `text` and `description`
|
|
2790
|
+
|
|
2791
|
+
A descriptor carries YOOtheme's own two meaning-bearing keys, and on a
|
|
2792
|
+
**checkbox** they are not optional reading:
|
|
2793
|
+
|
|
2794
|
+
- **A checkbox's value is a boolean (`true`/`false`); its MEANING is in `text`,
|
|
2795
|
+
because `label` on a checkbox is the GROUP heading, not the option.** `grid`'s
|
|
2796
|
+
equal-height option is
|
|
2797
|
+
`{"name":"image_expand","type":"checkbox","label":"Height","text":"Expand image","enable":"!grid_masonry"}`.
|
|
2798
|
+
Read `label` alone and it says "Height: true/false" — which is not what the
|
|
2799
|
+
option does. The caption rendered next to the box is `text`: "Expand image".
|
|
2800
|
+
Same shape for `image_loading` ("Loading" / "Load image eagerly"), `panel_link`
|
|
2801
|
+
("Link" / "Link panel") and `panel_match` ("Height" / "Match panel heights").
|
|
2802
|
+
- **`description` is the Builder's own tooltip** for the field, e.g.
|
|
2803
|
+
`grid.panel_content_width` → "Set an optional content width which doesn't
|
|
2804
|
+
affect the image if there is just one column."
|
|
2805
|
+
|
|
2806
|
+
Both keys are **absent when YOOtheme declares none**, so a descriptor without
|
|
2807
|
+
`text` means "this field has no caption", never "the caption was dropped".
|
|
2808
|
+
|
|
2809
|
+
### When the field says nothing, the GROUP does — `group` and `groups[]`
|
|
2810
|
+
|
|
2811
|
+
**YOOtheme parks the semantics of a whole class of fields on the enclosing
|
|
2812
|
+
fieldset GROUP, not on the field.** So a descriptor with no `label` and no
|
|
2813
|
+
`description` is not undocumented — you are reading at the wrong level.
|
|
2814
|
+
`group` names the nearest enclosing group (the heading the Builder renders beside
|
|
2815
|
+
the field), and the schema's **root `groups[]`** states that group's own sentence
|
|
2816
|
+
once. Join the two on the group name:
|
|
2817
|
+
|
|
2818
|
+
`{"name":"image_width","type":"number","placeholder":"auto","enable":"show_image || show_video","group":"Width/Height"}`
|
|
2819
|
+
|
|
2820
|
+
`"groups":[{"name":"Width/Height","description":"Setting just one value preserves the original proportions. The image will be resized and cropped automatically, and where possible, high resolution images will be auto-generated."}]`
|
|
2821
|
+
|
|
2822
|
+
Read the field alone and `image_width` looks like a plain number. The rule you
|
|
2823
|
+
actually needed — **set only ONE of width/height and the proportions are kept;
|
|
2824
|
+
set BOTH and the image is CROPPED** — lives on the group shared by `image_width`
|
|
2825
|
+
and `image_height`. Same shape for the `lightbox_image_*` pair.
|
|
2826
|
+
|
|
2827
|
+
- The sentence is the **group's**, never re-labelled as the field's, so a field
|
|
2828
|
+
may carry its own `description` while its group carries another at once —
|
|
2829
|
+
they are two different statements. It sits at the root because it is shared:
|
|
2830
|
+
measured over nine element types, 32 fields carried only 5 distinct sentences,
|
|
2831
|
+
one of them 20 times.
|
|
2832
|
+
- `groups[]` **narrows with your selection** — a `field_names` /`name_contains`
|
|
2833
|
+
read carries only the groups its descriptors name, never the whole type's.
|
|
2834
|
+
- `placeholder` is `attrs.placeholder`, which for several fields IS the
|
|
2835
|
+
documented fallback: `grid.image_width` → `"auto"`, `countdown.label_days` →
|
|
2836
|
+
`"Days"`. An empty value renders the placeholder, it does not render empty.
|
|
2837
|
+
|
|
2838
|
+
### What an enum VALUE does not say — `enum_labels`
|
|
2839
|
+
|
|
2840
|
+
`enum` carries the values you write. `enum_labels` carries the Builder's label for
|
|
2841
|
+
a value **where the value does not already carry it**:
|
|
2842
|
+
|
|
2843
|
+
`{"name":"grid_default","type":"select","label":"Phone Portrait","enum":["1","2","3","4","5","6","auto"],"default":"1","enum_labels":{"1":"1 Column","2":"2 Columns","3":"3 Columns","4":"4 Columns","5":"5 Columns","6":"6 Columns"}}`
|
|
2844
|
+
|
|
2845
|
+
Without it, `["1"…"6"]` is six digits with nothing saying they are **column
|
|
2846
|
+
counts**; `grid_column_gap`'s `"collapse"` is a word with nothing saying the
|
|
2847
|
+
Builder calls it **None**.
|
|
2848
|
+
|
|
2849
|
+
- **A value with no entry is not unlabelled.** Its label is the value itself, up
|
|
2850
|
+
to case and punctuation — `"small"` is "Small", `"arrow-right"` is
|
|
2851
|
+
"Arrow Right". Only genuine renamings are listed, which is why the key is
|
|
2852
|
+
affordable: measured over nine element types, 611 of 2043 label/value pairs
|
|
2853
|
+
(29.9 %) are genuine renamings and the other 1432 are silent.
|
|
2854
|
+
- **`""` is a real choice, not a gap.** YOOtheme uses the empty string as a
|
|
2855
|
+
meaningful selection — `panel.video_autoplay` declares `'On' => ''`,
|
|
2856
|
+
`switcher_item.item_element` declares `'div' => ''` — and since an empty value
|
|
2857
|
+
carries no characters at all, its label is always in `enum_labels`
|
|
2858
|
+
(`{"":"On"}`). So `""` in an `enum` is a selection to make.
|
|
2859
|
+
- **A missing entry can also mean "not decidable".** When two options collapse
|
|
2860
|
+
onto the same value — `'On' => ''` beside `'No' => false`, because `false`
|
|
2861
|
+
stringifies to `""` — neither label is emitted for that value rather than one
|
|
2862
|
+
guessed. The same value in a DIFFERENT field may legitimately mean something
|
|
2863
|
+
else: in `grid`, `""` carries twelve different labels across its fields, and
|
|
2864
|
+
`"1-2"` is "50%" on one field and "Halves" on another. Read `enum_labels` per
|
|
2865
|
+
field; never carry one field's label to another.
|
|
2866
|
+
|
|
2867
|
+
### A prop that is declared and does nothing — `no_effect`
|
|
2868
|
+
|
|
2869
|
+
A few props are declared in YOOtheme's config, ship a label and a tooltip, and
|
|
2870
|
+
are **read by no consumer**. For those the descriptor drops `text`/`description`
|
|
2871
|
+
— forwarding a promise nothing keeps is worse than silence — and carries
|
|
2872
|
+
`no_effect` with what was actually read instead:
|
|
2873
|
+
|
|
2874
|
+
`{"name":"lightbox_image_orientation","type":"checkbox","label":"Image Orientation","no_effect":"No effect in YOOtheme Pro 5.0.37: declared in grid/element.php and gallery/element.php only, read by no template. Writing it changes nothing.","enable":"lightbox","group":"Lightbox"}`
|
|
2875
|
+
|
|
2876
|
+
**A descriptor with `no_effect` is not a prop to set** — the write succeeds and
|
|
2877
|
+
nothing changes. The other one today is `panel-slider.show_hover_video`, dead
|
|
2878
|
+
because `panel-slider_item` lists `hover_image` twice and `hover_video` never.
|
|
2879
|
+
The prop stays writable on purpose: a stored layout may already carry the key,
|
|
2880
|
+
and rejecting it would break a round-trip. `enable` and `label` survive too —
|
|
2881
|
+
a Builder-UI condition and a UI name are not behaviour claims.
|
|
2882
|
+
|
|
2883
|
+
### Which build a claim was read on — `claims_verified_on` and `no_effect_unverified`
|
|
2884
|
+
|
|
2885
|
+
That evidence was read in **one** YOOtheme tree. A site running a different
|
|
2886
|
+
build is a site nobody has read, so the response says which is which:
|
|
2887
|
+
|
|
2888
|
+
`"claims_verified_on":{"build":"5.0.37","here":"4.5.33","status":"differs","keys":["rule_notes","no_effect_unverified"]}`
|
|
2889
|
+
|
|
2890
|
+
- **`status:"match"`** — the claims were read on the build you are running.
|
|
2891
|
+
Take them as fact.
|
|
2892
|
+
- **`status:"differs"`** — read on `build`, not re-checked on `here`.
|
|
2893
|
+
- **`status:"unknown"`** — the site's YOOtheme version could not be resolved
|
|
2894
|
+
(`here` is `null`). It never means "probably the same".
|
|
2895
|
+
|
|
2896
|
+
`here` is resolved per host — from YOOtheme's runtime on WordPress, from the
|
|
2897
|
+
installed `yootheme` template manifest on Joomla — and on both it is the SAME
|
|
2898
|
+
value `yootheme_builder_health` reports as `yootheme_version`. So the two must
|
|
2899
|
+
agree: **if health names a build while this record says `unknown`, that is a
|
|
2900
|
+
resolver bug, not a fact about your site.** Check health first.
|
|
2901
|
+
|
|
2902
|
+
Off a `match`, `no_effect` **is not emitted at all**. The finding ships under
|
|
2903
|
+
**`no_effect_unverified`** instead, naming both builds and ending in `UNKNOWN`,
|
|
2904
|
+
and `text`/`description` **stay** — those are YOOtheme's own words read from
|
|
2905
|
+
*your* build's live registry, so they are true for you, and only a verified
|
|
2906
|
+
deadness earns their removal. Every `rule_notes` entry is likewise prefixed
|
|
2907
|
+
`[Read in YOOtheme Pro <build>. … — unverified here.]`.
|
|
2908
|
+
|
|
2909
|
+
**`differs` does not mean the claim is false on your build** — it means nobody
|
|
2910
|
+
looked. Treat `no_effect_unverified` as a strong reason to check before relying
|
|
2911
|
+
on the prop, not as permission to assume it works.
|
|
2912
|
+
|
|
2913
|
+
**The record scopes exactly the keys it lists in `keys` — nothing else in the
|
|
2914
|
+
response.** It is emitted when, and only when, that list is non-empty, so scope
|
|
2915
|
+
and trigger are one thing and cannot drift apart. What can appear there:
|
|
2916
|
+
|
|
2917
|
+
| Key | Why it is build-scoped |
|
|
2918
|
+
|---|---|
|
|
2919
|
+
| `renders_only_if`, `blanked_by_parent`, `media_autoswap` | render gates read in one tree |
|
|
2920
|
+
| `rule_notes` | the shared render-rule texts, all read in that same tree |
|
|
2921
|
+
| `no_effect` / `no_effect_unverified` | whichever of the two actually shipped |
|
|
2922
|
+
| `value_hint` | **only** when the hint is one of the render-template reads: a value-FORMAT trap (`countdown.date`, `map_item.location`, `map.width`/`height`, `popover_item.position_x`/`_y`, `table.table_order`) or `image_expand` |
|
|
2923
|
+
|
|
2924
|
+
Everything else is **outside** it, whatever its own provenance — including
|
|
2925
|
+
`binding_contract`, whose `child_type` comes from a container/item table read on
|
|
2926
|
+
a *different* YOOtheme build, and `groups`, `enum_labels`, `semantic_role` and
|
|
2927
|
+
the descriptors themselves, which come from your own build's live registry.
|
|
2928
|
+
`placeholder_fallback` is outside for that second reason: it is derived from your
|
|
2929
|
+
own config, so it is true here by construction.
|
|
2930
|
+
|
|
2931
|
+
A `value_hint` that is **not** named in `keys` carries no single-build scope — the
|
|
2932
|
+
image/icon shape hints, the grid-layout string and the type scale state a
|
|
2933
|
+
convention rather than a render reading, and `title_element`'s per-type defaults
|
|
2934
|
+
were verified against **both** the 4.5.33 and the YT5 element sources. Do not read
|
|
2935
|
+
`keys` as "these are the only claims in the response"; read it as "these are the
|
|
2936
|
+
claims this record vouches for."
|
|
2937
|
+
|
|
2938
|
+
### Read what no field text can say — `value_hint`
|
|
2939
|
+
|
|
2940
|
+
`text` and `description` are YOOtheme's own words. `value_hint` is ours, and it
|
|
2941
|
+
carries only what lives in the element's **render template** — a rule no field
|
|
2942
|
+
definition can state. The equal-height option is the sharpest case, and it is
|
|
2943
|
+
**per element**:
|
|
2944
|
+
|
|
2945
|
+
- **A `grid` is ALREADY column-matched with no option set** — its container always
|
|
2946
|
+
renders `uk-grid-match` (sole exception: `grid_masonry`). Switching
|
|
2947
|
+
`image_expand` on to equalise the **columns** solves a solved problem: what it
|
|
2948
|
+
expands is the **IMAGE**. The media element takes `flex:1`, and because every
|
|
2949
|
+
`img` renders with `object-fit:cover`, the added height **crops** the picture.
|
|
2950
|
+
- **The order matters and the wrong order fails SILENTLY.** `image_expand` needs
|
|
2951
|
+
its gate set FIRST: `panel_match` on `panel-slider`, `height_expand` on
|
|
2952
|
+
`panel`, `grid_masonry` OFF on `grid` and `gallery`. Otherwise the element's
|
|
2953
|
+
template **blanks the value at RENDER** — the write succeeds and the value stays
|
|
2954
|
+
stored, so there is no error anywhere. **A field's `enable` condition does not
|
|
2955
|
+
protect you**: it is a Builder-UI condition only — no render path reads it, so it
|
|
2956
|
+
never guards an API write. Read the field's own `value_hint` for that element's exact condition
|
|
2957
|
+
(most gates have an `image_align: left`/`right` escape; `gallery` has none).
|
|
2958
|
+
- On the two sliders the render can also **FORCE it on**. With `slider_width`
|
|
2959
|
+
unset, `panel-slider` forces `panel_match` and `image_expand` to true, and
|
|
2960
|
+
`overlay-slider` forces `image_expand` to true — as it also does whenever
|
|
2961
|
+
`height_expand`, `slider_min_height` or `height_viewport` is set. There an
|
|
2962
|
+
`image_expand: false` write is accepted and stays stored, then replaced by true
|
|
2963
|
+
at **RENDER**: on those settings the option cannot be switched off at all.
|
|
2964
|
+
- **The fixed-size alternative:** set **BOTH** `image_width` and `image_height` —
|
|
2965
|
+
both numeric crops server-side to exactly that box, one alone only resizes
|
|
2966
|
+
proportionally.
|
|
2967
|
+
|
|
2968
|
+
`name_contains: "expand"` returns that one descriptor with the whole rule set.
|
|
2969
|
+
|
|
2970
|
+
### Will this element render at all? — `renders_only_if`, `placeholder_fallback` and `blanked_by_parent`
|
|
2971
|
+
|
|
2972
|
+
The schema root carries three arrays that decide whether a write is visible **at
|
|
2973
|
+
all**. They are element-level, so they are present on **every** read of that type
|
|
2974
|
+
— a `field_names` selection does not narrow them away.
|
|
2975
|
+
|
|
2976
|
+
**`renders_only_if` — an element with PRESENT-but-EMPTY required props is DELETED at render.**
|
|
2977
|
+
Not hidden, not empty: removed from the tree, with no error anywhere. It is a
|
|
2978
|
+
list of groups, and **ALL groups need at least one non-empty member**:
|
|
2979
|
+
|
|
2980
|
+
`"renders_only_if":[["title","meta","content","image","video","icon"]]` ← `grid_item`
|
|
2981
|
+
|
|
2982
|
+
`"renders_only_if":[["title"],["content","image","link"]]` ← `accordion_item`, which needs a title **and** one of the three
|
|
2983
|
+
|
|
2984
|
+
> **Read `placeholder_fallback` before concluding "nothing renders".** For props
|
|
2985
|
+
> you simply never wrote, the answer on **27 of the 28 gated element types** is
|
|
2986
|
+
> not deletion — see the next subsection. `renders_only_if` alone over-states it.
|
|
2987
|
+
|
|
2988
|
+
- **`link` is almost never a keeper.** It is absent from the gate of `grid_item`,
|
|
2989
|
+
`gallery_item`, `list_item`, `table_item`, `description_list_item` and `panel`,
|
|
2990
|
+
so a card carrying **only a link plus link text renders nothing at all**.
|
|
2991
|
+
`social_item` is the one element where a bare `link` is the whole gate — which
|
|
2992
|
+
is why this is a per-element read, never a blanket rule.
|
|
2993
|
+
- **The emptied parent goes too.** A container whose children all dropped is
|
|
2994
|
+
itself dropped, cascading column → row → section. So one unsatisfied gate on a
|
|
2995
|
+
repeated item can blank a whole section.
|
|
2996
|
+
- A member may be a `&&`-joined conjunction, and a `parent.` prefix addresses the
|
|
2997
|
+
**container's** prop: `switcher_item`'s second group lists
|
|
2998
|
+
`"title&&parent.show_title"`, i.e. with the container's `show_title` off a title
|
|
2999
|
+
alone does not satisfy it — meta, content or image is then required.
|
|
3000
|
+
- Media-only elements are the mirror image: `overlay`, `overlay-slider_item`,
|
|
3001
|
+
`slideshow_item` and `gallery_item` need an image or video and drop a text-only
|
|
3002
|
+
item.
|
|
3003
|
+
|
|
3004
|
+
**`placeholder_fallback` — an UNSET prop does not delete the element; it publishes
|
|
3005
|
+
YOOtheme's placeholder to the live page.** This is the difference between
|
|
3006
|
+
*absent* and *present-but-empty*, and absent is the normal shape of an MCP write:
|
|
3007
|
+
|
|
3008
|
+
`"placeholder_fallback":{"suppressed_by":["title","meta","content","image","video","icon","hover_image","hover_video"],"fills":["title","content"]}` ← `grid_item`
|
|
3009
|
+
|
|
3010
|
+
- **`fills` is what your visitors see.** YOOtheme merges the element type's own
|
|
3011
|
+
placeholder props *before* the gate is evaluated, on the **front end** as well
|
|
3012
|
+
as in the customizer, so the gate is satisfied by content you never wrote:
|
|
3013
|
+
lorem ipsum, the literal string `"Title"`, `"Headline"`, the icon `"star"`, a
|
|
3014
|
+
demo date, or `assets/images/element-image-placeholder.png`. On `overlay`, for
|
|
3015
|
+
instance, YOOtheme injects **both** its placeholder image **and** the literal
|
|
3016
|
+
text `"Title"` into the published page.
|
|
3017
|
+
- **`suppressed_by` is your off switch.** Write **any** key in that list —
|
|
3018
|
+
including to `""` — and the merge is skipped entirely for the whole element.
|
|
3019
|
+
Only then does `renders_only_if` delete. It is all-or-nothing: one written key
|
|
3020
|
+
suppresses every fill, not just its own.
|
|
3021
|
+
- **So there are three outcomes, not two.** Leave the props unset → placeholder
|
|
3022
|
+
content ships. Write one gate prop with real content → your content ships.
|
|
3023
|
+
Write a gate prop as `""` → the element is deleted. If you want an element
|
|
3024
|
+
gone, delete it; do not rely on leaving props unset.
|
|
3025
|
+
- **`social_item` is the one gated type with no placeholder at all**, so no key
|
|
3026
|
+
is present and an absent `link` really does drop the node. Absence of
|
|
3027
|
+
`placeholder_fallback` therefore means the gate is literal.
|
|
3028
|
+
|
|
3029
|
+
**`blanked_by_parent` — a container's `show_*` flag is NOT CSS visibility, and it
|
|
3030
|
+
is NOT a write either.** Both halves matter, and a reader who takes only one acts
|
|
3031
|
+
wrongly. It **empties the child's prop in the render transform**, so the value is
|
|
3032
|
+
gone from the **rendered HTML** while **the stored layout still holds it,
|
|
3033
|
+
unchanged** — flip the flag back and the render returns. The emptied value then
|
|
3034
|
+
feeds the child's own `renders_only_if`, in the same function, so clearing one
|
|
3035
|
+
flag can delete the **entire item**, not just the field you meant to hide:
|
|
3036
|
+
|
|
3037
|
+
`"blanked_by_parent":{"show_title":["title"],"show_image":["image","icon"],"show_link":["link"], …}`
|
|
3038
|
+
|
|
3039
|
+
- **MEASURED, YOOtheme Pro 5.0.37** — a two-item `grid`, written with
|
|
3040
|
+
`element_update_settings` + `page_publish`, read back with `page_get_layout`
|
|
3041
|
+
and from the published page. With `show_title: false` on the grid: rendered
|
|
3042
|
+
`.el-title` **2 → 0**, neither title string anywhere in the section HTML, and
|
|
3043
|
+
the grid's stored children subtree **byte-identical** before and after (equal
|
|
3044
|
+
sha256, 699 B, `diff` exit 0) — the whole-layout diff was **one added line**,
|
|
3045
|
+
`"show_title": false`, on the GRID node and nothing on any child. Setting it
|
|
3046
|
+
back to `true` restored the byte-identical baseline render. So **re-writing the
|
|
3047
|
+
props is never the fix; flipping the flag back is.**
|
|
3048
|
+
- **The whole item really does go — and takes its container.** In the state where
|
|
3049
|
+
the blanking left only `link` (which is not in `grid_item`'s gate), `.el-item`
|
|
3050
|
+
went **2 → 0** and the `.uk-grid` container with it (**2 → 1**; section HTML
|
|
3051
|
+
2221 → 609 chars) — while the read-back still carried `link` and `link_text` in
|
|
3052
|
+
full. Nothing rendered; nothing lost.
|
|
3053
|
+
- **A source-bound item behaves the same — measured, not assumed.** With
|
|
3054
|
+
`grid_item[0]` bound to a native source and `grid_item[1]` left literal,
|
|
3055
|
+
`show_title: false` blanked **both** (`.el-title` 4 → 0), and the read-back
|
|
3056
|
+
still showed the binding intact (`source.props.title`) on the bound item and
|
|
3057
|
+
the literal string on the static one. For a bound prop the surviving stored
|
|
3058
|
+
value is the **binding**, and it survives exactly as a literal does. Measured
|
|
3059
|
+
on the REST write path and the published page only — the customizer's own
|
|
3060
|
+
tick-and-save path was not measured, and nothing here is claimed about it.
|
|
3061
|
+
- **Intersect the two arrays to see the blast radius.** Where a blanked prop also
|
|
3062
|
+
appears in the gate, the item can vanish — true for nine of the thirteen item
|
|
3063
|
+
types that blank. In `nav_item`, `subnav_item`, `slideshow_item` and `map_item`
|
|
3064
|
+
it cannot: their gate is `content` / `content` / `image`+`video` / `location`,
|
|
3065
|
+
which no `show_*` blanks.
|
|
3066
|
+
- **`show_image` also wipes `icon`** on `grid_item`, `panel-slider_item`,
|
|
3067
|
+
`list_item`, `nav_item` and `subnav_item` — the array shows it, so read the
|
|
3068
|
+
values rather than assuming one flag clears one prop.
|
|
3069
|
+
- **Some flags blank from the TEMPLATE, not the transform** — `map_item`'s five
|
|
3070
|
+
and `switcher_item`'s `show_title`, which is why `switcher_item` carries seven
|
|
3071
|
+
flags and not the six its transform loop enumerates. These empty the value for
|
|
3072
|
+
**rendering** but do not feed the element's own gate, so do **not** intersect
|
|
3073
|
+
them with `renders_only_if`: `switcher_item`'s gate reads `title` raw in its
|
|
3074
|
+
first group and consults `show_title` itself in the second
|
|
3075
|
+
(`"title&&parent.show_title"`), and `map_item`'s gate is `location`, which no
|
|
3076
|
+
flag touches.
|
|
3077
|
+
- `table_item`, `social_item` and `button_item` have no blanking at all. They
|
|
3078
|
+
still have gates.
|
|
3079
|
+
- On `description_list_item`, `link` renders **only** as a wrapper around
|
|
3080
|
+
`content`, so losing `content` loses the link too — by a different route than
|
|
3081
|
+
the flag.
|
|
3082
|
+
|
|
3083
|
+
### `""` means INHERIT, not off — `empty_means_inherit`
|
|
3084
|
+
|
|
3085
|
+
A descriptor with `"empty_means_inherit":true` merges over its **container's**
|
|
3086
|
+
value. Because the empty string is falsy, writing it — which is what the
|
|
3087
|
+
Builder's **"None"** option stores — **re-inherits the parent** instead of
|
|
3088
|
+
switching the feature off. Item overrides are therefore **add-only**: a single
|
|
3089
|
+
item cannot be forced back to plain inside a styled container. **To make one item
|
|
3090
|
+
plain you must restyle the container, not the item.**
|
|
3091
|
+
|
|
3092
|
+
It is on **43 fields across 12 element types**, so read the flag rather than
|
|
3093
|
+
guessing the family: the styling props (`panel_style`, `*_text_color`, `media_*`)
|
|
3094
|
+
on `grid_item`, `gallery_item`, `panel-slider_item`, `overlay-slider_item` and
|
|
3095
|
+
`slideshow_item`, but also `link_text` on **ten** item types and
|
|
3096
|
+
`link_aria_label` on **eight** — **eleven** types carry one or the other,
|
|
3097
|
+
including `accordion_item`, `switcher_item` and `table_item` (`link_text` only)
|
|
3098
|
+
and `social_item` (`link_aria_label` only) — plus `list_item`'s `icon` and
|
|
3099
|
+
`image_svg_color` and `popover_item`'s `drop_position`. An empty `link_text` on a
|
|
3100
|
+
`table_item` shows the TABLE's link text, not no text.
|
|
3101
|
+
|
|
3102
|
+
### Which media actually renders — `media_autoswap`
|
|
3103
|
+
|
|
3104
|
+
Image/video collisions are resolved **per element, and YOOtheme is not
|
|
3105
|
+
consistent about it**. Read the key; there is no default to assume.
|
|
3106
|
+
|
|
3107
|
+
`"media_autoswap":{"precedence":["video","image","icon"],"sniffed":["image","video","hover_image","hover_video"]}`
|
|
3108
|
+
|
|
3109
|
+
- **`precedence`** — the first non-empty prop renders; the losers are simply not
|
|
3110
|
+
drawn. All seven builder media elements (`grid_item`, `panel`,
|
|
3111
|
+
`panel-slider_item`, `gallery_item`, `overlay`, `overlay-slider_item`,
|
|
3112
|
+
`slideshow_item`) are **video-first**, so setting both image and video shows the
|
|
3113
|
+
video.
|
|
3114
|
+
- **`sniffed`** — these props are re-classified by URL sniffing and the value is
|
|
3115
|
+
**MOVED** between `image` and `video`, nulling the other. **The field you wrote
|
|
3116
|
+
may not be the field that renders**: an `.mp4` in `image` ends up in `video`.
|
|
3117
|
+
- **`clears_on_conflict`** — `section` and `column` **invert** the precedence
|
|
3118
|
+
(image-first) **and** are destructive: with an image set, `video` is set to
|
|
3119
|
+
`false`, so anything derived from it downstream sees no video. These two are the
|
|
3120
|
+
only elements that do this.
|
|
3121
|
+
|
|
3122
|
+
### `rules` + `rule_notes` — the rules that need a sentence
|
|
3123
|
+
|
|
3124
|
+
The four keys above are arrays you can evaluate. Nine more rules have no array
|
|
3125
|
+
form, so they ship as prose — **stated once per response and referenced by id**,
|
|
3126
|
+
never repeated per prop:
|
|
3127
|
+
|
|
3128
|
+
```json
|
|
3129
|
+
{ "name": "slideshow",
|
|
3130
|
+
"rules": ["expand_model", "positional", "enable_is_ui_only"],
|
|
3131
|
+
"rule_notes": { "expand_model": "…", "positional": "…", "enable_is_ui_only": "…" },
|
|
3132
|
+
"fields": [ { "name": "slideshow_max_height", "enable": "!height_viewport",
|
|
3133
|
+
"rules": ["slideshow_max_height_vs_render"] } ] }
|
|
3134
|
+
```
|
|
3135
|
+
|
|
3136
|
+
- `rules` on the **element** = rules about the container's own template. They
|
|
3137
|
+
survive a `field_names` / `name_contains` selection, because they are true of
|
|
3138
|
+
the element whichever descriptors you asked for.
|
|
3139
|
+
- `rules` on a **field** = rules about that prop. Their notes are narrowed away
|
|
3140
|
+
when the field is not returned, so a two-descriptor read pays for two.
|
|
3141
|
+
- `rule_notes` resolves every id the response still references, and only those.
|
|
3142
|
+
|
|
3143
|
+
**`enable` and `show` are Builder-UI conditions only — they are not enforced.**
|
|
3144
|
+
In YOOtheme 5.0.37 the word `enable` appears **0×** in the theme's
|
|
3145
|
+
`packages/builder/src`, so no PHP render path reads either key and neither one
|
|
3146
|
+
guards an API write. They are still projected verbatim (they are the best
|
|
3147
|
+
statement of intent available), but they are wrong in **both** directions: a
|
|
3148
|
+
value the UI would refuse to let you edit is still stored and can still take
|
|
3149
|
+
effect, and a value whose `enable` was satisfied can still be dropped or
|
|
3150
|
+
overridden at render. Every prop where the render is known to disagree carries
|
|
3151
|
+
its own rule id — `slideshow_max_height_vs_render`, `thumbnav_ui_locked`,
|
|
3152
|
+
`nav_grid_needs_two`, `overlay_slider_two_positions`,
|
|
3153
|
+
`panel_slider_show_hover_video_dead` — plus the five-way `image_expand` split,
|
|
3154
|
+
which is in that field's `value_hint`.
|
|
3155
|
+
|
|
3156
|
+
That last one is the shape to expect from the rest: on `panel-slider`,
|
|
3157
|
+
`show_hover_video` is a **dead switch**. Its three sibling containers (`grid`,
|
|
3158
|
+
`gallery`, `overlay-slider`) blank the item's `hover_video` when it is off;
|
|
3159
|
+
`panel-slider`'s item lists `hover_image` twice and `hover_video` not at all, so
|
|
3160
|
+
the hover video keeps rendering. Prop, label and default are identical to the
|
|
3161
|
+
three that work — nothing in the descriptor can reveal it. Clear the item's
|
|
3162
|
+
`hover_video` prop instead.
|
|
3163
|
+
|
|
3164
|
+
Four rules to know before you write anything:
|
|
3165
|
+
|
|
3166
|
+
- **Equal height is not one switch** (`expand_model`). A `grid` already matches
|
|
3167
|
+
its column heights with nothing set; a `row` **never** does; a `column` matches
|
|
3168
|
+
only under a specific prop combination, and setting a row height **removes**
|
|
3169
|
+
matching from every column. `image_expand` expands the image, not the layout —
|
|
3170
|
+
the content-expand lever is a `*_margin_auto` checkbox.
|
|
3171
|
+
- **A link toggle silently strips HTML** (`link_striptags`). With `panel_link` /
|
|
3172
|
+
`overlay_link` / `card_link` on and a `link` present, YOOtheme's own renderer
|
|
3173
|
+
runs `striptags()` over `title`, `meta` **and** `content`. The stored value
|
|
3174
|
+
keeps the markup, so the loss is invisible in a read-back. This is YOOtheme's
|
|
3175
|
+
renderer, not this server's, and it cannot be switched off.
|
|
3176
|
+
**`list_item` is worse and gets its own id** (`link_striptags_ungated`): there
|
|
3177
|
+
is **no checkbox** to turn off — as soon as the item has a `link`, `content` is
|
|
3178
|
+
stripped, on both the with-image and the without-image render branch. To keep
|
|
3179
|
+
markup there, leave `link` empty or move the markup to an element that is not
|
|
3180
|
+
link-wrapped.
|
|
3181
|
+
- **A lightbox or modal drops the four link attributes**
|
|
3182
|
+
(`modal_drops_link_attrs`). `link_target`, `link_download`,
|
|
3183
|
+
`link_rel_nofollow` and `link_rel_noreferrer` are emitted only in the
|
|
3184
|
+
plain-link render branch. With `lightbox` on and a link present, an earlier
|
|
3185
|
+
branch wins and all four vanish — on `image`, and on `grid` and `gallery`
|
|
3186
|
+
(whose item templates have the identical shape; the four props are declared on
|
|
3187
|
+
the container). `button_item` does the same for a `dialog` with
|
|
3188
|
+
`dialog_layout` `modal`/`offcanvas` and no `link`. Open-in-new-tab, download
|
|
3189
|
+
and rel hints cannot be combined with a lightbox.
|
|
3190
|
+
- **An emptied container disappears with it** (`collapse_cascade`). A container
|
|
3191
|
+
that HAD children and ends up with none is removed — the mark is keyed on the
|
|
3192
|
+
element type's own `container` flag, and **21 element types** declare it in
|
|
3193
|
+
5.0.37, so `grid`, `gallery`, `slideshow` and the rest go exactly the way a
|
|
3194
|
+
`column` does. Items dropped by their own render gates therefore cascade
|
|
3195
|
+
upwards and a page can lose a whole section with no error anywhere. Only
|
|
3196
|
+
`column` offers `prevent_collapse`.
|
|
3197
|
+
|
|
3198
|
+
A field's **value format** is stated on the field itself, in `value_hint` — a
|
|
3199
|
+
value that does not parse is usually removed or defaulted silently, never
|
|
3200
|
+
rejected. `countdown.date` must be `strtotime()`-parseable or the element
|
|
3201
|
+
disappears; `map_item.location` must be numeric `"lat,lng"` or the marker is
|
|
3202
|
+
skipped (no geocoding); an empty `map.height` becomes 300; a non-numeric
|
|
3203
|
+
`popover_item.position_x`/`_y` becomes 50 %.
|
|
3204
|
+
|
|
2090
3205
|
## Dynamize an existing section (designed layout → live data, one call)
|
|
2091
3206
|
|
|
2092
3207
|
**Goal:** Take a section you (or the customer) already designed by hand
|
|
@@ -2705,58 +3820,62 @@ perform.
|
|
|
2705
3820
|
|
|
2706
3821
|
<!-- TOOL-CATALOG:BEGIN -->
|
|
2707
3822
|
|
|
2708
|
-
**
|
|
3823
|
+
**53 catalogued tools** plus the `yootheme_builder_advanced` (write) + `yootheme_builder_advanced_read` (read-only) gateways = **55 callable** end to end, of which **42 are advertised in `tools/list`** (38 L1 + 2 L3 + 2 gateways); the remaining 13 advanced tools are reachable only through the gateways. Generated by `scripts/extract-tools.mjs` from the compiled `buildAllTools()` registry. Do not hand-edit this section; re-run `npm run build && node scripts/extract-tools.mjs` after changing tool definitions.
|
|
2709
3824
|
|
|
2710
3825
|
| Tool | Kind | Input keys | Description |
|
|
2711
3826
|
| --- | --- | --- | --- |
|
|
2712
|
-
| `yootheme_builder_clean_implode_directives` | destructive | `confirm`, `element_path`, `etag`, `site_id`, `template_id` | Use when a Multi-Items element renders rows as ONE comma-joined string instead of N repeated items — typically stale `implode` directives left by a plugin upgrade (detect via yootheme_builder_inspect_multi_items_binding). Strips `props.source.props.*.implode` directives from an element binding. Returns audit log + new ETag. Idempotent (cleaned_count: 0 when nothing to remove) but destructive: the binding can't be restored without re-mapping. Ask the user to confirm, then call with `confirm: true`. Requires ETag.
|
|
2713
|
-
| `yootheme_builder_diagnose` | read+idempotent | `site_id` | Full diagnostic: /health + authenticated /etag probe. Returns site_url, home_url, plugin reachability, Bearer validity in one call. First call when you need to know where the site lives. For per-template URLs see pages_list.
|
|
2714
|
-
| `yootheme_builder_element_add` | mutating | `children`, `element_type`, `etag`, `index`, `name`, `parent_path`, `position`, `props`, `site_id`, `template_id`, `type_name` | Add a new element to a template. Provide `parent_path` (or "" for root), `element_type` (e.g. "headline", "text", "grid"), and optional `props` / `children`. Content/leaf elements may only be added inside a `column`; only `section` is valid at the root — call yootheme_builder_scaffold_section first for a valid column path. Appends by default; pass `index` (0 = first) to insert. Inserting renumbers later siblings — re-read paths via element_list after any structural write. Returns the new element's JSON-Pointer path. Requires ETag.
|
|
2715
|
-
| `yootheme_builder_element_bind_source` | idempotent | `arguments`, `bindingLevel`, `directives`, `element_path`, `etag`, `field`, `field_mappings`, `query_arguments`, `relation`, `site_id`, `source_id`, `source_name`, `template_id` | Binds a Builder source to an element (canonical shape: `source` top-level). Pass `field_mappings` in the SAME call; bindingLevel "item" binds a container's first *_item child. Leaf under an iterating *_item → CONSOLIDATE onto item slots (409 slot_collision if taken/not a slot); leaf under a #parent fragment → INHERIT (`__node_item__`); standalone renders empty. List on a grid_item with children → iterator_is_leaf_renderer. Map: bind `map_item`, map `location` to a "lat,lng" field or no markers. Requires ETag. Call directly.
|
|
2716
|
-
| `yootheme_builder_element_clone` | mutating | `element_path`, `etag`, `site_id`, `template_id` | Clone an element as a sibling (same parent, immediately after the source). Returns the new element's path. Cloning renumbers later siblings — re-read paths via element_list after any structural write. Requires ETag.
|
|
2717
|
-
| `yootheme_builder_element_delete` | destructive | `confirm`, `element_path`, `etag`, `site_id`, `template_id` | PERMANENTLY delete an element + children. Cannot be undone. Pass `confirm: true` (boolean) AND a fresh ETag. Deleting renumbers later siblings — re-read paths via element_list after any structural write. If `preview:true` returns despite confirm, check `details.received_confirm` — strings/numbers accepted, but boolean is canonical.
|
|
2718
|
-
| `yootheme_builder_element_get` | read+idempotent | `element_path`, `fields`, `max_chars`, `site_id`, `template_id` | Get the full element object at a specific JSON-Pointer path, including props and a `children_count` (NOT the children themselves — use yootheme_builder_element_list with root_path to enumerate children). `fields[]` narrows the returned object; `max_chars` bounds the text output. Address by template_id only — resolve a URL/post first via pages_resolve.
|
|
2719
|
-
| `yootheme_builder_element_get_binding` | read+idempotent | `element_path`, `site_id`, `template_id` | Read the source binding attached to an element. Always returns {template_id, element_path, binding, has_binding, field_mapping_count, warning?}: `binding` is the raw source binding (bound source name, field-mappings, query args/directives); `has_binding` is false and `field_mapping_count` 0 when the element is not bound.
|
|
2720
|
-
| `yootheme_builder_element_list` | read+idempotent | `cursor`, `depth`, `fields`, `limit`, `max_chars`, `root_path`, `site_id`, `template_id` | List elements in a template as a flat array with JSON-Pointer paths + types + `label` (the builder-assigned name; "" when unset — not that the type lacks one). Scope with `root_path`/`depth` for a subtree, paginate with `limit`/`cursor` for large templates. `fields[]` narrows each row; `max_chars` bounds the text output. template_id only — resolve a URL/post first via pages_resolve.
|
|
2721
|
-
| `yootheme_builder_element_move` | idempotent | `element_path`, `etag`, `site_id`, `template_id`, `to_index`, `to_parent_path` | Move an element to a new parent + index in the tree. Useful for reordering or reparenting (e.g. moving a card from one grid column to another). Moving renumbers later siblings — re-read paths via element_list after any structural write. Requires ETag.
|
|
2722
|
-
| `yootheme_builder_element_set_source` | idempotent | `element_path`, `etag`, `site_id`, `source`, `source_extended`, `template_id` | Surgically SET a precomposed `source` (+ optional `source_extended.props`) on ONE element at a pointer, ETag-locked (blast-radius 0). Use this — not element_bind_source — when you have the exact source shape OR need `source_extended.props`, the native Multi-Items item-mapping carrier bind_source cannot write. E.g. FAQ accordion scoped to term: query.name="categoryFaqs.taxonomyCategoryFaq", field={name:"faq"}, source_extended.props→#node. Element-prop keys the type does not render echo back as `unknown_prop_keys` (non-fatal). ETag req.
|
|
2723
|
-
| `
|
|
2724
|
-
| `
|
|
2725
|
-
| `
|
|
2726
|
-
| `
|
|
2727
|
-
| `
|
|
2728
|
-
| `
|
|
2729
|
-
| `
|
|
2730
|
-
| `
|
|
2731
|
-
| `
|
|
2732
|
-
| `
|
|
2733
|
-
| `
|
|
2734
|
-
| `
|
|
2735
|
-
| `
|
|
2736
|
-
| `
|
|
2737
|
-
| `
|
|
2738
|
-
| `
|
|
2739
|
-
| `
|
|
2740
|
-
| `
|
|
2741
|
-
| `
|
|
2742
|
-
| `
|
|
2743
|
-
| `
|
|
2744
|
-
| `
|
|
2745
|
-
| `
|
|
2746
|
-
| `
|
|
2747
|
-
| `
|
|
2748
|
-
| `
|
|
2749
|
-
| `
|
|
2750
|
-
| `
|
|
2751
|
-
| `
|
|
2752
|
-
| `
|
|
2753
|
-
| `
|
|
2754
|
-
| `
|
|
3827
|
+
| `yootheme_builder_clean_implode_directives` | destructive | `confirm`, `element_path`, `etag`, `site_id`, `template_id` | Use when a Multi-Items element renders rows as ONE comma-joined string instead of N repeated items — typically stale `implode` directives left by a plugin upgrade (detect via yootheme_builder_inspect_multi_items_binding). Strips `props.source.props.*.implode` directives from an element binding. Returns audit log + new ETag. Idempotent (cleaned_count: 0 when nothing to remove) but destructive: the binding can't be restored without re-mapping. Ask the user to confirm, then call with `confirm: true`. Requires ETag. |
|
|
3828
|
+
| `yootheme_builder_diagnose` | read+idempotent | `site_id` | Full diagnostic: /health + authenticated /etag probe. Returns site_url, home_url, plugin reachability, Bearer validity in one call. First call when you need to know where the site lives. For per-template URLs see pages_list. When a write-scoped key resolves to a CMS user with no write capability, the response adds `capability_warning` and `effective_capabilities.can_edit:false` instead of writes failing with a blind 403. |
|
|
3829
|
+
| `yootheme_builder_element_add` | mutating | `children`, `element_type`, `etag`, `index`, `name`, `parent_path`, `position`, `props`, `site_id`, `template_id`, `type_name` | Add a new element to a template. Provide `parent_path` (or "" for root), `element_type` (e.g. "headline", "text", "grid"), and optional `props` / `children`. Content/leaf elements may only be added inside a `column`; only `section` is valid at the root — call yootheme_builder_scaffold_section first for a valid column path. Appends by default; pass `index` (0 = first) to insert. Inserting renumbers later siblings — re-read paths via element_list after any structural write. Returns the new element's JSON-Pointer path. Requires ETag. |
|
|
3830
|
+
| `yootheme_builder_element_bind_source` | idempotent | `arguments`, `bindingLevel`, `directives`, `element_path`, `etag`, `field`, `field_mappings`, `query_arguments`, `relation`, `site_id`, `source_id`, `source_name`, `template_id` | Binds a Builder source to an element (canonical shape: `source` top-level). Pass `field_mappings` in the SAME call; bindingLevel "item" binds a container's first *_item child. Leaf under an iterating *_item → CONSOLIDATE onto item slots (409 slot_collision if taken/not a slot); leaf under a #parent fragment → INHERIT (`__node_item__`); standalone renders empty. List on a grid_item with children → iterator_is_leaf_renderer. Map: bind `map_item`, map `location` to a "lat,lng" field or no markers. Requires ETag. Call directly. |
|
|
3831
|
+
| `yootheme_builder_element_clone` | mutating | `element_path`, `etag`, `site_id`, `template_id` | Clone an element as a sibling (same parent, immediately after the source). Returns the new element's path. Cloning renumbers later siblings — re-read paths via element_list after any structural write. Requires ETag. |
|
|
3832
|
+
| `yootheme_builder_element_delete` | destructive | `confirm`, `element_path`, `etag`, `site_id`, `template_id` | PERMANENTLY delete an element + children. Cannot be undone. Pass `confirm: true` (boolean) AND a fresh ETag. Deleting renumbers later siblings — re-read paths via element_list after any structural write. If `preview:true` returns despite confirm, check `details.received_confirm` — strings/numbers accepted, but boolean is canonical. |
|
|
3833
|
+
| `yootheme_builder_element_get` | read+idempotent | `element_path`, `fields`, `max_chars`, `site_id`, `template_id` | Get the full element object at a specific JSON-Pointer path, including props and a `children_count` (NOT the children themselves — use yootheme_builder_element_list with root_path to enumerate children). `fields[]` narrows the returned object; `max_chars` bounds the text output. Address by template_id only — resolve a URL/post first via pages_resolve. |
|
|
3834
|
+
| `yootheme_builder_element_get_binding` | read+idempotent | `element_path`, `site_id`, `template_id` | Read the source binding attached to an element. Always returns {template_id, element_path, binding, has_binding, field_mapping_count, warning?}: `binding` is the raw source binding (bound source name, field-mappings, query args/directives); `has_binding` is false and `field_mapping_count` 0 when the element is not bound. |
|
|
3835
|
+
| `yootheme_builder_element_list` | read+idempotent | `cursor`, `depth`, `fields`, `include_meta`, `limit`, `max_chars`, `root_path`, `site_id`, `template_id` | List elements in a template as a flat array with JSON-Pointer paths + types + `label` (the builder-assigned name; "" when unset — not that the type lacks one). Scope with `root_path`/`depth` for a subtree, paginate with `limit`/`cursor` for large templates. `fields[]` narrows each row; `max_chars` bounds the text output. template_id only — resolve a URL/post first via pages_resolve. |
|
|
3836
|
+
| `yootheme_builder_element_move` | idempotent | `element_path`, `etag`, `site_id`, `template_id`, `to_index`, `to_parent_path` | Move an element to a new parent + index in the tree. Useful for reordering or reparenting (e.g. moving a card from one grid column to another). Moving renumbers later siblings — re-read paths via element_list after any structural write. Requires ETag. |
|
|
3837
|
+
| `yootheme_builder_element_set_source` | idempotent | `element_path`, `etag`, `site_id`, `source`, `source_extended`, `template_id` | Surgically SET a precomposed `source` (+ optional `source_extended.props`) on ONE element at a pointer, ETag-locked (blast-radius 0). Use this — not element_bind_source — when you have the exact source shape OR need `source_extended.props`, the native Multi-Items item-mapping carrier bind_source cannot write. E.g. FAQ accordion scoped to term: query.name="categoryFaqs.taxonomyCategoryFaq", field={name:"faq"}, source_extended.props→#node. Element-prop keys the type does not render echo back as `unknown_prop_keys` (non-fatal). ETag req. |
|
|
3838
|
+
| `yootheme_builder_element_set_status` | idempotent | `element_path`, `etag`, `site_id`, `status`, `template_id` | Enable or disable an element via its `props.status` field — the SAME field YOOtheme's own native "Disable element" checkbox writes. status:false excludes the element and its descendants from BOTH the live render and live findings; pages_audit and template_summary content_nodes both honour it. status:true re-enables it, restoring the YOOtheme default (no status field means active). Requires ETag. |
|
|
3839
|
+
| `yootheme_builder_element_type_get_schema` | read+idempotent | `element_type`, `field_names`, `fields`, `max_chars`, `name_contains`, `site_id`, `type_name` | Prop schema; read before element_add/element_update_settings: unknown keys rejected (error lists valid_prop_keys+did_you_mean). Field: {name,type,label?,text?,description?,enum?,value_hint?,group?,no_effect?} + groups[] + field_count/returned_count + binding_contract (containers/items). value_hint+enum first: some defaults are SEO-fatal (headline.title_element→h1); checkbox = boolean true/false; no_effect = read by nothing, never write it; group:"runtime-accepted" = honoured at render, hidden in UI. Select descriptors: field_names/name_contains. Bound size with fields[]/max_chars. |
|
|
3840
|
+
| `yootheme_builder_element_types_list` | read+idempotent | `fields`, `include_meta`, `site_id` | List element types registered on this site (built-ins + YOOessentials/uEssentials extras). Names feed `element_type` of element_add. Pass `fields[]` to narrow each row. Keywords: "slider"/"carousel" → `slideshow`/`panel-slider`/`overlay-slider`. Prop keys differ per element (grid_item `title` vs headline `content`) — use element_type_get_schema before element_add/bind. Container rows carry `requires_child_type` (grid→grid_item); `has_children`/`has_children_support` are aliases (same value). |
|
|
3841
|
+
| `yootheme_builder_element_unbind_source` | destructive | `confirm`, `element_path`, `etag`, `site_id`, `template_id` | Remove the source binding from an element. Clears the element's canonical top-level `source` key AND the legacy `props.source`. Destructive in the sense that it may break dynamic-content rendering — always ask the user to confirm. Requires ETag. |
|
|
3842
|
+
| `yootheme_builder_element_update_settings` | idempotent | `element_path`, `etag`, `merge`, `name`, `props`, `site_id`, `template_id`, `unset` | Update `props` on an element. Default replaces all props; pass `merge:true` for server-side deep-merge (only request keys overwritten, others survive — avoids read-modify-write races). Requires ETag. Pass `unset:["key",…]` to reset props to their YOOtheme default by removing them. |
|
|
3843
|
+
| `yootheme_builder_get_etag` | read+idempotent | `site_id` | Get the current ETag (state revision) for the YOOtheme builder. Returns a sha256+revision string for optimistic locking. This ETag is SITE-WIDE: it covers the ENTIRE builder state, so ANY write to ANY template (by any client) bumps it — a 412 means the site state advanced, not necessarily that YOUR template changed. It is the SAME token the read tools (page_get_layout, element_list) return in their `etag`. Pass it back as `etag` on any write tool; on 412, re-read and retry. |
|
|
3844
|
+
| `yootheme_builder_header_transparency_set` | idempotent | `acknowledge_contrast_risk`, `dry_run`, `etag`, `mode`, `pull_content_behind`, `site_id`, `template_id`, `text_color`, `transparent` | Inspect + repair the transparent header (nav over the hero). Always targets the FIRST section — YOOtheme reads these props there only; later ones are silently dead. dry_run defaults TRUE: returns current state, the theme-declared --uk-inverse, risk_class, confidence, recommendation, would_change. mode:"auto" writes the theme-declared colour; over a hero image/video it writes NO colour and says to LOOK. mode:"explicit" refuses a colour contradicting the theme (409) unless acknowledge_contrast_risk. transparent:false is always allowed. |
|
|
3845
|
+
| `yootheme_builder_heading_outline` | read+idempotent | `mode`, `site_id`, `template_id` | A real, doc-order H1-H6 outline of ONE template: each heading with its TEXT and the source element that carries it (`element_type`/`path`), plus the alt-status of every image (including images inside html/text/code raw markup). Read-only STRUCTURE inspection, not findings; use yootheme_builder_pages_audit for severity-sorted SEO findings across the whole site. Returns {outline, images, h1_count, has_level_skip, images_missing_alt}. Address by `template_id` (from pages_list or pages_resolve). |
|
|
3846
|
+
| `yootheme_builder_health` | read+idempotent | `site_id` | Check plugin installed/reachable. Returns plugin version, YT Pro version, REST endpoints. available_endpoints is derived from the live route registration on both platforms, so every path listed is request-ready as printed. Authenticated payload adds site_url + home_url for deep-linking. See yootheme_builder_diagnose for Bearer-validity + connectivity summary. |
|
|
3847
|
+
| `yootheme_builder_image_import` | idempotent | `site_id`, `url` | Sideload a photo into the site's own Media Library. Accepts ANY public image URL — it does NOT require a Library API key, so it still works when image_search is unavailable (typically pass a `full_url` from yootheme_builder_image_search). Returns `{imported:true, local_url}` where local_url is a SITE-RELATIVE path (e.g. /wp-content/uploads/...). Bind it via element_update_settings({props:{image: local_url}, merge:true}). A caught import failure returns `{imported:false, reason}`, never a 500. |
|
|
3848
|
+
| `yootheme_builder_image_search` | read+idempotent | `color`, `include_meta`, `orientation`, `per_page`, `query`, `site_id`, `source` | Search real on-topic photos (Unsplash/Pexels) or YOOtheme Pro Images to fill layout image slots with REAL, on-brand photography instead of bare placeholders. Returns candidate rows with `full_url` (pass straight to yootheme_builder_image_import) plus dimensions/author/orientation. `total:0` usually means no Library API key is configured (the same theme apikey library_browse/insert use). |
|
|
3849
|
+
| `yootheme_builder_inspect_multi_items_binding` | read+idempotent | `element_path`, `site_id`, `template_id` | Reports Multi-Items binding state: container/item pair (grid↔grid_item, slideshow↔slideshow_item, map↔map_item, …), the binding level (none\|container\|item), and a recommended_fix when it sits on the container. For map/map_item, a bound item without a `location` ("lat,lng") field is flagged as a blocking map_location_warning (renders 0 markers). A List on a grid_item with nested children → blocking iterator_is_leaf_renderer (children never render). If rows render as ONE comma-joined string, see clean_implode_directives. |
|
|
3850
|
+
| `yootheme_builder_inspect_sublayout` | read+idempotent | `element_path`, `site_id`, `template_id` | Inspects a panel/grid_item `< SUBLAYOUT`: lists the `fragment` subtree bindable leaves (with JSON-Pointer paths to bind on — NOT the container) and the render-time inner grid. Flags `ungated_grid_prop` (blocking): grid props set but the gate (title_align=left / image_align=left\|right) unsatisfied → silent no-op. For container↔item binding see inspect_multi_items_binding. |
|
|
3851
|
+
| `yootheme_builder_layout_query` | read+idempotent | `content_contains`, `cursor`, `fields`, `has_binding`, `include_meta`, `limit`, `max_chars`, `max_depth`, `name_matches`, `site_id`, `template_id`, `type`, `under_section` | Find elements in a template via AND-combined filters: `type` (one type or a list), `name_matches` (case-insensitive substring of the name), `content_contains` (case-insensitive substring of `content`/`title`/`meta` props), `has_binding` (bound vs unbound), `max_depth` (cap descent — 0 = top-level), `under_section` (inside a named section). template_id only — resolve a URL/post first via pages_resolve. Returns rows {path, element_type, label, has_binding}; paginate `limit`/`cursor`, narrow `fields[]`, bound `max_chars`. |
|
|
3852
|
+
| `yootheme_builder_library_browse` | read+idempotent | `cursor`, `element_type`, `has_effects`, `include_meta`, `kind`, `limit`, `purpose`, `query`, `role`, `site_id`, `source`, `topic`, `type`, `website` | Browse the YOOtheme Pro Library catalog: pre-designed page LAYOUTS, element PRESETS, or purpose-classified SECTIONS (hero/features/cta/...). Filter with `kind` ("layout" default \| "element" \| "section"), `topic`, `type`/`role`, `element_type`, `purpose`/`has_effects` (sections only), `website`, `query`. Returns `{items, total, next_cursor}`; a "section" id is the composite "<layoutId>#<sectionIndex>" — feeds straight into library_get/library_insert. `total:0` usually means no Library API key configured. |
|
|
3853
|
+
| `yootheme_builder_library_delete` | destructive | `confirm`, `id`, `site_id`, `source` | Delete ONE saved entry from your "WootsUp AI Layouts" store (the isolated library yootheme_builder_library_save_as writes into) — clean up experimental sections/layouts the agent saved. Pass the `id` from a library_browse(source:"ai") row. `source` defaults to "ai", the ONLY deletable store; the curated library is read-only, so source:"catalog" returns a 403. IRREVERSIBLE — requires confirm:true. Returns `{deleted, id, deleted_count, source}`; an unknown id returns a 404. Advanced/gateway-only. |
|
|
3854
|
+
| `yootheme_builder_library_get` | read+idempotent | `id`, `include_thumbnail`, `kind`, `site_id`, `source` | Fetch a SINGLE YOOtheme Pro Library layout or element preset by `id` — call after yootheme_builder_library_browse (items[].id feeds this). Returns `{id, kind, name, version, tree, sections, images_manifest_count}`: `tree` is the raw JSON; `sections` lists `tree.children` (layouts only, presets return `[]`), each `{index, name, path, element_count}`. `include_thumbnail:true` also returns an image block (costs tokens — see its own description). Read-only; does not insert/bind. |
|
|
3855
|
+
| `yootheme_builder_library_insert` | mutating | `etag`, `id`, `images`, `kind`, `section_index`, `site_id`, `source`, `target` | Insert a YOOtheme Pro Library item (layout, section, or preset) onto a page — call after yootheme_builder_library_get/browse. `target`: new_page (creates a page), {template_id,element_path?} (splices onto the layout root, or a preset onto a Column), or sublayout (splices rows into a fragment). A preset (kind:"element") MUST target a Column — 422 otherwise; scaffold one first with yootheme_builder_scaffold_section. `images`: "placeholder" (default) or "curated" (downloads to Media Library). Optional `etag` locks (412 if stale). |
|
|
3856
|
+
| `yootheme_builder_library_save_as` | mutating | `destination`, `name`, `site_id`, `source` | Save a page or subtree into a reusable library — the REVERSE of yootheme_builder_library_insert. `source`: {template_id, element_path?} (omit element_path to save the WHOLE page). `destination`: "ai" (default — your isolated "WootsUp AI Layouts", reusable via library_browse/insert source:"ai") or "native" (YOOtheme's built-in My Layouts). Returns `{library_id, name, storage, invalid_source_possible}` (the last flags bound sources that may need re-binding after reload). Advanced/gateway-only. |
|
|
3857
|
+
| `yootheme_builder_local_content_read` | read+idempotent | `fields`, `include_children`, `include_meta`, `kind`, `limit`, `max_chars`, `offset`, `parent_id`, `search`, `site_id`, `status`, `taxonomy`, `type` | Read native records for one `kind`: entities (posts/pages/CPT/articles), terms (TERMS — ids that archive-scope a Grid), users, fields (ACF/com_fields defs), menus, or media. Published-only + paginated: {items,total,next_offset}; pass next_offset back as `offset`. Archive-scoping flow: read {kind:"terms",taxonomy:"category"} → pick a term id → bind a source, pass query_arguments:{id:<term_id>}. CALL local_content_types FIRST for valid type/taxonomy. E.g. {kind:"entities",type:"product",search:"shoe"}. Slim with `fields[]`. |
|
|
3858
|
+
| `yootheme_builder_local_content_types` | read+idempotent | `fields`, `kind`, `max_chars`, `site_id` | Discover the site's NATIVE content landscape — post types/articles, taxonomies and custom-field groups (ACF/com_fields), native to WP/Joomla (no API Mapper). Returns {content_types, taxonomies, field_groups, capabilities}; rows carry `bindable` + `source_name` (null for native types — bind those with element_bind_source by the content-type `name`, e.g. "post"). CALL FIRST, then local_content_read for records. Narrow with `kind`, slim with `fields[]`. E.g. {kind:"content_types"}. |
|
|
3859
|
+
| `yootheme_builder_navigation` | read+idempotent | `menu`, `site_id` | The site's navigation menus (WP nav menus / Joomla menus) as a parent -> children TREE, not a flat list, for "which page is under which menu item" (product pages usually live in the main menu). Each item carries a `page_role` facet (home\|landing\|product\|legal\|thankyou\|blog\|mockup\|template\|generic, same vocabulary as pages_list) and, when derivable at zero extra cost, a `target_page` (page:<id> / article:<id>) ready for yootheme_builder_page_get_layout. Read-only. Narrow to one menu with `menu`. |
|
|
3860
|
+
| `yootheme_builder_page_dynamize` | mutating | `conditions`, `dry_run`, `element_path`, `etag`, `leaf_map`, `list_source_name`, `page`, `remove_leaves`, `section_name`, `site_id`, `source_map`, `template_id` | One-call way to dynamize a designed section: copies the layout byte-identically and swaps only what you map. Use INSTEAD of hand-editing JSON. Give a REFERENCE (section_name from template_summary, or element_path) + list_source_name + a leaf_map (original leaf field-name → flat flow field; #parent + tuned props kept). A section with several source blocks dynamizes in ONE call via source_map ({match, source_name, leaf_map?}[]). Sub-query panels: key by "<subquery>.<leaf>". Returns page + `summary`. Preview with dry_run:true. |
|
|
3861
|
+
| `yootheme_builder_page_get_layout` | read+idempotent | `cursor`, `fields`, `flat`, `force`, `max_chars`, `mode`, `post_id`, `site_id`, `template_id`, `url` | Get the layout tree for one template — address it by `template_id` (from pages_list), `post_id`, or `url`. `mode:"bindings_only"` for dynamize/bind; `mode:"full"` (default) is nested `{layout, etag}`, `flat:true` a depth-first array. `max_chars` bounds the text; a clipped read returns `next_cursor` to page the rest. For a site-wide SEO/structure/content/links audit (h1, image alt, thin pages, dead links) do NOT loop this tool page-by-page — call yootheme_builder_pages_audit (one sweep via yootheme_builder_advanced_read). |
|
|
3862
|
+
| `yootheme_builder_page_get_schema` | read+idempotent | `fields`, `include_meta`, `max_chars`, `post_id`, `site_id`, `template_id`, `url` | Get the flat schema for a template — nodes with their JSON-Pointer paths and element types. Lighter than page_get_layout, enough to locate elements before editing. Address by `template_id` (pages_list), `post_id`, or `url`. `fields[]` slims each node; `max_chars` bounds output. |
|
|
3863
|
+
| `yootheme_builder_page_publish` | idempotent | `etag`, `quick_check`, `site_id`, `template_id` | Publish a template — persist state, flush YT + WP caches, snapshot the published-state ETag. YOOtheme templates already publish on save, so this is a cache-flush + state-snapshot op. Neither publish nor save changes the CMS post status — a draft page stays invisible to visitors; set status at creation via pages_create/library_insert. ETag optional — when provided, 412 on conflict; when omitted, last-write-wins. |
|
|
3864
|
+
| `yootheme_builder_page_save` | idempotent | `dry_run`, `etag`, `layout`, `quick_check`, `site_id`, `template_id` | Re-run save-transforms + flush caches. Optionally REPLACE the template tree by passing the INNER {type:"layout",children:[...]} tree as `layout` — page_get_layout's `.layout` child, NOT the whole response it returns (rejected 422, would blank the page). Validated + ETag-locked; nothing persists on reject. Does NOT change CMS post status — a draft stays invisible to visitors; set status via pages_create. `dry_run:true` preflights; success carries `previous_layout` for rollback. ETag optional (412 on conflict; else last-write-wins). |
|
|
3865
|
+
| `yootheme_builder_pages_audit` | read+idempotent | `checks`, `include_meta`, `max_chars`, `mode`, `site_id`, `stale_after_months`, `target`, `thin_content_threshold`, `types` | One-call site-wide SEO/structure/content/links/design audit — severity-sorted findings with fix_hints (a props fragment to merge: supply template_id+path from the locator). Covers h1/headings, thin pages, image alt, duplicate layouts, title collisions, non-pretty URLs, dead anchors. Only active (non-disabled) nodes produce findings. A site sweep caps hits/page, drops content_snippet; `target` scopes to ONE page for the full set (cap 100). Filter with `types`/`checks`. Disclaimer: SEO-plugin meta (title/description/OG) NOT covered. |
|
|
3866
|
+
| `yootheme_builder_pages_create` | idempotent | `dry_run`, `layout`, `menu_item`, `published`, `site_id`, `slug`, `status`, `title` | Create a page (WordPress) or article (Joomla 5/6) with a YOOtheme per-page layout. Required: title. Optional: slug, status (default draft), published (default false → live on true: WP publish / Joomla state 1), layout (JSON), menu_item (Joomla SEF route), dry_run (preflight, no persist). Returns page_id (WP) / article_id (Joomla), slug, template_id (page:{id} or article:{id}), etag, warnings. Joomla: a published article has NO SEF route until a menu item points at it — pass menu_item, else frontend_url may 404. |
|
|
3867
|
+
| `yootheme_builder_pages_delete` | destructive | `confirm`, `force`, `post_id`, `site_id` | Delete a page (WordPress) or article (Joomla 5/6) with its YOOtheme per-page layout. Requires confirm:true (gate against accidental deletion). Default: trash (recoverable). force:true: permanent delete. Refuses non-page/article types with 403 (Layer 9 defense). Returns deleted:true, page_id (WP) / article_id (Joomla), force_deleted, bumped etag. |
|
|
3868
|
+
| `yootheme_builder_pages_list` | read+idempotent | `cursor`, `fields`, `include_meta`, `label_contains`, `limit`, `max_chars`, `site_id` | List pages, templates, and layouts in the YOOtheme Pro builder (id, label, type, elements_count, frontend_url, builder_url, serves_context_kind, serves_context_ids, page_role per row). CALL THIS FIRST to discover template IDs before page_get_layout / element_list / page_get_schema. Paginates: up to `limit` rows (default 50) plus `total` and a `next_cursor` (pass it back as `cursor`) when more remain. Filter with `label_contains`. Slim rows with `fields:["id","label"]`; bound output with `max_chars`. |
|
|
3869
|
+
| `yootheme_builder_pages_resolve` | read+idempotent | `site_id`, `target`, `url` | Resolve a public page/article/archive URL to the template_id that renders it — paste a URL, get the layout id. Pretty/SEF permalinks resolve on WordPress AND Joomla (incl. deep sub-category paths), not only raw ?option= URLs. A singular page → its per-page layout; a category/tag/CPT/author archive → the governing THEME TEMPLATE, and for a category/term the SPECIFICALLY-ASSIGNED template (honoring include-child-categories), not a generic one. Or a `target`. Returns {template_id, post_id, type, frontend_url, context, resolved_via}. |
|
|
3870
|
+
| `yootheme_builder_rename_source` | idempotent | `dry_run`, `etag`, `field_map`, `from`, `site_id`, `source_map`, `template_id`, `to` | Template-wide RENAME of a source / type / field name across EVERY binding in a template, in ONE ETag-locked write — preserving each binding's arguments, props and field_mappings. Use after a taxonomy/CPT rename (faq→help) breaks every bound layout. Pass `from`/`to` for one source, and/or `source_map`/`field_map` to rename a relation + its field tokens together; the dotted "<source>.<relation>" form is rewritten segment-by-segment. Returns {replaced_count, changed_paths, new_etag}. Preview with dry_run:true. ETag req. |
|
|
3871
|
+
| `yootheme_builder_render_preview` | read+idempotent | `expect`, `post_id`, `site_id`, `template_id`, `url` | Render-check a saved template — does it actually render, with how many items, or is it blank? Address by `template_id` (from pages_list), `post_id`, or `url` (resolved for you) — exactly one. `item_count` = items yielded by the template's dynamic/Multi-Items bindings (rendered repeat-source rows); 0 with empty:true means a binding returned nothing. Returns {rendered, item_count, empty, error?, cache_purge_hint}; with `expect`, also {matched, snippet}. Use AFTER a save/bind to catch a silently-empty page. Read-only. |
|
|
3872
|
+
| `yootheme_builder_scaffold_section` | mutating | `column_widths`, `columns`, `etag`, `site_id`, `template_id` | Create a VALID section → row → column hull in ONE call; returns the column JSON-Pointer path(s) for content. The correct way to start a layout block: only `section` may sit at the layout root; a bare section accepts `row`, a bare row accepts `column`; leaf/content elements are rejected anywhere except inside a `column`. Pass `columns` (1-12, default 1); columns get width tokens so they render side-by-side (hand-built columns default to full width), or `column_widths` (one token per column) for an asymmetric row. Requires ETag. |
|
|
3873
|
+
| `yootheme_builder_sites_list` | read+idempotent | `include_meta`, `site_id` | List all sites configured in this multi-site MCP installation. Returns site_id + URL + platform (wordpress\|joomla) + default flag per row. CALL THIS FIRST when working with a fresh MCP connection to discover available site_ids before targeting one with any other tool. Read-only, no REST calls. Keywords: list sites, list connections, list installations, discover site_id, available sites, configured sites, what sites exist, multi-site index. The `default_site_id` is the write target when a call omits site_id. (site_id is accepted for schema-uniformity but ignored by this tool.) |
|
|
2755
3874
|
| `yootheme_builder_sites_test` | read+idempotent | `site_id` | Verify connectivity to ONE site: probes /health (no auth) + /etag (auth) in parallel; returns plugin_reachable + bearer_valid. `site_id` is REQUIRED. Use sites_list to find IDs. |
|
|
2756
|
-
| `yootheme_builder_source_get_schema` | read+idempotent | `max_chars`, `name`, `site_id` | Introspect ONE data source: its query methods + arguments + RELATIONS (e.g. Term→Posts: categoryFaqs.taxonomyCategoryFaq → field "faq") + a provides_archive_context flag (id:0 = current archive term, auto-scoped). SOME arguments carry `enum_values` (the allowed values); when an arg has none it is a free string — pick from enum_values when present, never invent values. CALL BEFORE binding on an archive/category template. Keywords: source schema, relations, archive context, enum values. Get `name` from yootheme_builder_sources_list.
|
|
2757
|
-
| `yootheme_builder_sources_list` | read+idempotent | `cursor`, `fields`, `group`, `kind`, `limit`, `max_chars`, `name_contains`, `site_id` | List all data sources, feeds, and dynamic content sources available in the YOOtheme Pro builder. Returns name + label + origin (apimapper / wordpress / joomla / essentials) per source. CALL THIS BEFORE binding any element to a data source. Then bind directly with yootheme_builder_element_bind_source. Keywords: list sources, list feeds, list bindings, dynamic content, what sources exist. Paginates (`limit`/`cursor`/`total`); scope with `group`/`kind`/`name_contains`; slim with `fields[]`; bound output with `max_chars`.
|
|
2758
|
-
| `yootheme_builder_template_set_type` | idempotent | `dry_run`, `etag`, `site_id`, `template_id`, `type` | Reassign which archive/single a YOOtheme THEME template applies to — set its `type` condition (the value pages_list shows) in ONE ETag-locked write. Use after a taxonomy/CPT rename orphans a template (e.g. re-type "taxonomy-category_faq" → "taxonomy-category_help"). Returns {type, previous_type, new_etag}. Site-wide + immediate — preview with dry_run:true, reversible via previous_type. THEME templates only: page:/article: is refused (422). ETag req.
|
|
2759
|
-
| `yootheme_builder_template_summary` | read+idempotent | `site_id`, `template_id` | Token-efficient template overview: element counts by type, binding count, max nesting depth, and named landmark sections
|
|
3875
|
+
| `yootheme_builder_source_get_schema` | read+idempotent | `max_chars`, `name`, `site_id` | Introspect ONE data source: its query methods + arguments + RELATIONS (e.g. Term→Posts: categoryFaqs.taxonomyCategoryFaq → field "faq") + a provides_archive_context flag (id:0 = current archive term, auto-scoped). SOME arguments carry `enum_values` (the allowed values); when an arg has none it is a free string — pick from enum_values when present, never invent values. CALL BEFORE binding on an archive/category template. Keywords: source schema, relations, archive context, enum values. Get `name` from yootheme_builder_sources_list. |
|
|
3876
|
+
| `yootheme_builder_sources_list` | read+idempotent | `cursor`, `fields`, `group`, `include_meta`, `kind`, `limit`, `max_chars`, `name_contains`, `site_id` | List all data sources, feeds, and dynamic content sources available in the YOOtheme Pro builder. Returns name + label + origin (apimapper / wordpress / joomla / essentials) per source. CALL THIS BEFORE binding any element to a data source. Then bind directly with yootheme_builder_element_bind_source. Keywords: list sources, list feeds, list bindings, dynamic content, what sources exist. Paginates (`limit`/`cursor`/`total`); scope with `group`/`kind`/`name_contains`; slim with `fields[]`; bound output with `max_chars`. |
|
|
3877
|
+
| `yootheme_builder_template_set_type` | idempotent | `dry_run`, `etag`, `site_id`, `template_id`, `type` | Reassign which archive/single a YOOtheme THEME template applies to — set its `type` condition (the value pages_list shows) in ONE ETag-locked write. Use after a taxonomy/CPT rename orphans a template (e.g. re-type "taxonomy-category_faq" → "taxonomy-category_help"). Returns {type, previous_type, new_etag}. Site-wide + immediate — preview with dry_run:true, reversible via previous_type. THEME templates only: page:/article: is refused (422). ETag req. |
|
|
3878
|
+
| `yootheme_builder_template_summary` | read+idempotent | `cursor`, `include_nodes`, `limit`, `site_id`, `template_id`, `template_ids` | Token-efficient template overview: element counts by type, binding count, max nesting depth, and named landmark sections, computed server-side in one call. Use this to grasp a large template before pulling element_list or page_get_layout. Pass `include_nodes:true` for a `content_nodes` per-node breakdown (status/effective_visible/headings/alts), pageable via `limit`/`cursor`. Pass `template_ids:[...]` for a batch: response carries `summaries` + `not_found`. Example: yootheme_builder_template_summary({ template_id: "default" }). |
|
|
2760
3879
|
| `yootheme_builder_use_site` | idempotent | `probe`, `site_id` | Switch the ACTIVE site for this multi-site session. After this call, every tool that omits `site_id` targets the site you picked here. NOTE: this default is PROCESS-WIDE — concurrent clients sharing this MCP server inherit it; pass `site_id` per call for isolation between parallel agents. Runs a /health + /etag probe by default; pass `probe: false` to switch offline. A degraded site is a soft warning (the switch still happens). `site_id` is REQUIRED. Keywords: switch/change/set active/target site, select installation, use profile. Use yootheme_builder_sites_list first to find IDs. |
|
|
2761
3880
|
|
|
2762
3881
|
<!-- TOOL-CATALOG:END -->
|