@wootsup/yt-builder-mcp 1.12.0 → 2.0.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 +128 -30
- package/bin/node-floor.d.ts +28 -0
- package/bin/node-floor.js +123 -0
- package/bin/yt-builder-mcp.js +51 -10
- package/dist/auth.d.ts +3 -121
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +40 -170
- package/dist/auth.js.map +1 -1
- package/dist/catalog/build-catalog.d.ts +4 -49
- package/dist/catalog/build-catalog.d.ts.map +1 -1
- package/dist/catalog/build-catalog.js +15 -126
- package/dist/catalog/build-catalog.js.map +1 -1
- package/dist/catalog/json-schema.d.ts +3 -0
- package/dist/catalog/json-schema.d.ts.map +1 -0
- package/dist/catalog/json-schema.js +68 -0
- package/dist/catalog/json-schema.js.map +1 -0
- package/dist/catalog/output-shapes.d.ts +6 -0
- package/dist/catalog/output-shapes.d.ts.map +1 -0
- package/dist/catalog/output-shapes.js +89 -0
- package/dist/catalog/output-shapes.js.map +1 -0
- package/dist/catalog/tool-catalog-markdown.d.ts +1 -37
- package/dist/catalog/tool-catalog-markdown.d.ts.map +1 -1
- package/dist/catalog/tool-catalog-markdown.js +8 -52
- package/dist/catalog/tool-catalog-markdown.js.map +1 -1
- package/dist/cli/doctor-command.d.ts +2 -7
- package/dist/cli/doctor-command.d.ts.map +1 -1
- package/dist/cli/doctor-command.js +21 -62
- package/dist/cli/doctor-command.js.map +1 -1
- package/dist/cli/invocation-decision.d.ts +4 -52
- package/dist/cli/invocation-decision.d.ts.map +1 -1
- package/dist/cli/invocation-decision.js +45 -54
- package/dist/cli/invocation-decision.js.map +1 -1
- package/dist/client.d.ts +3 -107
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +34 -115
- package/dist/client.js.map +1 -1
- package/dist/clients/claude-code.d.ts +0 -15
- package/dist/clients/claude-code.d.ts.map +1 -1
- package/dist/clients/claude-code.js +0 -22
- package/dist/clients/claude-code.js.map +1 -1
- package/dist/clients/claude-desktop.d.ts +0 -16
- package/dist/clients/claude-desktop.d.ts.map +1 -1
- package/dist/clients/claude-desktop.js +0 -18
- package/dist/clients/claude-desktop.js.map +1 -1
- package/dist/clients/cline.d.ts +0 -23
- package/dist/clients/cline.d.ts.map +1 -1
- package/dist/clients/cline.js +0 -33
- package/dist/clients/cline.js.map +1 -1
- package/dist/clients/codex-cli.d.ts +0 -39
- package/dist/clients/codex-cli.d.ts.map +1 -1
- package/dist/clients/codex-cli.js +0 -137
- package/dist/clients/codex-cli.js.map +1 -1
- package/dist/clients/continue.d.ts +0 -10
- package/dist/clients/continue.d.ts.map +1 -1
- package/dist/clients/continue.js +0 -26
- package/dist/clients/continue.js.map +1 -1
- package/dist/clients/cursor.d.ts +0 -9
- package/dist/clients/cursor.d.ts.map +1 -1
- package/dist/clients/cursor.js +0 -11
- package/dist/clients/cursor.js.map +1 -1
- package/dist/clients/gemini-cli.d.ts +0 -15
- package/dist/clients/gemini-cli.d.ts.map +1 -1
- package/dist/clients/gemini-cli.js +0 -17
- package/dist/clients/gemini-cli.js.map +1 -1
- package/dist/clients/home.d.ts +0 -12
- package/dist/clients/home.d.ts.map +1 -1
- package/dist/clients/home.js +0 -12
- package/dist/clients/home.js.map +1 -1
- package/dist/clients/index.d.ts +0 -150
- package/dist/clients/index.d.ts.map +1 -1
- package/dist/clients/index.js +0 -112
- package/dist/clients/index.js.map +1 -1
- package/dist/clients/roo-code.d.ts +0 -20
- package/dist/clients/roo-code.d.ts.map +1 -1
- package/dist/clients/roo-code.js +0 -22
- package/dist/clients/roo-code.js.map +1 -1
- package/dist/clients/zed.d.ts +0 -9
- package/dist/clients/zed.d.ts.map +1 -1
- package/dist/clients/zed.js +0 -18
- package/dist/clients/zed.js.map +1 -1
- package/dist/compat-floor.d.ts +35 -0
- package/dist/compat-floor.d.ts.map +1 -0
- package/dist/compat-floor.js +84 -0
- package/dist/compat-floor.js.map +1 -0
- package/dist/compat-probe.d.ts +6 -0
- package/dist/compat-probe.d.ts.map +1 -0
- package/dist/compat-probe.js +20 -0
- package/dist/compat-probe.js.map +1 -0
- package/dist/diagnostics/ca-reexec.d.ts +0 -30
- package/dist/diagnostics/ca-reexec.d.ts.map +1 -1
- package/dist/diagnostics/ca-reexec.js +0 -62
- package/dist/diagnostics/ca-reexec.js.map +1 -1
- package/dist/diagnostics/ca-trust.d.ts +0 -22
- package/dist/diagnostics/ca-trust.d.ts.map +1 -1
- package/dist/diagnostics/ca-trust.js +3 -42
- package/dist/diagnostics/ca-trust.js.map +1 -1
- package/dist/diagnostics/diagnose-network-error.d.ts +1 -38
- package/dist/diagnostics/diagnose-network-error.d.ts.map +1 -1
- package/dist/diagnostics/diagnose-network-error.js +26 -37
- package/dist/diagnostics/diagnose-network-error.js.map +1 -1
- package/dist/diagnostics/doctor.d.ts +1 -85
- package/dist/diagnostics/doctor.d.ts.map +1 -1
- package/dist/diagnostics/doctor.js +5 -90
- package/dist/diagnostics/doctor.js.map +1 -1
- package/dist/diagnostics/startup-self-probe.d.ts +3 -25
- package/dist/diagnostics/startup-self-probe.d.ts.map +1 -1
- package/dist/diagnostics/startup-self-probe.js +11 -48
- package/dist/diagnostics/startup-self-probe.js.map +1 -1
- package/dist/errors/hints.d.ts +0 -44
- package/dist/errors/hints.d.ts.map +1 -1
- package/dist/errors/hints.js +0 -32
- package/dist/errors/hints.js.map +1 -1
- package/dist/errors/mask.d.ts +0 -31
- package/dist/errors/mask.d.ts.map +1 -1
- package/dist/errors/mask.js +0 -32
- package/dist/errors/mask.js.map +1 -1
- package/dist/errors/redact-credentials.d.ts +2 -0
- package/dist/errors/redact-credentials.d.ts.map +1 -0
- package/dist/errors/redact-credentials.js +13 -0
- package/dist/errors/redact-credentials.js.map +1 -0
- package/dist/errors/sanitize.d.ts +0 -29
- package/dist/errors/sanitize.d.ts.map +1 -1
- package/dist/errors/sanitize.js +0 -40
- package/dist/errors/sanitize.js.map +1 -1
- package/dist/errors.d.ts +6 -17
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +16 -21
- package/dist/errors.js.map +1 -1
- package/dist/gateway/advanced-read-tool.d.ts +0 -32
- package/dist/gateway/advanced-read-tool.d.ts.map +1 -1
- package/dist/gateway/advanced-read-tool.js +43 -111
- package/dist/gateway/advanced-read-tool.js.map +1 -1
- package/dist/gateway/advanced-tool/discovery.d.ts +1 -12
- package/dist/gateway/advanced-tool/discovery.d.ts.map +1 -1
- package/dist/gateway/advanced-tool/discovery.js +15 -24
- package/dist/gateway/advanced-tool/discovery.js.map +1 -1
- package/dist/gateway/advanced-tool/domains.d.ts +7 -66
- package/dist/gateway/advanced-tool/domains.d.ts.map +1 -1
- package/dist/gateway/advanced-tool/domains.js +36 -80
- package/dist/gateway/advanced-tool/domains.js.map +1 -1
- package/dist/gateway/advanced-tool/execute.d.ts +0 -25
- package/dist/gateway/advanced-tool/execute.d.ts.map +1 -1
- package/dist/gateway/advanced-tool/execute.js +13 -49
- package/dist/gateway/advanced-tool/execute.js.map +1 -1
- package/dist/gateway/advanced-tool/index.d.ts +1 -32
- package/dist/gateway/advanced-tool/index.d.ts.map +1 -1
- package/dist/gateway/advanced-tool/index.js +1 -35
- package/dist/gateway/advanced-tool/index.js.map +1 -1
- package/dist/gateway/advanced-tool/register.d.ts +0 -14
- package/dist/gateway/advanced-tool/register.d.ts.map +1 -1
- package/dist/gateway/advanced-tool/register.js +22 -58
- package/dist/gateway/advanced-tool/register.js.map +1 -1
- package/dist/gateway/advanced-tool.d.ts +0 -11
- package/dist/gateway/advanced-tool.d.ts.map +1 -1
- package/dist/gateway/advanced-tool.js +0 -11
- package/dist/gateway/advanced-tool.js.map +1 -1
- package/dist/gateway/capturing-server.d.ts +3 -88
- package/dist/gateway/capturing-server.d.ts.map +1 -1
- package/dist/gateway/capturing-server.js +0 -77
- package/dist/gateway/capturing-server.js.map +1 -1
- package/dist/gateway/essentials.d.ts +1 -63
- package/dist/gateway/essentials.d.ts.map +1 -1
- package/dist/gateway/essentials.js +9 -178
- package/dist/gateway/essentials.js.map +1 -1
- package/dist/gateway/test-support.d.ts +0 -28
- package/dist/gateway/test-support.d.ts.map +1 -1
- package/dist/gateway/test-support.js +0 -29
- package/dist/gateway/test-support.js.map +1 -1
- package/dist/health-response.d.ts +2 -0
- package/dist/health-response.d.ts.map +1 -0
- package/dist/health-response.js +7 -0
- package/dist/health-response.js.map +1 -0
- package/dist/icon-data.js +0 -3
- package/dist/icon-data.js.map +1 -1
- package/dist/index.d.ts +7 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -130
- package/dist/index.js.map +1 -1
- package/dist/install-skill.d.ts +0 -44
- package/dist/install-skill.d.ts.map +1 -1
- package/dist/install-skill.js +5 -46
- package/dist/install-skill.js.map +1 -1
- package/dist/net/extra-headers.d.ts +0 -58
- package/dist/net/extra-headers.d.ts.map +1 -1
- package/dist/net/extra-headers.js +0 -67
- package/dist/net/extra-headers.js.map +1 -1
- package/dist/net/internal-host.d.ts +0 -31
- package/dist/net/internal-host.d.ts.map +1 -1
- package/dist/net/internal-host.js +7 -65
- package/dist/net/internal-host.js.map +1 -1
- package/dist/net/proxy-dispatcher.d.ts +0 -37
- package/dist/net/proxy-dispatcher.d.ts.map +1 -1
- package/dist/net/proxy-dispatcher.js +0 -40
- package/dist/net/proxy-dispatcher.js.map +1 -1
- package/dist/net/public-fetch.d.ts +53 -0
- package/dist/net/public-fetch.d.ts.map +1 -0
- package/dist/net/public-fetch.js +143 -0
- package/dist/net/public-fetch.js.map +1 -0
- package/dist/net/public-page-report.d.ts +26 -0
- package/dist/net/public-page-report.d.ts.map +1 -0
- package/dist/net/public-page-report.js +145 -0
- package/dist/net/public-page-report.js.map +1 -0
- package/dist/net/redirect-safe-fetch.d.ts +14 -0
- package/dist/net/redirect-safe-fetch.d.ts.map +1 -0
- package/dist/net/redirect-safe-fetch.js +61 -0
- package/dist/net/redirect-safe-fetch.js.map +1 -0
- package/dist/net/session-recovery-fetch.d.ts +7 -0
- package/dist/net/session-recovery-fetch.d.ts.map +1 -0
- package/dist/net/session-recovery-fetch.js +139 -0
- package/dist/net/session-recovery-fetch.js.map +1 -0
- package/dist/net/site-basic-auth.d.ts +1 -59
- package/dist/net/site-basic-auth.d.ts.map +1 -1
- package/dist/net/site-basic-auth.js +5 -94
- package/dist/net/site-basic-auth.js.map +1 -1
- package/dist/package-version.d.ts +2 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +15 -0
- package/dist/package-version.js.map +1 -0
- package/dist/platform/detect.d.ts +0 -54
- package/dist/platform/detect.d.ts.map +1 -1
- package/dist/platform/detect.js +0 -53
- package/dist/platform/detect.js.map +1 -1
- package/dist/platform/index.d.ts +0 -100
- package/dist/platform/index.d.ts.map +1 -1
- package/dist/platform/index.js +0 -106
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/joomla.d.ts +0 -35
- package/dist/platform/joomla.d.ts.map +1 -1
- package/dist/platform/joomla.js +0 -35
- package/dist/platform/joomla.js.map +1 -1
- package/dist/proxy/bridge.d.ts +36 -76
- package/dist/proxy/bridge.d.ts.map +1 -1
- package/dist/proxy/bridge.js +443 -136
- package/dist/proxy/bridge.js.map +1 -1
- package/dist/proxy/confirm-elicitation.d.ts +22 -0
- package/dist/proxy/confirm-elicitation.d.ts.map +1 -0
- package/dist/proxy/confirm-elicitation.js +170 -0
- package/dist/proxy/confirm-elicitation.js.map +1 -0
- package/dist/proxy/confirm-question-contract.d.ts +6 -0
- package/dist/proxy/confirm-question-contract.d.ts.map +1 -0
- package/dist/proxy/confirm-question-contract.js +9 -0
- package/dist/proxy/confirm-question-contract.js.map +1 -0
- package/dist/proxy/confirm-question.d.ts +2 -0
- package/dist/proxy/confirm-question.d.ts.map +1 -0
- package/dist/proxy/confirm-question.js +125 -0
- package/dist/proxy/confirm-question.js.map +1 -0
- package/dist/proxy/instructions.d.ts +4 -0
- package/dist/proxy/instructions.d.ts.map +1 -0
- package/dist/proxy/instructions.js +12 -0
- package/dist/proxy/instructions.js.map +1 -0
- package/dist/proxy/mode.d.ts +14 -9
- package/dist/proxy/mode.d.ts.map +1 -1
- package/dist/proxy/mode.js +58 -34
- package/dist/proxy/mode.js.map +1 -1
- package/dist/proxy/multi-site.d.ts +42 -0
- package/dist/proxy/multi-site.d.ts.map +1 -0
- package/dist/proxy/multi-site.js +134 -0
- package/dist/proxy/multi-site.js.map +1 -0
- package/dist/proxy/request-headers.d.ts +9 -0
- package/dist/proxy/request-headers.d.ts.map +1 -0
- package/dist/proxy/request-headers.js +17 -0
- package/dist/proxy/request-headers.js.map +1 -0
- package/dist/proxy/shadow-tools.d.ts +9 -0
- package/dist/proxy/shadow-tools.d.ts.map +1 -0
- package/dist/proxy/shadow-tools.js +42 -0
- package/dist/proxy/shadow-tools.js.map +1 -0
- package/dist/proxy/tools-list-cap.d.ts +7 -0
- package/dist/proxy/tools-list-cap.d.ts.map +1 -0
- package/dist/proxy/tools-list-cap.js +14 -0
- package/dist/proxy/tools-list-cap.js.map +1 -0
- package/dist/proxy/transport-uncertainty.d.ts +2 -0
- package/dist/proxy/transport-uncertainty.d.ts.map +1 -0
- package/dist/proxy/transport-uncertainty.js +8 -0
- package/dist/proxy/transport-uncertainty.js.map +1 -0
- package/dist/proxy/upstream-error.d.ts +7 -0
- package/dist/proxy/upstream-error.d.ts.map +1 -0
- package/dist/proxy/upstream-error.js +35 -0
- package/dist/proxy/upstream-error.js.map +1 -0
- package/dist/proxy/wire-dialect.d.ts +5 -0
- package/dist/proxy/wire-dialect.d.ts.map +1 -0
- package/dist/proxy/wire-dialect.js +33 -0
- package/dist/proxy/wire-dialect.js.map +1 -0
- package/dist/server.d.ts +14 -82
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +107 -203
- package/dist/server.js.map +1 -1
- package/dist/setup/banner-color.d.ts +13 -0
- package/dist/setup/banner-color.d.ts.map +1 -0
- package/dist/setup/banner-color.js +93 -0
- package/dist/setup/banner-color.js.map +1 -0
- package/dist/setup/banner.d.ts +22 -0
- package/dist/setup/banner.d.ts.map +1 -0
- package/dist/setup/banner.js +119 -0
- package/dist/setup/banner.js.map +1 -0
- package/dist/setup/ca-recovery.d.ts +20 -0
- package/dist/setup/ca-recovery.d.ts.map +1 -0
- package/dist/setup/ca-recovery.js +92 -0
- package/dist/setup/ca-recovery.js.map +1 -0
- package/dist/setup/closing.d.ts +15 -0
- package/dist/setup/closing.d.ts.map +1 -0
- package/dist/setup/closing.js +43 -0
- package/dist/setup/closing.js.map +1 -0
- package/dist/setup/corporate-ca.d.ts +16 -0
- package/dist/setup/corporate-ca.d.ts.map +1 -0
- package/dist/setup/corporate-ca.js +32 -0
- package/dist/setup/corporate-ca.js.map +1 -0
- package/dist/setup/host-platform.d.ts +8 -0
- package/dist/setup/host-platform.d.ts.map +1 -0
- package/dist/setup/host-platform.js +21 -0
- package/dist/setup/host-platform.js.map +1 -0
- package/dist/setup/install-configs.d.ts +18 -0
- package/dist/setup/install-configs.d.ts.map +1 -0
- package/dist/setup/install-configs.js +70 -0
- package/dist/setup/install-configs.js.map +1 -0
- package/dist/setup/multi-site-loop.d.ts +14 -0
- package/dist/setup/multi-site-loop.d.ts.map +1 -0
- package/dist/setup/multi-site-loop.js +20 -0
- package/dist/setup/multi-site-loop.js.map +1 -0
- package/dist/setup/phases.d.ts +18 -0
- package/dist/setup/phases.d.ts.map +1 -0
- package/dist/setup/phases.js +55 -0
- package/dist/setup/phases.js.map +1 -0
- package/dist/setup/registry-remedy.d.ts +12 -0
- package/dist/setup/registry-remedy.d.ts.map +1 -0
- package/dist/setup/registry-remedy.js +37 -0
- package/dist/setup/registry-remedy.js.map +1 -0
- package/dist/setup/resume.d.ts +28 -0
- package/dist/setup/resume.d.ts.map +1 -0
- package/dist/setup/resume.js +99 -0
- package/dist/setup/resume.js.map +1 -0
- package/dist/setup-ca-env.d.ts +0 -30
- package/dist/setup-ca-env.d.ts.map +1 -1
- package/dist/setup-ca-env.js +0 -28
- package/dist/setup-ca-env.js.map +1 -1
- package/dist/setup-ca-reprobe.d.ts +0 -25
- package/dist/setup-ca-reprobe.d.ts.map +1 -1
- package/dist/setup-ca-reprobe.js +0 -34
- package/dist/setup-ca-reprobe.js.map +1 -1
- package/dist/setup-cli-confirm.d.ts +0 -34
- package/dist/setup-cli-confirm.d.ts.map +1 -1
- package/dist/setup-cli-confirm.js +0 -36
- package/dist/setup-cli-confirm.js.map +1 -1
- package/dist/setup-cli.d.ts +6 -167
- package/dist/setup-cli.d.ts.map +1 -1
- package/dist/setup-cli.js +161 -173
- package/dist/setup-cli.js.map +1 -1
- package/dist/setup-npx-spec.d.ts +0 -36
- package/dist/setup-npx-spec.d.ts.map +1 -1
- package/dist/setup-npx-spec.js +0 -38
- package/dist/setup-npx-spec.js.map +1 -1
- package/dist/setup-prompts.d.ts +5 -70
- package/dist/setup-prompts.d.ts.map +1 -1
- package/dist/setup-prompts.js +67 -107
- package/dist/setup-prompts.js.map +1 -1
- package/dist/setup-token.d.ts +0 -34
- package/dist/setup-token.d.ts.map +1 -1
- package/dist/setup-token.js +0 -40
- package/dist/setup-token.js.map +1 -1
- package/dist/setup-wizard-defaults.d.ts +0 -36
- package/dist/setup-wizard-defaults.d.ts.map +1 -1
- package/dist/setup-wizard-defaults.js +0 -75
- package/dist/setup-wizard-defaults.js.map +1 -1
- package/dist/setup-wizard-handshake.d.ts +1 -19
- package/dist/setup-wizard-handshake.d.ts.map +1 -1
- package/dist/setup-wizard-handshake.js +8 -45
- package/dist/setup-wizard-handshake.js.map +1 -1
- package/dist/setup-wizard-types.d.ts +16 -209
- package/dist/setup-wizard-types.d.ts.map +1 -1
- package/dist/setup-wizard-types.js +0 -9
- package/dist/setup-wizard-types.js.map +1 -1
- package/dist/setup-wizard.d.ts +0 -28
- package/dist/setup-wizard.d.ts.map +1 -1
- package/dist/setup-wizard.js +167 -412
- package/dist/setup-wizard.js.map +1 -1
- package/dist/setup.d.ts +0 -15
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +0 -15
- package/dist/setup.js.map +1 -1
- package/dist/sites/bootstrap.d.ts +0 -38
- package/dist/sites/bootstrap.d.ts.map +1 -1
- package/dist/sites/bootstrap.js +0 -44
- package/dist/sites/bootstrap.js.map +1 -1
- package/dist/sites/cli/add-site.d.ts +0 -76
- package/dist/sites/cli/add-site.d.ts.map +1 -1
- package/dist/sites/cli/add-site.js +0 -71
- package/dist/sites/cli/add-site.js.map +1 -1
- package/dist/sites/cli/list-sites.d.ts +0 -31
- package/dist/sites/cli/list-sites.d.ts.map +1 -1
- package/dist/sites/cli/list-sites.js +0 -45
- package/dist/sites/cli/list-sites.js.map +1 -1
- package/dist/sites/cli/remove-site.d.ts +0 -37
- package/dist/sites/cli/remove-site.d.ts.map +1 -1
- package/dist/sites/cli/remove-site.js +0 -37
- package/dist/sites/cli/remove-site.js.map +1 -1
- package/dist/sites/cli/set-default.d.ts +0 -25
- package/dist/sites/cli/set-default.d.ts.map +1 -1
- package/dist/sites/cli/set-default.js +0 -22
- package/dist/sites/cli/set-default.js.map +1 -1
- package/dist/sites/cli/test-site.d.ts +0 -44
- package/dist/sites/cli/test-site.d.ts.map +1 -1
- package/dist/sites/cli/test-site.js +0 -39
- package/dist/sites/cli/test-site.js.map +1 -1
- package/dist/sites/client-pool.d.ts +11 -164
- package/dist/sites/client-pool.d.ts.map +1 -1
- package/dist/sites/client-pool.js +73 -194
- package/dist/sites/client-pool.js.map +1 -1
- package/dist/sites/config-problem.d.ts +2 -0
- package/dist/sites/config-problem.d.ts.map +1 -0
- package/dist/sites/config-problem.js +13 -0
- package/dist/sites/config-problem.js.map +1 -0
- package/dist/sites/env-bridge.d.ts +0 -69
- package/dist/sites/env-bridge.d.ts.map +1 -1
- package/dist/sites/env-bridge.js +2 -71
- package/dist/sites/env-bridge.js.map +1 -1
- package/dist/sites/op-inject.d.ts +23 -0
- package/dist/sites/op-inject.d.ts.map +1 -0
- package/dist/sites/op-inject.js +131 -0
- package/dist/sites/op-inject.js.map +1 -0
- package/dist/sites/paths.d.ts +0 -19
- package/dist/sites/paths.d.ts.map +1 -1
- package/dist/sites/paths.js +0 -19
- package/dist/sites/paths.js.map +1 -1
- package/dist/sites/probe.d.ts +1 -47
- package/dist/sites/probe.d.ts.map +1 -1
- package/dist/sites/probe.js +4 -46
- package/dist/sites/probe.js.map +1 -1
- package/dist/sites/registry.d.ts +4 -135
- package/dist/sites/registry.d.ts.map +1 -1
- package/dist/sites/registry.js +4 -98
- package/dist/sites/registry.js.map +1 -1
- package/dist/sites/schema.d.ts +3 -51
- package/dist/sites/schema.d.ts.map +1 -1
- package/dist/sites/schema.js +6 -52
- package/dist/sites/schema.js.map +1 -1
- package/dist/sites/secret-resolver.d.ts +6 -76
- package/dist/sites/secret-resolver.d.ts.map +1 -1
- package/dist/sites/secret-resolver.js +39 -104
- package/dist/sites/secret-resolver.js.map +1 -1
- package/dist/sites/store.d.ts +12 -69
- package/dist/sites/store.d.ts.map +1 -1
- package/dist/sites/store.js +37 -86
- package/dist/sites/store.js.map +1 -1
- package/dist/sites/substituted-registry.d.ts +11 -0
- package/dist/sites/substituted-registry.d.ts.map +1 -0
- package/dist/sites/substituted-registry.js +13 -0
- package/dist/sites/substituted-registry.js.map +1 -0
- package/dist/sites/tools/fetch-public-url.d.ts +4 -0
- package/dist/sites/tools/fetch-public-url.d.ts.map +1 -0
- package/dist/sites/tools/fetch-public-url.js +231 -0
- package/dist/sites/tools/fetch-public-url.js.map +1 -0
- package/dist/sites/tools/index.d.ts +2 -26
- package/dist/sites/tools/index.d.ts.map +1 -1
- package/dist/sites/tools/index.js +5 -28
- package/dist/sites/tools/index.js.map +1 -1
- package/dist/sites/tools/sites-list.d.ts +3 -47
- package/dist/sites/tools/sites-list.d.ts.map +1 -1
- package/dist/sites/tools/sites-list.js +65 -79
- package/dist/sites/tools/sites-list.js.map +1 -1
- package/dist/sites/tools/sites-overview.d.ts +4 -0
- package/dist/sites/tools/sites-overview.d.ts.map +1 -0
- package/dist/sites/tools/sites-overview.js +209 -0
- package/dist/sites/tools/sites-overview.js.map +1 -0
- package/dist/sites/tools/sites-test.d.ts +2 -45
- package/dist/sites/tools/sites-test.d.ts.map +1 -1
- package/dist/sites/tools/sites-test.js +0 -55
- package/dist/sites/tools/sites-test.js.map +1 -1
- package/dist/sites/tools/use-site.d.ts +2 -45
- package/dist/sites/tools/use-site.d.ts.map +1 -1
- package/dist/sites/tools/use-site.js +29 -78
- package/dist/sites/tools/use-site.js.map +1 -1
- package/dist/sites/unknown-name-result.d.ts +10 -0
- package/dist/sites/unknown-name-result.d.ts.map +1 -0
- package/dist/sites/unknown-name-result.js +13 -0
- package/dist/sites/unknown-name-result.js.map +1 -0
- package/dist/skill-instructions.d.ts +7 -0
- package/dist/skill-instructions.d.ts.map +1 -0
- package/dist/skill-instructions.js +84 -0
- package/dist/skill-instructions.js.map +1 -0
- package/dist/skill-loader.d.ts +0 -102
- package/dist/skill-loader.d.ts.map +1 -1
- package/dist/skill-loader.js +4 -142
- package/dist/skill-loader.js.map +1 -1
- package/dist/skill-manifest.d.ts +0 -20
- package/dist/skill-manifest.d.ts.map +1 -1
- package/dist/skill-manifest.js +1 -21
- package/dist/skill-manifest.js.map +1 -1
- package/dist/skill-topics.d.ts +19 -0
- package/dist/skill-topics.d.ts.map +1 -0
- package/dist/skill-topics.js +181 -0
- package/dist/skill-topics.js.map +1 -0
- package/dist/tools/active-site.d.ts +16 -0
- package/dist/tools/active-site.d.ts.map +1 -0
- package/dist/tools/active-site.js +55 -0
- package/dist/tools/active-site.js.map +1 -0
- package/dist/tools/bind-guidance.d.ts +2 -72
- package/dist/tools/bind-guidance.d.ts.map +1 -1
- package/dist/tools/bind-guidance.js +2 -72
- package/dist/tools/bind-guidance.js.map +1 -1
- package/dist/tools/budgeted-table.d.ts +14 -19
- package/dist/tools/budgeted-table.d.ts.map +1 -1
- package/dist/tools/budgeted-table.js +171 -105
- package/dist/tools/budgeted-table.js.map +1 -1
- package/dist/tools/elements/bind-output-schema.d.ts +69 -0
- package/dist/tools/elements/bind-output-schema.d.ts.map +1 -0
- package/dist/tools/elements/bind-output-schema.js +64 -0
- package/dist/tools/elements/bind-output-schema.js.map +1 -0
- package/dist/tools/elements/builders.d.ts +1 -22
- package/dist/tools/elements/builders.d.ts.map +1 -1
- package/dist/tools/elements/builders.js +147 -133
- package/dist/tools/elements/builders.js.map +1 -1
- package/dist/tools/elements/index.d.ts +0 -26
- package/dist/tools/elements/index.d.ts.map +1 -1
- package/dist/tools/elements/index.js +0 -25
- package/dist/tools/elements/index.js.map +1 -1
- package/dist/tools/elements/read-output-schemas.d.ts +121 -0
- package/dist/tools/elements/read-output-schemas.d.ts.map +1 -0
- package/dist/tools/elements/read-output-schemas.js +225 -0
- package/dist/tools/elements/read-output-schemas.js.map +1 -0
- package/dist/tools/elements/write-output-schemas.d.ts +185 -0
- package/dist/tools/elements/write-output-schemas.d.ts.map +1 -0
- package/dist/tools/elements/write-output-schemas.js +98 -0
- package/dist/tools/elements/write-output-schemas.js.map +1 -0
- package/dist/tools/elements.d.ts +0 -11
- package/dist/tools/elements.d.ts.map +1 -1
- package/dist/tools/elements.js +0 -11
- package/dist/tools/elements.js.map +1 -1
- package/dist/tools/format/health-format.d.ts +26 -43
- package/dist/tools/format/health-format.d.ts.map +1 -1
- package/dist/tools/format/health-format.js +28 -60
- package/dist/tools/format/health-format.js.map +1 -1
- package/dist/tools/format/inspection-format.d.ts +2 -54
- package/dist/tools/format/inspection-format.d.ts.map +1 -1
- package/dist/tools/format/inspection-format.js +33 -119
- package/dist/tools/format/inspection-format.js.map +1 -1
- package/dist/tools/format/pages-format.d.ts +32 -69
- package/dist/tools/format/pages-format.d.ts.map +1 -1
- package/dist/tools/format/pages-format.js +114 -139
- package/dist/tools/format/pages-format.js.map +1 -1
- package/dist/tools/format/sources-format.d.ts +5 -39
- package/dist/tools/format/sources-format.d.ts.map +1 -1
- package/dist/tools/format/sources-format.js +57 -87
- package/dist/tools/format/sources-format.js.map +1 -1
- package/dist/tools/health-output-schemas.d.ts +82 -0
- package/dist/tools/health-output-schemas.d.ts.map +1 -0
- package/dist/tools/health-output-schemas.js +130 -0
- package/dist/tools/health-output-schemas.js.map +1 -0
- package/dist/tools/health.d.ts +2 -20
- package/dist/tools/health.d.ts.map +1 -1
- package/dist/tools/health.js +46 -232
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/index.d.ts +1 -26
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +12 -39
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inspection.d.ts +4 -34
- package/dist/tools/inspection.d.ts.map +1 -1
- package/dist/tools/inspection.js +164 -536
- package/dist/tools/inspection.js.map +1 -1
- package/dist/tools/library.d.ts +115 -127
- package/dist/tools/library.d.ts.map +1 -1
- package/dist/tools/library.js +706 -454
- package/dist/tools/library.js.map +1 -1
- package/dist/tools/local-content/builders.d.ts +1 -17
- package/dist/tools/local-content/builders.d.ts.map +1 -1
- package/dist/tools/local-content/builders.js +103 -46
- package/dist/tools/local-content/builders.js.map +1 -1
- package/dist/tools/local-content/index.d.ts +0 -15
- package/dist/tools/local-content/index.d.ts.map +1 -1
- package/dist/tools/local-content/index.js +0 -14
- package/dist/tools/local-content/index.js.map +1 -1
- package/dist/tools/local-content/schemas.d.ts +31 -47
- package/dist/tools/local-content/schemas.d.ts.map +1 -1
- package/dist/tools/local-content/schemas.js +33 -62
- package/dist/tools/local-content/schemas.js.map +1 -1
- package/dist/tools/modules/builders.d.ts +4 -0
- package/dist/tools/modules/builders.d.ts.map +1 -0
- package/dist/tools/modules/builders.js +58 -0
- package/dist/tools/modules/builders.js.map +1 -0
- package/dist/tools/modules/index.d.ts +2 -0
- package/dist/tools/modules/index.d.ts.map +1 -0
- package/dist/tools/modules/index.js +2 -0
- package/dist/tools/modules/index.js.map +1 -0
- package/dist/tools/modules/module-id-schema.d.ts +4 -0
- package/dist/tools/modules/module-id-schema.d.ts.map +1 -0
- package/dist/tools/modules/module-id-schema.js +14 -0
- package/dist/tools/modules/module-id-schema.js.map +1 -0
- package/dist/tools/modules/module-type-fields.d.ts +10 -0
- package/dist/tools/modules/module-type-fields.d.ts.map +1 -0
- package/dist/tools/modules/module-type-fields.js +34 -0
- package/dist/tools/modules/module-type-fields.js.map +1 -0
- package/dist/tools/modules/modules-create.d.ts +59 -0
- package/dist/tools/modules/modules-create.d.ts.map +1 -0
- package/dist/tools/modules/modules-create.js +154 -0
- package/dist/tools/modules/modules-create.js.map +1 -0
- package/dist/tools/modules/modules-delete.d.ts +51 -0
- package/dist/tools/modules/modules-delete.d.ts.map +1 -0
- package/dist/tools/modules/modules-delete.js +180 -0
- package/dist/tools/modules/modules-delete.js.map +1 -0
- package/dist/tools/modules/modules-update.d.ts +43 -0
- package/dist/tools/modules/modules-update.d.ts.map +1 -0
- package/dist/tools/modules/modules-update.js +177 -0
- package/dist/tools/modules/modules-update.js.map +1 -0
- package/dist/tools/modules/placement-hints.d.ts +5 -0
- package/dist/tools/modules/placement-hints.d.ts.map +1 -0
- package/dist/tools/modules/placement-hints.js +90 -0
- package/dist/tools/modules/placement-hints.js.map +1 -0
- package/dist/tools/modules/placement-ways.d.ts +13 -0
- package/dist/tools/modules/placement-ways.d.ts.map +1 -0
- package/dist/tools/modules/placement-ways.js +61 -0
- package/dist/tools/modules/placement-ways.js.map +1 -0
- package/dist/tools/multi-items/builders.d.ts +1 -21
- package/dist/tools/multi-items/builders.d.ts.map +1 -1
- package/dist/tools/multi-items/builders.js +22 -65
- package/dist/tools/multi-items/builders.js.map +1 -1
- package/dist/tools/multi-items/index.d.ts +0 -15
- package/dist/tools/multi-items/index.d.ts.map +1 -1
- package/dist/tools/multi-items/index.js +0 -15
- package/dist/tools/multi-items/index.js.map +1 -1
- package/dist/tools/multi-items/item-container-map.d.ts +0 -35
- package/dist/tools/multi-items/item-container-map.d.ts.map +1 -1
- package/dist/tools/multi-items/item-container-map.js +0 -35
- package/dist/tools/multi-items/item-container-map.js.map +1 -1
- package/dist/tools/multi-items/output-schemas.d.ts +101 -0
- package/dist/tools/multi-items/output-schemas.d.ts.map +1 -0
- package/dist/tools/multi-items/output-schemas.js +74 -0
- package/dist/tools/multi-items/output-schemas.js.map +1 -0
- package/dist/tools/navigation/builders.d.ts +1 -16
- package/dist/tools/navigation/builders.d.ts.map +1 -1
- package/dist/tools/navigation/builders.js +5 -27
- package/dist/tools/navigation/builders.js.map +1 -1
- package/dist/tools/navigation/index.d.ts +0 -13
- package/dist/tools/navigation/index.d.ts.map +1 -1
- package/dist/tools/navigation/index.js +0 -12
- package/dist/tools/navigation/index.js.map +1 -1
- package/dist/tools/navigation/schemas.d.ts +0 -30
- package/dist/tools/navigation/schemas.d.ts.map +1 -1
- package/dist/tools/navigation/schemas.js +0 -30
- package/dist/tools/navigation/schemas.js.map +1 -1
- package/dist/tools/output-schema-metadata.d.ts +5 -0
- package/dist/tools/output-schema-metadata.d.ts.map +1 -0
- package/dist/tools/output-schema-metadata.js +83 -0
- package/dist/tools/output-schema-metadata.js.map +1 -0
- package/dist/tools/pages/builders.d.ts +1 -41
- package/dist/tools/pages/builders.d.ts.map +1 -1
- package/dist/tools/pages/builders.js +320 -405
- package/dist/tools/pages/builders.js.map +1 -1
- package/dist/tools/pages/header-transparency.d.ts +2 -32
- package/dist/tools/pages/header-transparency.d.ts.map +1 -1
- package/dist/tools/pages/header-transparency.js +9 -43
- package/dist/tools/pages/header-transparency.js.map +1 -1
- package/dist/tools/pages/index.d.ts +1 -19
- package/dist/tools/pages/index.d.ts.map +1 -1
- package/dist/tools/pages/index.js +1 -19
- package/dist/tools/pages/index.js.map +1 -1
- package/dist/tools/pages/schemas.d.ts +323 -241
- package/dist/tools/pages/schemas.d.ts.map +1 -1
- package/dist/tools/pages/schemas.js +514 -549
- package/dist/tools/pages/schemas.js.map +1 -1
- package/dist/tools/pages/write-output-schemas.d.ts +139 -0
- package/dist/tools/pages/write-output-schemas.d.ts.map +1 -0
- package/dist/tools/pages/write-output-schemas.js +138 -0
- package/dist/tools/pages/write-output-schemas.js.map +1 -0
- package/dist/tools/pages.d.ts +0 -11
- package/dist/tools/pages.d.ts.map +1 -1
- package/dist/tools/pages.js +0 -11
- package/dist/tools/pages.js.map +1 -1
- package/dist/tools/pool-resolve-helper.d.ts +0 -52
- package/dist/tools/pool-resolve-helper.d.ts.map +1 -1
- package/dist/tools/pool-resolve-helper.js +29 -104
- package/dist/tools/pool-resolve-helper.js.map +1 -1
- package/dist/tools/progress-phases.d.ts +0 -42
- package/dist/tools/progress-phases.d.ts.map +1 -1
- package/dist/tools/progress-phases.js +0 -44
- package/dist/tools/progress-phases.js.map +1 -1
- package/dist/tools/response-budget.d.ts +0 -69
- package/dist/tools/response-budget.d.ts.map +1 -1
- package/dist/tools/response-budget.js +0 -127
- package/dist/tools/response-budget.js.map +1 -1
- package/dist/tools/shared-schemas.d.ts +11 -65
- package/dist/tools/shared-schemas.d.ts.map +1 -1
- package/dist/tools/shared-schemas.js +40 -123
- package/dist/tools/shared-schemas.js.map +1 -1
- package/dist/tools/skill/builders.d.ts +3 -0
- package/dist/tools/skill/builders.d.ts.map +1 -0
- package/dist/tools/skill/builders.js +81 -0
- package/dist/tools/skill/builders.js.map +1 -0
- package/dist/tools/sources/builders.d.ts +1 -11
- package/dist/tools/sources/builders.d.ts.map +1 -1
- package/dist/tools/sources/builders.js +194 -170
- package/dist/tools/sources/builders.js.map +1 -1
- package/dist/tools/sources/index.d.ts +0 -17
- package/dist/tools/sources/index.d.ts.map +1 -1
- package/dist/tools/sources/index.js +0 -16
- package/dist/tools/sources/index.js.map +1 -1
- package/dist/tools/sources/read-output-schemas.d.ts +101 -0
- package/dist/tools/sources/read-output-schemas.d.ts.map +1 -0
- package/dist/tools/sources/read-output-schemas.js +105 -0
- package/dist/tools/sources/read-output-schemas.js.map +1 -0
- package/dist/tools/sources/source-name-argument.d.ts +17 -0
- package/dist/tools/sources/source-name-argument.d.ts.map +1 -0
- package/dist/tools/sources/source-name-argument.js +64 -0
- package/dist/tools/sources/source-name-argument.js.map +1 -0
- package/dist/tools/sources/write-output-schemas.d.ts +100 -0
- package/dist/tools/sources/write-output-schemas.d.ts.map +1 -0
- package/dist/tools/sources/write-output-schemas.js +61 -0
- package/dist/tools/sources/write-output-schemas.js.map +1 -0
- package/dist/tools/sources.d.ts +0 -12
- package/dist/tools/sources.d.ts.map +1 -1
- package/dist/tools/sources.js +0 -12
- package/dist/tools/sources.js.map +1 -1
- package/dist/tools/sparse-fields.d.ts +16 -232
- package/dist/tools/sparse-fields.d.ts.map +1 -1
- package/dist/tools/sparse-fields.js +178 -242
- package/dist/tools/sparse-fields.js.map +1 -1
- package/dist/tools/strict-input.d.ts +5 -0
- package/dist/tools/strict-input.d.ts.map +1 -0
- package/dist/tools/strict-input.js +55 -0
- package/dist/tools/strict-input.js.map +1 -0
- package/dist/tools/sublayout/builders.d.ts +1 -21
- package/dist/tools/sublayout/builders.d.ts.map +1 -1
- package/dist/tools/sublayout/builders.js +10 -39
- package/dist/tools/sublayout/builders.js.map +1 -1
- package/dist/tools/sublayout/index.d.ts +1 -11
- package/dist/tools/sublayout/index.d.ts.map +1 -1
- package/dist/tools/sublayout/index.js +1 -11
- package/dist/tools/sublayout/index.js.map +1 -1
- package/dist/tools/sublayout/output-schemas.d.ts +57 -0
- package/dist/tools/sublayout/output-schemas.d.ts.map +1 -0
- package/dist/tools/sublayout/output-schemas.js +51 -0
- package/dist/tools/sublayout/output-schemas.js.map +1 -0
- package/dist/tools/tool-builder/annotations.d.ts +1 -49
- package/dist/tools/tool-builder/annotations.d.ts.map +1 -1
- package/dist/tools/tool-builder/annotations.js +9 -49
- package/dist/tools/tool-builder/annotations.js.map +1 -1
- package/dist/tools/tool-builder/confirm-preview.d.ts +24 -0
- package/dist/tools/tool-builder/confirm-preview.d.ts.map +1 -0
- package/dist/tools/tool-builder/confirm-preview.js +37 -0
- package/dist/tools/tool-builder/confirm-preview.js.map +1 -0
- package/dist/tools/tool-builder/define.d.ts +6 -26
- package/dist/tools/tool-builder/define.d.ts.map +1 -1
- package/dist/tools/tool-builder/define.js +0 -25
- package/dist/tools/tool-builder/define.js.map +1 -1
- package/dist/tools/tool-builder/index.d.ts +3 -26
- package/dist/tools/tool-builder/index.d.ts.map +1 -1
- package/dist/tools/tool-builder/index.js +2 -25
- package/dist/tools/tool-builder/index.js.map +1 -1
- package/dist/tools/tool-builder/results.d.ts +4 -124
- package/dist/tools/tool-builder/results.d.ts.map +1 -1
- package/dist/tools/tool-builder/results.js +258 -263
- package/dist/tools/tool-builder/results.js.map +1 -1
- package/dist/tools/tool-builder/types.d.ts +5 -59
- package/dist/tools/tool-builder/types.d.ts.map +1 -1
- package/dist/tools/tool-builder/types.js +0 -7
- package/dist/tools/tool-builder/types.js.map +1 -1
- package/dist/tools/tool-builder.d.ts +0 -14
- package/dist/tools/tool-builder.d.ts.map +1 -1
- package/dist/tools/tool-builder.js +0 -14
- package/dist/tools/tool-builder.js.map +1 -1
- package/dist/uninstall-skill.d.ts +0 -41
- package/dist/uninstall-skill.d.ts.map +1 -1
- package/dist/uninstall-skill.js +0 -49
- package/dist/uninstall-skill.js.map +1 -1
- package/dist/uninstall.d.ts +0 -41
- package/dist/uninstall.d.ts.map +1 -1
- package/dist/uninstall.js +1 -29
- package/dist/uninstall.js.map +1 -1
- package/dist/update/update-check.d.ts +0 -26
- package/dist/update/update-check.d.ts.map +1 -1
- package/dist/update/update-check.js +0 -25
- package/dist/update/update-check.js.map +1 -1
- package/manifest.json +4 -5
- package/package.json +4 -4
- package/skills/yt-builder-mcp/SKILL.md +1857 -400
- package/dist/tools/elements/handlers-write.d.ts +0 -89
- package/dist/tools/elements/handlers-write.d.ts.map +0 -1
- package/dist/tools/elements/handlers-write.js +0 -797
- package/dist/tools/elements/handlers-write.js.map +0 -1
- package/dist/tools/elements/handlers.d.ts +0 -130
- package/dist/tools/elements/handlers.d.ts.map +0 -1
- package/dist/tools/elements/handlers.js +0 -581
- package/dist/tools/elements/handlers.js.map +0 -1
- package/dist/tools/elements/layout-hierarchy-rules.d.ts +0 -62
- package/dist/tools/elements/layout-hierarchy-rules.d.ts.map +0 -1
- package/dist/tools/elements/layout-hierarchy-rules.js +0 -154
- package/dist/tools/elements/layout-hierarchy-rules.js.map +0 -1
- package/dist/tools/elements/schema-validation.d.ts +0 -218
- package/dist/tools/elements/schema-validation.d.ts.map +0 -1
- package/dist/tools/elements/schema-validation.js +0 -685
- package/dist/tools/elements/schema-validation.js.map +0 -1
- package/dist/tools/elicitation.d.ts +0 -87
- package/dist/tools/elicitation.d.ts.map +0 -1
- package/dist/tools/elicitation.js +0 -100
- package/dist/tools/elicitation.js.map +0 -1
- package/dist/tools/format/elements-format.d.ts +0 -75
- package/dist/tools/format/elements-format.d.ts.map +0 -1
- package/dist/tools/format/elements-format.js +0 -368
- package/dist/tools/format/elements-format.js.map +0 -1
- package/dist/tools/format/source-schema-format.d.ts +0 -74
- package/dist/tools/format/source-schema-format.d.ts.map +0 -1
- package/dist/tools/format/source-schema-format.js +0 -208
- package/dist/tools/format/source-schema-format.js.map +0 -1
- package/dist/tools/layout-flatten.d.ts +0 -84
- package/dist/tools/layout-flatten.d.ts.map +0 -1
- package/dist/tools/layout-flatten.js +0 -129
- package/dist/tools/layout-flatten.js.map +0 -1
- package/dist/tools/local-content/handlers.d.ts +0 -56
- package/dist/tools/local-content/handlers.d.ts.map +0 -1
- package/dist/tools/local-content/handlers.js +0 -320
- package/dist/tools/local-content/handlers.js.map +0 -1
- package/dist/tools/multi-items/clean-implode-handler.d.ts +0 -37
- package/dist/tools/multi-items/clean-implode-handler.d.ts.map +0 -1
- package/dist/tools/multi-items/clean-implode-handler.js +0 -65
- package/dist/tools/multi-items/clean-implode-handler.js.map +0 -1
- package/dist/tools/multi-items/format/inspection-format.d.ts +0 -41
- package/dist/tools/multi-items/format/inspection-format.d.ts.map +0 -1
- package/dist/tools/multi-items/format/inspection-format.js +0 -64
- package/dist/tools/multi-items/format/inspection-format.js.map +0 -1
- package/dist/tools/multi-items/inspect-handler.d.ts +0 -97
- package/dist/tools/multi-items/inspect-handler.d.ts.map +0 -1
- package/dist/tools/multi-items/inspect-handler.js +0 -323
- package/dist/tools/multi-items/inspect-handler.js.map +0 -1
- package/dist/tools/multi-items/inspection-cache.d.ts +0 -85
- package/dist/tools/multi-items/inspection-cache.d.ts.map +0 -1
- package/dist/tools/multi-items/inspection-cache.js +0 -126
- package/dist/tools/multi-items/inspection-cache.js.map +0 -1
- package/dist/tools/navigation/handlers.d.ts +0 -30
- package/dist/tools/navigation/handlers.d.ts.map +0 -1
- package/dist/tools/navigation/handlers.js +0 -41
- package/dist/tools/navigation/handlers.js.map +0 -1
- package/dist/tools/pages/handlers-audit.d.ts +0 -140
- package/dist/tools/pages/handlers-audit.d.ts.map +0 -1
- package/dist/tools/pages/handlers-audit.js +0 -714
- package/dist/tools/pages/handlers-audit.js.map +0 -1
- package/dist/tools/pages/handlers-read.d.ts +0 -133
- package/dist/tools/pages/handlers-read.d.ts.map +0 -1
- package/dist/tools/pages/handlers-read.js +0 -1061
- package/dist/tools/pages/handlers-read.js.map +0 -1
- package/dist/tools/pages/handlers-write.d.ts +0 -212
- package/dist/tools/pages/handlers-write.d.ts.map +0 -1
- package/dist/tools/pages/handlers-write.js +0 -419
- package/dist/tools/pages/handlers-write.js.map +0 -1
- package/dist/tools/pages/target.d.ts +0 -162
- package/dist/tools/pages/target.d.ts.map +0 -1
- package/dist/tools/pages/target.js +0 -239
- package/dist/tools/pages/target.js.map +0 -1
- package/dist/tools/sources/handlers-bind.d.ts +0 -148
- package/dist/tools/sources/handlers-bind.d.ts.map +0 -1
- package/dist/tools/sources/handlers-bind.js +0 -657
- package/dist/tools/sources/handlers-bind.js.map +0 -1
- package/dist/tools/sources/handlers.d.ts +0 -116
- package/dist/tools/sources/handlers.d.ts.map +0 -1
- package/dist/tools/sources/handlers.js +0 -289
- package/dist/tools/sources/handlers.js.map +0 -1
- package/dist/tools/sublayout/inspect-sublayout-handler.d.ts +0 -95
- package/dist/tools/sublayout/inspect-sublayout-handler.d.ts.map +0 -1
- package/dist/tools/sublayout/inspect-sublayout-handler.js +0 -181
- package/dist/tools/sublayout/inspect-sublayout-handler.js.map +0 -1
|
@@ -12,18 +12,20 @@ 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
|
-
|
|
15
|
+
`@wootsup/yt-builder-mcp` server. The server catalogues 60 typed, scoped
|
|
16
|
+
tools (62 callable end-to-end with the two gateways). 49 of them
|
|
17
17
|
advertised as first-class entries in `tools/list`
|
|
18
|
-
(
|
|
18
|
+
(45 essential L1 forwards + 2 direct L3 entries + 2 gateways), and 13 advanced
|
|
19
19
|
tools reachable through the gateways. The write-capable
|
|
20
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
|
+
(inspect_sublayout, layout_query, local_content_types,
|
|
23
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
|
-
per-call write-approval prompt. This 3-lane split
|
|
26
|
-
|
|
25
|
+
per-call write-approval prompt. This 3-lane split exists because Cursor (and
|
|
26
|
+
similar hosts) cap a server at roughly 40 first-class `tools/list` entries —
|
|
27
|
+
it keeps the advertised surface small while every catalogued tool stays
|
|
28
|
+
fully reachable through the gateways.
|
|
27
29
|
|
|
28
30
|
## How to use this MCP server
|
|
29
31
|
|
|
@@ -54,7 +56,7 @@ Some clients (notably Claude Desktop with the `.mcpb` bundle) expose **two**
|
|
|
54
56
|
entries when the user types "YT Builder MCP" into the picker:
|
|
55
57
|
|
|
56
58
|
- **`YT Builder MCP for YOOtheme Pro (unofficial)`**: the MCP **server**.
|
|
57
|
-
Provides the
|
|
59
|
+
Provides the 49 first-class tools (45 essential, 2 direct, 2 gateways).
|
|
58
60
|
The `yootheme_builder_advanced` gateway routes all 13 advanced tools; the
|
|
59
61
|
read-only `yootheme_builder_advanced_read` gateway routes the read-only
|
|
60
62
|
subset without a write-approval prompt.
|
|
@@ -82,39 +84,40 @@ The server exposes:
|
|
|
82
84
|
- **2 direct top-level tools**, always callable, always in `tools/list`:
|
|
83
85
|
`yootheme_builder_health` and `yootheme_builder_diagnose`. These are
|
|
84
86
|
the "the gateway itself might be broken" escape hatch.
|
|
85
|
-
- **
|
|
86
|
-
(pages_list, pages_resolve, pages_create, pages_delete,
|
|
87
|
-
get_etag, element_list / add / update_settings / set_status / get / move / clone / delete,
|
|
87
|
+
- **45 essential forwarded tools**: common reads + the most-used writes
|
|
88
|
+
(pages_list, pages_resolve, pages_create, pages_delete, pages_set_status, pages_restore, page_generate_dynamic, scaffold_section,
|
|
89
|
+
get_etag, get_skill, element_list / add / update_settings / set_status / get / move / clone / delete,
|
|
88
90
|
page_get_layout, sources_list, source_get_schema, element_types_list, element_type_get_schema,
|
|
89
91
|
template_summary, inspect_multi_items_binding, library_browse, library_get, library_insert,
|
|
90
|
-
image_search, image_import,
|
|
92
|
+
image_search, image_import, modules_delete,
|
|
91
93
|
sites_list, sites_test,
|
|
92
|
-
use_site, the bind→verify→save→publish chain element_bind_source /
|
|
93
|
-
element_get_binding / page_save / page_publish, and the archive-rebind /
|
|
94
|
-
refactor surface render_preview / element_set_source /
|
|
94
|
+
use_site, the bind→verify→save→publish→unbind chain element_bind_source /
|
|
95
|
+
element_get_binding / page_save / page_publish / element_unbind_source, and the archive-rebind /
|
|
96
|
+
refactor surface render_preview / element_set_source / repair_source_references /
|
|
95
97
|
template_set_type). Always advertised in
|
|
96
98
|
`tools/list` so AI clients see them first-class. `element_get_binding`
|
|
97
99
|
(B4) is L1 so the verify half of a bind is a direct call, not a
|
|
98
100
|
gateway hop. `source_get_schema` (U4-L1) is L1 so the archive/category
|
|
99
101
|
binding entry-point — introspect a source's query methods + relations +
|
|
100
102
|
the current-term archive-context convention before binding — is a direct
|
|
101
|
-
call too. `render_preview`, `element_set_source`, `
|
|
102
|
-
`template_set_type` (customer HANDOFF-4/5) are L1 so the core write/refactor +
|
|
103
|
+
call too. `render_preview`, `element_set_source`, `repair_source_references`
|
|
104
|
+
and `template_set_type` (customer HANDOFF-4/5) are L1 so the core write/refactor +
|
|
103
105
|
render-verify tools an agent reaches for after a taxonomy/CPT rename — set a
|
|
104
106
|
precomposed source at a pointer, render-verify the saved page is not silently
|
|
105
|
-
blank,
|
|
107
|
+
blank, repair a source/field binding REFERENCE template-wide (the registered
|
|
108
|
+
source itself is untouched), re-type an orphaned archive/single
|
|
106
109
|
template — are direct calls, not undiscoverable gateway hops. `scaffold_section`
|
|
107
110
|
creates a valid
|
|
108
111
|
section → row → column hull in one call — the easiest correct way to
|
|
109
112
|
start a layout block (content placed anywhere else is rejected by the
|
|
110
|
-
structural-hierarchy guard). `library_browse` (
|
|
113
|
+
structural-hierarchy guard). `library_browse` (Layout Library) is L1
|
|
111
114
|
so browsing the YOOtheme Pro Library catalog — pre-designed page layouts and
|
|
112
115
|
element presets, filterable by topic/type/role/website/element_type/query —
|
|
113
116
|
is a direct call before hand-building a section from scratch. `library_get`
|
|
114
|
-
(
|
|
117
|
+
(Layout Library) is L1 so fetching a SINGLE browsed item's full tree
|
|
115
118
|
+ derived section list (`{id, name, path, element_count}` per top-level
|
|
116
119
|
section) — the natural next call once `library_browse` returns an `id` —
|
|
117
|
-
is a direct call too. `library_insert` (
|
|
120
|
+
is a direct call too. `library_insert` (Layout Library) is L1 so
|
|
118
121
|
composing + persisting a browsed/fetched item (layout, section, or preset)
|
|
119
122
|
onto a page or existing template — target `new_page` / `{template_id,
|
|
120
123
|
element_path}` / `sublayout` — through the SAME hardened write chain as
|
|
@@ -124,24 +127,98 @@ The server exposes:
|
|
|
124
127
|
the chosen photo into the site's Media Library, are direct calls — the
|
|
125
128
|
step that turns a placeholder-filled skeleton into a finished-looking
|
|
126
129
|
page (see "Fill image slots with real on-topic photos" under Layout
|
|
127
|
-
Library).
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
Library). `get_skill` (Design E2, 2026-08-10) is L1 because it is the
|
|
131
|
+
Rettungsanker back to this document: the connect-time `instructions`
|
|
132
|
+
field now carries only a short curated preamble instead of the full
|
|
133
|
+
SKILL.md, so without this tool being directly callable a cold agent
|
|
134
|
+
would have no way to reach the rest of the guide. Call it without
|
|
135
|
+
`topic` for the list of chapters, or with `topic: "<slug>"` for one
|
|
136
|
+
chapter verbatim. `element_unbind_source` is L1 so the write half of
|
|
137
|
+
the bind pair is discoverable the same way its siblings
|
|
138
|
+
(`element_bind_source`, `element_get_binding`, `element_set_source`)
|
|
139
|
+
already are — a customer looking for "how do I remove a binding" could
|
|
140
|
+
otherwise not find it by name in `tools/list`, nor from any of the
|
|
141
|
+
other three tools' own descriptions. `pages_restore` is L1 for the same
|
|
142
|
+
discoverability reason: two cold-customer runs each measured the same gap
|
|
143
|
+
from opposite ends — a trashed page/article could not be found, and even
|
|
144
|
+
once found there was no correct way back — and one caller searched
|
|
145
|
+
explicitly for "restore"/"untrash"/"recover" and found nothing, because a
|
|
146
|
+
gateway-only placement is discoverable only to a caller who already knows
|
|
147
|
+
the tool exists.
|
|
148
|
+
- **13 advanced captured tools**: everything else (
|
|
149
|
+
clean_implode_directives,
|
|
130
150
|
`inspect_sublayout`, `layout_query`, `library_save_as`, the two Local
|
|
131
151
|
Content Read tools `local_content_types` / `local_content_read`,
|
|
132
152
|
`pages_audit`, `library_delete` — the INVERSE of `library_save_as` —
|
|
133
153
|
`heading_outline`, a real doc-order H1-H6 outline of one page,
|
|
134
|
-
`navigation`, the site's menus as a parent -> children tree,
|
|
135
|
-
`header_transparency_set`, the transparent-header inspect/repair
|
|
154
|
+
`navigation`, the site's menus as a parent -> children tree,
|
|
155
|
+
`header_transparency_set`, the transparent-header inspect/repair,
|
|
156
|
+
`modules_update`, the widget/module write surface, and
|
|
157
|
+
`modules_create`, the widget/module CREATE surface).
|
|
158
|
+
`modules_delete`, the widget/module DELETE surface, is first-class (L1) —
|
|
159
|
+
not one of these 14 (live-test-befunde, 2026-08-11): the one
|
|
160
|
+
gateway-only destructive tool that removes data OUTSIDE the layout tree (a
|
|
161
|
+
Joomla module row + its `#__modules_menu` assignments), so a host can gate
|
|
162
|
+
it individually instead of only via the all-or-nothing "Advanced Tool
|
|
163
|
+
Gateway" permission.
|
|
164
|
+
To remove a widget/module — the clean-up verb create/update lacked, so a
|
|
165
|
+
probe no longer leaves permanent litter on a customer site — pass the widget
|
|
166
|
+
`id` and PREVIEW first (`confirm` defaults FALSE and the call deletes
|
|
167
|
+
NOTHING, answering with the row plus `usage.element_refs` /
|
|
168
|
+
`usage.assigned_pages`):
|
|
169
|
+
`yootheme_builder_modules_delete({ id: "custom_html-2" })`,
|
|
170
|
+
then repeat with `confirm: true` AND an `intent` — a call missing or
|
|
171
|
+
misnaming it is refused with a 400 naming exactly which intents are
|
|
172
|
+
possible for the module's CURRENT state, each with its own
|
|
173
|
+
`effect`/`reversible`/`native_name`, so calling with neither `confirm` nor
|
|
174
|
+
`intent` doubles as that discovery step.
|
|
175
|
+
WHICH INTENTS ARE OFFERED DEPENDS ON THE PLATFORM AND STATE, because each
|
|
176
|
+
arm mirrors its own CMS rather than inventing a common one. `intent:
|
|
177
|
+
"permanent"` is offered on BOTH platforms and deletes the row for good —
|
|
178
|
+
irreversible, and `modules_create` makes a NEW id with default fields
|
|
179
|
+
rather than restoring this one. `intent: "trash"` exists ONLY on JOOMLA
|
|
180
|
+
and is REVERSIBLE: it sets `published = -2` — the module manager offers
|
|
181
|
+
`Trash` and no direct `Delete` at all, and its `Delete` button exists only
|
|
182
|
+
in the *Trashed* view. The row and its `#__modules_menu` assignments
|
|
183
|
+
survive and Joomla restores both; it does stop rendering at once (Joomla
|
|
184
|
+
loads modules with `published = 1`), so an element bound to it shows
|
|
185
|
+
nothing meanwhile. Joomla offers `"permanent"` directly too — trashing
|
|
186
|
+
first is no longer required to remove a module for good — but once a
|
|
187
|
+
module is ALREADY trashed, `"trash"` stops being offered (only
|
|
188
|
+
`"permanent"` empties it from there). On WORDPRESS widgets have no trash
|
|
189
|
+
step at all (`widgets.php` offers deactivate-by-drag and an immediate
|
|
190
|
+
`Delete` link, not a recoverable one), so only `"permanent"` is ever
|
|
191
|
+
offered there and the confirmed call is always the irreversible one. Read
|
|
192
|
+
the MEASURED `trashed` / `deleted` flags in the response rather than
|
|
193
|
+
assuming which happened.
|
|
194
|
+
Only wanted it off the page, not gone? That is not a removal at all, and
|
|
195
|
+
`modules_delete` is the wrong tool: `modules_update({ id, active: false })`
|
|
196
|
+
parks the widget in WordPress's **Inactive Widgets** area (settings stay
|
|
197
|
+
intact, dragging it back into a widget area restores it) or unpublishes the
|
|
198
|
+
module on Joomla (`published = 0`, position kept) — the same wording the
|
|
199
|
+
400 response's `notes` gives when you call `modules_delete` without an
|
|
200
|
+
`intent`.
|
|
201
|
+
A layout element that references the id DIRECTLY
|
|
202
|
+
(`usage.element_refs` with `via:"element"`) BLOCKS the IRREVERSIBLE step with
|
|
203
|
+
a 409 naming every stranded location, until you either remove those elements
|
|
204
|
+
with `element_delete` or pass `acknowledge_element_refs`: true. Joomla's
|
|
205
|
+
reversible `"trash"` step is never refused — a restore puts the reference
|
|
206
|
+
back in working order — so on that platform the 409 can only come from
|
|
207
|
+
`intent: "permanent"`. An INDIRECT
|
|
208
|
+
reference (`via:"position"`, a `module_position` element naming the POSITION
|
|
209
|
+
this module sits in) never blocks, because it points at a position rather
|
|
210
|
+
than at this module and stays valid for whatever else occupies it.
|
|
211
|
+
`assigned_pages` never blocks either: a Joomla module's `#__modules_menu`
|
|
212
|
+
rows are its OWN rows and go with it.
|
|
136
213
|
Reachable through one gateway tool:
|
|
137
214
|
`yootheme_builder_advanced({ tool: "<name>", arguments: { ... } })`. For
|
|
138
215
|
example, to inspect a panel/grid_item `< SUBLAYOUT`:
|
|
139
|
-
`yootheme_builder_advanced({ tool: "yootheme_builder_inspect_sublayout", arguments: {
|
|
216
|
+
`yootheme_builder_advanced({ tool: "yootheme_builder_inspect_sublayout", arguments: { layout_id, element_path } })`.
|
|
140
217
|
And to find elements by a structured filter (G5 — no expression language,
|
|
141
|
-
AND-combined `
|
|
142
|
-
`yootheme_builder_advanced({ tool: "yootheme_builder_layout_query", arguments: {
|
|
218
|
+
AND-combined `element_type`/`name_matches`/`has_binding`/`max_depth`/`under_section`):
|
|
219
|
+
`yootheme_builder_advanced({ tool: "yootheme_builder_layout_query", arguments: { layout_id, element_type: "headline", has_binding: false, under_section: "Hero" } })`
|
|
143
220
|
(it's read-only, so the read gateway `yootheme_builder_advanced_read({ tool: "yootheme_builder_layout_query", … })` works too).
|
|
144
|
-
And to save a page/subtree INTO "My Layouts" (
|
|
221
|
+
And to save a page/subtree INTO "My Layouts" (the reverse of
|
|
145
222
|
`library_insert`; omit `element_path` to save the whole page):
|
|
146
223
|
`yootheme_builder_advanced({ tool: "yootheme_builder_library_save_as", arguments: { source: { template_id, element_path }, name: "Hero Section" } })`.
|
|
147
224
|
It flags `invalid_source_possible: true` when the saved subtree carries
|
|
@@ -162,7 +239,7 @@ The server exposes:
|
|
|
162
239
|
And to get a real, doc-order H1-H6 outline of ONE page (each heading with
|
|
163
240
|
its source element and TEXT, plus the alt-status of every image) —
|
|
164
241
|
read-only STRUCTURE inspection, not findings:
|
|
165
|
-
`yootheme_builder_advanced_read({ tool: "yootheme_builder_heading_outline", arguments: {
|
|
242
|
+
`yootheme_builder_advanced_read({ tool: "yootheme_builder_heading_outline", arguments: { layout_id } })`
|
|
166
243
|
(or via the write gateway: `yootheme_builder_advanced({ tool: "yootheme_builder_heading_outline" })`).
|
|
167
244
|
And to see the site's Haupt- + Sub-Menüstruktur as a parent -> children
|
|
168
245
|
TREE (not a flat list) — for "which page sits under which menu item"
|
|
@@ -179,24 +256,55 @@ The server exposes:
|
|
|
179
256
|
TRUE, so this call reports the current state, the theme-declared
|
|
180
257
|
`--uk-inverse`, `risk_class`, `confidence` and `would_change` without
|
|
181
258
|
touching anything:
|
|
182
|
-
`yootheme_builder_advanced({ tool: "yootheme_builder_header_transparency_set", arguments: {
|
|
259
|
+
`yootheme_builder_advanced({ tool: "yootheme_builder_header_transparency_set", arguments: { layout_id } })`
|
|
183
260
|
Repeat with `mode: "auto", dry_run: false, etag` to write the theme-declared
|
|
184
261
|
colour. See "Transparent header (the exception — never a default)".
|
|
262
|
+
And to update a widget's (WordPress) or module's (Joomla) title,
|
|
263
|
+
position, or theme_overlay — a WRITE, so the write gateway only.
|
|
264
|
+
`dry_run` defaults to TRUE, so this call previews the diff without
|
|
265
|
+
touching anything:
|
|
266
|
+
`yootheme_builder_advanced({ tool: "yootheme_builder_modules_update", arguments: { id: "custom_html-2", title: "New Title" } })`
|
|
267
|
+
Repeat with `dry_run: false` to persist. See "Now change what you found —
|
|
268
|
+
`modules_update`" above for the platform-specific field split.
|
|
269
|
+
And to CREATE a new widget/module — one of exactly five known types
|
|
270
|
+
(WordPress custom_html/text/block; Joomla mod_custom/mod_menu) — a WRITE,
|
|
271
|
+
so the write gateway only. `placement` is REQUIRED (`position` /
|
|
272
|
+
`module_element` / `position_element` — how the module reaches a page); a
|
|
273
|
+
call without it creates NOTHING and returns BOTH halves of the answer:
|
|
274
|
+
`ways`, every way for this platform with its reach and a summary, and
|
|
275
|
+
`notes`, what `modules_create` can build and when you need no module at all.
|
|
276
|
+
Which ways additionally need a `position` is a PLATFORM fact: all three on
|
|
277
|
+
WordPress, two on Joomla. It is never defaulted on either. `dry_run` defaults to TRUE, so once `placement` is chosen this call
|
|
278
|
+
previews (type, position, fields) without creating anything:
|
|
279
|
+
`yootheme_builder_advanced({ tool: "yootheme_builder_modules_create", arguments: { type: "custom_html", title: "New Widget", content: "<p>Hi</p>", placement: "module_element", position: "builder-1" } })`
|
|
280
|
+
Repeat with `dry_run: false` to persist — the response's `row` is the full
|
|
281
|
+
`local_content_read(kind:'modules')` shape and `next_step` is the exact
|
|
282
|
+
follow-up call **for the placement you chose**, which is an `element_add`
|
|
283
|
+
for only two of the three: `module_element` and `position_element` get one,
|
|
284
|
+
`position` gets `modules_update{assignment}` on Joomla and a read-only
|
|
285
|
+
`local_content_read{kind:"modules", include_usage:true}` on WordPress — the
|
|
286
|
+
widget is already live site wide (no write to narrow it with), but add
|
|
287
|
+
`verify_on_urls` to that same call to PROVE guest-visible rendering rather
|
|
288
|
+
than trust `active`/`builder_visible` alone. See "Create → wire in (the
|
|
289
|
+
two-step recipe)" below.
|
|
185
290
|
|
|
186
291
|
The core write/refactor + render-verify tools the customer HANDOFF-4/5 ask for
|
|
187
292
|
are now **L1 — call them directly by name**, not through a gateway:
|
|
188
293
|
- To render-check a saved page over the authenticated channel (F5+N5 — does
|
|
189
294
|
it render, with how many items, or is it silently blank? where an external
|
|
190
295
|
cURL 401s behind staging Basic-Auth), call the now-L1
|
|
191
|
-
`yootheme_builder_render_preview({
|
|
296
|
+
`yootheme_builder_render_preview({ layout_id })` directly — or address it by
|
|
192
297
|
a public URL with `yootheme_builder_render_preview({ url })` (the URL is
|
|
193
298
|
resolved to its template for you, exactly like `page_get_layout`), so if you
|
|
194
299
|
only hold a resolved category/archive URL you can render-check it in ONE call
|
|
195
|
-
without first converting it to a
|
|
300
|
+
without first converting it to a layout_id. It takes no post/term target: it
|
|
196
301
|
renders YOOtheme's implicit fallback context, so a source-bound template comes
|
|
197
302
|
back non-empty. Treat a non-empty render as the success signal, not "blank
|
|
198
|
-
despite a binding". `
|
|
199
|
-
(a static grid/list nested in a repeated
|
|
303
|
+
despite a binding". `rendered_item_wrapper_count` counts the TOP-LEVEL repeat-item
|
|
304
|
+
wrappers the render emitted, BOUND OR NOT (a static grid/list nested in a repeated
|
|
305
|
+
card does not inflate it); `source_item_counts` is the per-source ROW count,
|
|
306
|
+
and `unresolved_sources` names any bound source the registry does not recognise
|
|
307
|
+
(the render still succeeds, only that source contributes nothing). Pass
|
|
200
308
|
`expect: "<substring>"` to confirm a specific edit reached the HTML in one call
|
|
201
309
|
(the response adds `matched` + `snippet`).
|
|
202
310
|
- To surgically SET a precomposed `source` + the native item-level
|
|
@@ -204,30 +312,52 @@ The server exposes:
|
|
|
204
312
|
leaving the rest of the layout byte-unchanged (F2 — the one-call source edit
|
|
205
313
|
bind_source can't do because it cannot write `source_extended`), call the
|
|
206
314
|
now-L1
|
|
207
|
-
`yootheme_builder_element_set_source({
|
|
208
|
-
- To template-wide
|
|
209
|
-
one ETag-locked write — after
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
315
|
+
`yootheme_builder_element_set_source({ layout_id, element_path, source: { query: { name: "categoryFaqs.taxonomyCategoryFaq", field: { name: "faq" } } }, source_extended: { props: { title: { query: { name: "#node" }, name: "title" } } }, etag })` directly.
|
|
316
|
+
- To template-wide REPAIR binding REFERENCES to a source/type/field name
|
|
317
|
+
across EVERY binding in one template, in one ETag-locked write — after
|
|
318
|
+
that name changed OUTSIDE the builder (a taxonomy/CPT rename such as
|
|
319
|
+
faq→help, a re-published flow), preserving each binding's arguments +
|
|
320
|
+
props + field_mappings — this does NOT rename the source itself,
|
|
321
|
+
`sources_list` stays unchanged; call the now-L1
|
|
322
|
+
`yootheme_builder_repair_source_references({ layout_id, from: "categoryFaqs", to: "categoryHelps", source_map: { taxonomyCategoryFaq: "taxonomyCategoryHelp", faq: "help" }, field_map: { faq_title: "help_title" }, etag })` directly.
|
|
323
|
+
`to` (and each `source_map` value landing at the top-level source
|
|
324
|
+
position) must be a currently registered source name — an unregistered
|
|
325
|
+
target is refused with the nearest matches, unless you pass
|
|
326
|
+
`allow_unregistered:true` for the case where the target only exists
|
|
327
|
+
after a flow republish.
|
|
328
|
+
That write is per-template, so find WHICH templates carry the binding FIRST —
|
|
329
|
+
`yootheme_builder_layout_query({ source_name: "categoryFaqs" })` with **no**
|
|
330
|
+
`layout_id` sweeps the whole site and answers per-source counts plus the
|
|
331
|
+
templates each one lives on (add `max_paths` for the individual element
|
|
332
|
+
paths). One call instead of one per template.
|
|
333
|
+
The sweep recognises a binding by the SAME rule `has_binding` uses per
|
|
334
|
+
template, so an unfiltered call splits its `total_bindings` three ways and
|
|
335
|
+
only the first is a source: `sources` (real source names — the ones
|
|
336
|
+
`sources_list` also knows), `inherited_context_bindings` (a `#`-prefixed
|
|
337
|
+
YOOtheme Pro self-reference such as `#parent`, i.e. a binding that INHERITS
|
|
338
|
+
the row context of an iterating ancestor — on real sites this is often the
|
|
339
|
+
majority of the count and it is NOT a source), and `unnamed_bindings`
|
|
340
|
+
(bound via field mappings or a query-field selector only, so there is no
|
|
341
|
+
name for `repair_source_references` to match). Repair covers the first
|
|
342
|
+
bucket; the third names the templates you still have to open by hand.
|
|
213
343
|
- To reassign which archive/single a THEME template applies to — set its
|
|
214
344
|
`type` condition (the value pages_list shows) in one ETag-locked write, the
|
|
215
345
|
step a taxonomy rename leaves orphaned (N3 — re-type
|
|
216
346
|
`taxonomy-category_faq` → `taxonomy-category_help`), call the now-L1
|
|
217
|
-
`yootheme_builder_template_set_type({
|
|
347
|
+
`yootheme_builder_template_set_type({ layout_id, type: "taxonomy-category_help", etag })` directly.
|
|
218
348
|
|
|
219
349
|
To introspect a source's query methods + relations + the current-term
|
|
220
350
|
archive-context convention (U4-L1 — turns a ~25-call YOOtheme code-dive into
|
|
221
351
|
one call before binding on an archive/category template), call the now-L1
|
|
222
352
|
`yootheme_builder_source_get_schema({ name: "categoryFaqs" })` directly.
|
|
223
353
|
- **2 gateway tools**: `yootheme_builder_advanced` (write-capable; can route
|
|
224
|
-
into delete
|
|
225
|
-
(read-only; routes ONLY the read-only advanced subset —
|
|
354
|
+
into delete, so hosts gate it) and `yootheme_builder_advanced_read`
|
|
355
|
+
(read-only; routes ONLY the read-only advanced subset —
|
|
226
356
|
inspect_sublayout, layout_query, local_content_types, local_content_read,
|
|
227
357
|
pages_audit, heading_outline, navigation —
|
|
228
358
|
and is read-only-annotated so hosts auto-allow it). Prefer the read gateway
|
|
229
359
|
for pure inspection:
|
|
230
|
-
`yootheme_builder_advanced_read({ tool: "
|
|
360
|
+
`yootheme_builder_advanced_read({ tool: "yootheme_builder_pages_audit", arguments: { site_id } })`.
|
|
231
361
|
Discover the site's NATIVE content the same way — the landscape then the records:
|
|
232
362
|
`yootheme_builder_advanced_read({ tool: "yootheme_builder_local_content_types" })`
|
|
233
363
|
then `yootheme_builder_advanced_read({ tool: "yootheme_builder_local_content_read", arguments: { kind: "terms", taxonomy: "category" } })`.
|
|
@@ -235,9 +365,9 @@ The server exposes:
|
|
|
235
365
|
`yootheme_builder_advanced({ tool: "yootheme_builder_local_content_types" })` and
|
|
236
366
|
`yootheme_builder_advanced({ tool: "yootheme_builder_local_content_read", arguments: { kind: "entities", type: "post" } })`.)
|
|
237
367
|
|
|
238
|
-
`tools/list` therefore advertises
|
|
239
|
-
essentials + 2 L3 direct + 2 gateways. The total callable surface is
|
|
240
|
-
(
|
|
368
|
+
`tools/list` therefore advertises 49 names (45 + 2 + 2). That's 45 L1
|
|
369
|
+
essentials + 2 L3 direct + 2 gateways. The total callable surface is 62
|
|
370
|
+
(49 advertised + 13 advanced reachable through the gateways). If the AI
|
|
241
371
|
client reports "tool not found", you are almost certainly calling an
|
|
242
372
|
advanced tool by its raw name. Wrap it in
|
|
243
373
|
`yootheme_builder_advanced({ tool, arguments })` (or the read gateway for
|
|
@@ -248,10 +378,14 @@ read-only tools) instead.
|
|
|
248
378
|
The server surfaces the connected site's URLs in two places so you never have to
|
|
249
379
|
guess where the agent is pointing:
|
|
250
380
|
|
|
251
|
-
- **`yootheme_builder_health` (Bearer-authenticated)
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
381
|
+
- **`yootheme_builder_health` (Bearer-authenticated)** returns `site_url` for
|
|
382
|
+
the connected install, and on WordPress `home_url` as well, because WordPress
|
|
383
|
+
distinguishes the two; Joomla has a single site URL and sends only
|
|
384
|
+
`site_url`. Call it when the user asks "which site are you on?" or before
|
|
385
|
+
deep-linking the user back into wp-admin / Joomla administrator.
|
|
386
|
+
`yootheme_builder_diagnose` carries `site_url` too, so read it there when you
|
|
387
|
+
are already triaging an auth failure — but **only** `site_url`. `home_url` is
|
|
388
|
+
on health alone.
|
|
255
389
|
- **`yootheme_builder_pages_list`** returns per-template `frontend_url`,
|
|
256
390
|
`frontend_url_template`, and `frontend_url_description` columns when the
|
|
257
391
|
host plugin can resolve them. Use these when the user asks for a verification
|
|
@@ -276,9 +410,37 @@ Do not hand-write a Joomla endpoint list from memory: the Joomla paths carry a
|
|
|
276
410
|
`yt-builder-mcp` segment (`/v1/yt-builder-mcp/pages/audit`, not `/v1/pages/audit`)
|
|
277
411
|
and the router rejects the short form.
|
|
278
412
|
|
|
413
|
+
If the field is **missing** from a health answer, the host could not read its own
|
|
414
|
+
route table for this request — read that as "not knowable here", never as "this
|
|
415
|
+
install serves nothing". It is omitted rather than sent as an empty list for
|
|
416
|
+
exactly that reason.
|
|
417
|
+
|
|
279
418
|
Treat `frontend_url: null` as "host plugin could not resolve a public URL for
|
|
280
419
|
this template". Surface that honestly rather than fabricating one.
|
|
281
420
|
|
|
421
|
+
### What a write clears, and what it does not
|
|
422
|
+
|
|
423
|
+
`page_save` and `page_publish` say they flush caches. They flush **the caches
|
|
424
|
+
this plugin owns**, and that is a narrower set than "the caches on this site":
|
|
425
|
+
|
|
426
|
+
| layer | who clears it after a write |
|
|
427
|
+
|---|---|
|
|
428
|
+
| YOOtheme's own render / schema cache | this plugin |
|
|
429
|
+
| the option entries this plugin writes | this plugin |
|
|
430
|
+
| the CMS object-cache entry for the page itself | **the CMS core**, on its own post/article save — not this plugin |
|
|
431
|
+
| a full-page cache or CDN (WP Rocket, LiteSpeed, Cloudflare, a reverse proxy) | **nobody, from here** |
|
|
432
|
+
|
|
433
|
+
The last row is the one that reaches visitors. **No full-page-cache or CDN
|
|
434
|
+
plugin was measured**; what was measured is that the CMS core hooks such
|
|
435
|
+
plugins register on do fire during a write. That is an indication, not a
|
|
436
|
+
guarantee — and on Joomla there is no equivalent link at all.
|
|
437
|
+
|
|
438
|
+
So after a publish that must be visible immediately: purge the page cache / CDN
|
|
439
|
+
yourself, then prove it with `yootheme_builder_fetch_public_url`, which fetches
|
|
440
|
+
the page as an anonymous visitor and reports whether a cache served the answer.
|
|
441
|
+
`render_preview` cannot settle this — it renders the stored layout server-side
|
|
442
|
+
and never touches the visitor's path.
|
|
443
|
+
|
|
282
444
|
## Resolve a URL back to the template that renders it
|
|
283
445
|
|
|
284
446
|
The two sections above go template → URL (forward). To go the OTHER way — the
|
|
@@ -287,18 +449,35 @@ complementary tools.
|
|
|
287
449
|
|
|
288
450
|
**`yootheme_builder_pages_resolve({ url })` — the direct reverse lookup.** Paste
|
|
289
451
|
any public frontend URL and it returns
|
|
290
|
-
`{
|
|
452
|
+
`{ layout_id, post_id, type, frontend_url, context, resolved_via }` (or a
|
|
291
453
|
`url_not_resolvable` when it genuinely maps to no template — it degrades to an
|
|
292
454
|
honest null rather than guessing a wrong one). It handles **SEF / pretty
|
|
293
455
|
permalinks on BOTH WordPress and Joomla** (not just raw `?option=…&id=…` query
|
|
294
|
-
URLs), including deep sub-category paths like `/matches/first-men-s-team
|
|
456
|
+
URLs), including deep sub-category paths like `/matches/first-men-s-team` and
|
|
457
|
+
the deep ARTICLE paths a Joomla site prints for an article routed below a
|
|
458
|
+
category menu item (`/company/blog/9-news/36-my-article`) — the id and the alias
|
|
459
|
+
in that last segment are both checked against the article, so an address whose
|
|
460
|
+
alias belongs to something else is refused rather than guessed. For a
|
|
295
461
|
**category or term URL it returns the SPECIFICALLY-ASSIGNED template** — the one
|
|
296
462
|
whose YOOtheme `query.catid` (Joomla) / `query.terms` (WordPress) binds THAT
|
|
297
463
|
category/term, honoring **include-child-categories** (a template assigned to a
|
|
298
464
|
parent category with child-inclusion covers its subcategories; the nearest
|
|
299
465
|
specific assignment wins). Only when nothing is specifically assigned does it
|
|
300
466
|
fall back to the governing generic theme template. Then feed the returned
|
|
301
|
-
`
|
|
467
|
+
`layout_id` straight into `page_get_layout` / `element_list` / the write tools.
|
|
468
|
+
|
|
469
|
+
**The site's own front page resolves too, in every spelling** — bare host,
|
|
470
|
+
trailing slash, `…/index.php`, and the subdirectory forms of an install served
|
|
471
|
+
from one (`https://example.com/joomla/index.php`). It resolves to whatever
|
|
472
|
+
renders it, which on Joomla is usually the single article the default menu item
|
|
473
|
+
points at. `pages_list` hands out one of those spellings as the front page's
|
|
474
|
+
`frontend_url`, so an address this server issues is one it accepts.
|
|
475
|
+
|
|
476
|
+
A front page that resolves to NOTHING is answered by name, not as
|
|
477
|
+
`url_not_resolvable`: the code is **`url_resolves_but_no_layout`** and the
|
|
478
|
+
message says which page was identified and why nothing renders it. Re-spelling
|
|
479
|
+
the URL will not help there — give that page a layout, or address the governing
|
|
480
|
+
template by `layout_id`.
|
|
302
481
|
|
|
303
482
|
**`serves_context_kind` + `serves_context_ids` on `pages_list` rows — resolve it
|
|
304
483
|
yourself, zero extra calls.** Every `pages_list` row also carries these two Layer-C
|
|
@@ -351,7 +530,7 @@ this page". Supply **exactly ONE of** these five keys:
|
|
|
351
530
|
|
|
352
531
|
| Key | Value | Example |
|
|
353
532
|
|-----|-------|---------|
|
|
354
|
-
| `template_id` | An exact
|
|
533
|
+
| `template_id` | An exact layout id | `{ template_id: "page:4816" }` |
|
|
355
534
|
| `post_id` | The numeric WP post / Joomla article id | `{ post_id: 4816 }` |
|
|
356
535
|
| `url` | A public frontend URL (same resolver as `pages_resolve({ url })`) | `{ url: "https://example.com/schedule-demo/" }` |
|
|
357
536
|
| `special` | A semantic page ROLE (vocabulary below) | `{ special: "home" }` |
|
|
@@ -426,15 +605,18 @@ never a transport failure — report it verbatim rather than retrying blindly.
|
|
|
426
605
|
| `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
606
|
| `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`. |
|
|
428
607
|
| `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.** |
|
|
429
|
-
| `
|
|
608
|
+
| `insufficient_capability` | 403 | The object EXISTS and this key may not read it — a permission answer, not a missing page. The message names the object (`page:5040` / `article:42`). **These tools answer this way**, the ones that read ONE layout: `page_get_layout`, `layout_query` (single template), `element_list`, `element_get`, `element_get_binding`, `inspect_multi_items_binding`, `inspect_sublayout`, `render_preview`, `template_summary`, `heading_outline`. **The others do NOT**, on purpose: `pages_list`, `pages_resolve`, `pages_audit`, `get_etag` and a site-wide `layout_query` sweep report a forbidden object as absent, because a half-sharpened sweep is more misleading than an honestly lenient one. **Never read their silence about an object as proof that it does not exist.** | Do not repeat the call: it gives the same answer while the key stays the same. Ask an administrator to open the object to this key, or use a key whose subject can read it. See [Scopes](#scopes-bearer-key-permissions). |
|
|
609
|
+
| `template_not_found` | 404 | The `layout_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. |
|
|
430
610
|
| `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. |
|
|
431
|
-
| `url_no_match` | 404 | The URL is well-formed but
|
|
611
|
+
| `url_no_match` | 404 | The URL is well-formed but this server could not map it to any page at all — neither a per-page layout nor an archive/term/author theme template matched. Note this is NOT the "page exists, has no layout" case: that one has its own code in the next row. | Check the address is this site's and names a public, singular page/article. A LIST view is usually reached with `target: { special: … }` instead. Failing both, browse `pages_list`. |
|
|
612
|
+
| `url_resolves_but_no_layout` | 404 | The URL DID resolve to a page — WordPress mapped it to a post, this server read the Joomla article the address names, or the address is this site's front page — but that page carries no YOOtheme layout of its own; something else renders it. The message names the page. | Nothing here is fixed by re-spelling the address. Give that page a layout in the builder, or find the governing theme template in `pages_list` (its `serves_context_kind` column says which context each one serves) and address it by `layout_id`. |
|
|
432
613
|
| `title_no_match` | 404 | No page NAME contains that substring. | Try a shorter substring, or list with `pages_list`. |
|
|
433
614
|
| `special_not_resolvable` | 404 | The token is in the vocabulary and resolvable in principle, but matches no template on THIS site. | Report which role was requested; do not silently fall back to another page. |
|
|
434
615
|
| `title_ambiguous` | 409 | The substring matches two or more pages. | A `candidates` list (max 10) rides along — show it and let the user choose. |
|
|
435
616
|
| `blog_ambiguous` | 409 | Joomla only: several distinct blog pages. | Same `candidates` shape; show it (see the Joomla caveat above). |
|
|
436
617
|
| `special_not_supported` | 422 | The token is recognised but this platform has no such page role (today: `date`, on both). | Say so plainly instead of guessing an analogue. |
|
|
437
618
|
| `target_not_scannable` | 422 | **The target resolved to a real page, but this site does not enumerate it as an auditable page** — a post type or status the page enumerator does not cover, or a trashed item. | **This is NOT a clean result.** An audit that answered 200 with zero findings would read as "your page is fine"; this code exists precisely so it cannot. Audit it via `template_summary`, or pick a page `pages_list` returns. |
|
|
619
|
+
| `render_failed` | 422 | `yootheme_builder_heading_outline` in `mode:"rendered"` — the target resolved to a real template, but rendering it genuinely failed (a broken binding or other structural problem — NOT a Joomla `module`/`module_position` gap, which the server tolerates transparently; see `render_fallback`/`render_fallback_used` below). | The page is real but cannot currently be rendered. Read the message for the underlying cause; fix the layout, or fall back to `mode:"builder"` (structural inspection over the layout tree, no render needed). |
|
|
438
620
|
|
|
439
621
|
`server_too_old` is the one code the SERVER never sends — see
|
|
440
622
|
[Audit ONE page instead of the whole site](#audit-one-page-instead-of-the-whole-site).
|
|
@@ -460,11 +642,35 @@ Do not loop on auth errors.
|
|
|
460
642
|
> capabilities like `manage_options` gate the admin settings page only; the
|
|
461
643
|
> REST API surface is Bearer-gated.
|
|
462
644
|
|
|
463
|
-
> **Joomla L2 articles surface.** The Joomla plugin ships
|
|
464
|
-
> REST endpoints
|
|
465
|
-
>
|
|
466
|
-
>
|
|
467
|
-
>
|
|
645
|
+
> **Joomla L2 articles surface.** The Joomla plugin ships READ-ONLY
|
|
646
|
+
> `/v1/articles*` REST endpoints (`GET /articles`, `GET /articles/<id>/page-layout`,
|
|
647
|
+
> `GET /articles/<id>/elements/<path>`) for inspecting an article's Builder
|
|
648
|
+
> state, but they are NOT exposed via MCP tools in v1.x. To read that
|
|
649
|
+
> surface, call the REST endpoints directly with the Bearer key. To WRITE
|
|
650
|
+
> an article's layout, address it the same way as any other page — target
|
|
651
|
+
> `article:<id>` with the normal page-editing tools (`page_save`,
|
|
652
|
+
> `element_add`, `element_update_settings`, …); there is no separate write
|
|
653
|
+
> route for articles.
|
|
654
|
+
|
|
655
|
+
### The write budget is attached to the KEY, not to you
|
|
656
|
+
|
|
657
|
+
Writes are rate-limited **per bearer key**. Several clients — several agents,
|
|
658
|
+
several editors, a script running alongside you — sharing one key share **one**
|
|
659
|
+
budget, so a burst that is well within the cap for you alone can still be
|
|
660
|
+
refused. This is reproducible: six agents on one key hit it.
|
|
661
|
+
|
|
662
|
+
`yootheme_builder_health` publishes the cap as `write_rate_limit`
|
|
663
|
+
(`{limit, window_seconds, scope: "kid", applies_to: "writes"}`), read from the
|
|
664
|
+
limiter the site actually enforces. **Read it before a burst instead of
|
|
665
|
+
discovering it as a refusal.** Two bounds worth knowing:
|
|
666
|
+
|
|
667
|
+
- `limit` is the CAP, never the headroom you have left — no counter is read.
|
|
668
|
+
- Reads are not capped, so there is no reason to slow a read loop down for it.
|
|
669
|
+
|
|
670
|
+
Exceeding it answers HTTP 429 with `retry_after_seconds`, and **the write did
|
|
671
|
+
not happen** — wait out the window and retry rather than treating it as a
|
|
672
|
+
failed write to investigate. Anything throttling in FRONT of the site (a CDN,
|
|
673
|
+
a WAF, the host) is invisible here and answers with its own status.
|
|
468
674
|
|
|
469
675
|
---
|
|
470
676
|
|
|
@@ -507,6 +713,16 @@ To verify a specific site before doing work on it, call
|
|
|
507
713
|
and `/etag` in parallel and returns `plugin_reachable` + `bearer_valid`
|
|
508
714
|
without mutating anything.
|
|
509
715
|
|
|
716
|
+
For the whole fleet at once — "which of my sites are up, and how much is on
|
|
717
|
+
them" — call `yootheme_builder_sites_overview()`. It answers in ONE call what
|
|
718
|
+
`sites_list` plus one `sites_test` and one `pages_list` per site answered
|
|
719
|
+
before, probing every site in parallel: one row per site with `reachable`,
|
|
720
|
+
`bearer_valid` and the page counts. A site that is down becomes a row carrying
|
|
721
|
+
`error`, never a failed call — which is the point: you want to see WHICH site
|
|
722
|
+
is broken, not lose the answer for the others. Pass `include_pages: false` to
|
|
723
|
+
skip the per-site page read. The content/theme-template split on a row is
|
|
724
|
+
ABSENT on a host that does not report it; it is never reported as zero.
|
|
725
|
+
|
|
510
726
|
### Default-site mechanics
|
|
511
727
|
|
|
512
728
|
The default site is set automatically on first add:
|
|
@@ -527,7 +743,9 @@ You can store the Bearer key two ways per site:
|
|
|
527
743
|
- **1Password reference**: `bearer_ref: "op://Vault/Item/credential"` in
|
|
528
744
|
`sites.json`. The plaintext token never touches disk. The MCP server shells
|
|
529
745
|
out to the `op` CLI at first use per site to fetch the live token, then
|
|
530
|
-
caches it in memory for that process lifetime.
|
|
746
|
+
caches it in memory for that process lifetime. `op` itself does not cache
|
|
747
|
+
— restart the MCP client after rotating a key so the server re-resolves
|
|
748
|
+
the new value.
|
|
531
749
|
|
|
532
750
|
**Recommendation for production sites**: use `bearer_ref`. You get rotation
|
|
533
751
|
without editing `sites.json`, and your 1Password audit log captures every
|
|
@@ -535,6 +753,24 @@ fetch. The `op` CLI must be installed and signed in on the machine running
|
|
|
535
753
|
the MCP server. If `op` is missing, the resolver returns a structured error
|
|
536
754
|
(`op CLI not found in PATH`) pointing at the install docs.
|
|
537
755
|
|
|
756
|
+
### Prompt friction depends on how the client was launched
|
|
757
|
+
|
|
758
|
+
Per 1Password's own documentation, an app-integration session binds to a
|
|
759
|
+
**TTY** (macOS/Linux) or PID + start time (Windows) — a GUI-launched process
|
|
760
|
+
(Claude Desktop, Cursor) has neither, so the confirmation-free window a
|
|
761
|
+
terminal session gets does not carry over there. Three environment classes,
|
|
762
|
+
worst-first:
|
|
763
|
+
|
|
764
|
+
| Class | Confirmation dialogs | Who |
|
|
765
|
+
|---|---|---|
|
|
766
|
+
| Service account (`OP_SERVICE_ACCOUNT_TOKEN`) | none | agencies, servers, CI |
|
|
767
|
+
| Terminal + app integration | one per window, valid ~10 min | developers running the CLI |
|
|
768
|
+
| **GUI-launched + app integration** | **per call** | **Claude Desktop, Cursor — the customer default** |
|
|
769
|
+
|
|
770
|
+
**For multi-site customers a service account is the frictionless path** —
|
|
771
|
+
worth setting up explicitly rather than discovering the per-call prompts one
|
|
772
|
+
site at a time.
|
|
773
|
+
|
|
538
774
|
### `sites.json` location
|
|
539
775
|
|
|
540
776
|
The registry lives at `~/.config/yt-builder-mcp/sites.json` (XDG-conform).
|
|
@@ -607,9 +843,9 @@ one conversation.
|
|
|
607
843
|
2. For each site, the agent runs the same sequence with `site_id` set:
|
|
608
844
|
- `yootheme_builder_pages_list({ site_id: "wp-acme", fields: ["id", "label"] })`
|
|
609
845
|
- `yootheme_builder_get_etag({ site_id: "wp-acme" })`
|
|
610
|
-
- `yootheme_builder_element_update_settings({ site_id: "wp-acme",
|
|
611
|
-
- `yootheme_builder_page_save({ site_id: "wp-acme",
|
|
612
|
-
- `yootheme_builder_page_publish({ site_id: "wp-acme",
|
|
846
|
+
- `yootheme_builder_element_update_settings({ site_id: "wp-acme", layout_id: "home", element_path: "/children/0/children/0/children/0/children/0", props: { content: "New headline" }, merge: true, etag: "<etag>" })`
|
|
847
|
+
- `yootheme_builder_page_save({ site_id: "wp-acme", layout_id: "home", etag: "<fresh>" })`
|
|
848
|
+
- `yootheme_builder_page_publish({ site_id: "wp-acme", layout_id: "home", etag: "<fresh>" })`
|
|
613
849
|
3. The customer sees a stream of replies, each prefixed with the matching
|
|
614
850
|
`[label @ host]`, so it is obvious which site is at which step.
|
|
615
851
|
|
|
@@ -623,7 +859,7 @@ for a focused diagnosis without touching the others.
|
|
|
623
859
|
## Pages vs theme templates (know what you are editing)
|
|
624
860
|
|
|
625
861
|
`pages_list` returns TWO different kinds of editable layout, and every tool
|
|
626
|
-
takes a `
|
|
862
|
+
takes a `layout_id` that addresses either. Know which one you have before
|
|
627
863
|
you write — they have very different blast radius.
|
|
628
864
|
|
|
629
865
|
| Kind | `id` shape | `type` value(s) | What it is |
|
|
@@ -634,7 +870,7 @@ you write — they have very different blast radius.
|
|
|
634
870
|
**The safety rule: editing a theme template changes the layout for ALL
|
|
635
871
|
matching content, not one page.** If the user says "restyle the blog post
|
|
636
872
|
template", `pages_list` shows it as e.g. `{ id: "I99YS8Ii", label: "Post",
|
|
637
|
-
type: "single-post" }` — editing that `
|
|
873
|
+
type: "single-post" }` — editing that `layout_id` restyles EVERY blog post
|
|
638
874
|
on the site at once. That is often exactly what's wanted (that's what theme
|
|
639
875
|
templates are for), but confirm the intent — do not edit a `single-post` /
|
|
640
876
|
`taxonomy-*` / `archive-*` template when the user meant one specific page.
|
|
@@ -646,6 +882,33 @@ templates are for), but confirm the intent — do not edit a `single-post` /
|
|
|
646
882
|
- `pages_create` creates a **page/article** (an individual layout). Theme
|
|
647
883
|
templates are defined in the theme itself and are not created via this MCP;
|
|
648
884
|
you edit the existing ones `pages_list` reports.
|
|
885
|
+
- `yootheme_builder_pages_delete` removes one — an L1 tool, call it directly
|
|
886
|
+
(no gateway). `confirm: true` is REQUIRED on every call (a bare call is
|
|
887
|
+
refused, `pages.confirm_required`); there is no preview-by-omitting-confirm
|
|
888
|
+
the way `modules_delete` has. Preview instead with `dry_run: true`
|
|
889
|
+
(reports `element_count`, `has_dynamic_binding`, and whether the real call
|
|
890
|
+
would be refused for lacking an `intent`; nothing is written and no
|
|
891
|
+
request is sent). The real call additionally needs `intent`: call
|
|
892
|
+
`confirm: true` without it once and the server refuses with a 400 naming
|
|
893
|
+
which intents are possible for THIS page's/article's CURRENT status, each
|
|
894
|
+
with its own `effect`/`reversible`/`native_name` — the same
|
|
895
|
+
`intent_not_chosen` shape `modules_delete` uses. Unlike modules,
|
|
896
|
+
`intent: "trash"` is REVERSIBLE on BOTH platforms: it sets the WordPress
|
|
897
|
+
`post_status` to `trash` or the Joomla article `state` to `-2`;
|
|
898
|
+
`intent: "permanent"` removes the row and its layout for good on either
|
|
899
|
+
platform, and neither CMS can recreate them. There is a way back from the
|
|
900
|
+
trash step, but it is NOT this tool and NOT `pages_set_status` (see "Finding
|
|
901
|
+
and un-trashing a page" below).
|
|
902
|
+
`yootheme_builder_pages_delete({ layout_id: 42, confirm: true, intent: "trash" })`.
|
|
903
|
+
A page/article ALREADY in the trash short-circuits BEFORE the intent gate:
|
|
904
|
+
passing `intent: "permanent"` still deletes it for good (the only intent
|
|
905
|
+
offered there); passing anything else — no `intent`, `intent: "trash"`, or
|
|
906
|
+
an unrecognised one — instead answers `200 {deleted:false,
|
|
907
|
+
already_trashed:true}`, a MEASUREMENT, not an error, that names
|
|
908
|
+
`intent:"permanent"` or the way back (below) as the two ways forward. The
|
|
909
|
+
EXECUTED trash response (`confirm:true, intent:"trash"`) carries the same
|
|
910
|
+
pointer in its own `note` field — the caller who just trashed something and
|
|
911
|
+
regrets it never has to re-read the preview to find it.
|
|
649
912
|
|
|
650
913
|
**Reaching a theme template by ROLE instead of by id.** You rarely know a theme
|
|
651
914
|
template's opaque id (`I99YS8Ii`). The `special` tokens of the shared `target`
|
|
@@ -666,6 +929,40 @@ response's `resolved_target` tells you which template that actually was. The
|
|
|
666
929
|
same safety rule still applies: a finding on a theme template is a finding on
|
|
667
930
|
EVERY page that template renders.
|
|
668
931
|
|
|
932
|
+
**Finding and un-trashing a page you cannot see in `pages_list`.** A trashed
|
|
933
|
+
page/article never appears there, and by default `local_content_read`
|
|
934
|
+
answers the same `total: 0` for "genuinely nothing" and "it's in the trash" —
|
|
935
|
+
pass `kind: "entities", status: "trash"` for the EXCLUSIVE trashed view (never
|
|
936
|
+
mixed into the default list), with the identifier in `id`. Even without
|
|
937
|
+
asking: a plain `local_content_read({kind:"entities"})` call carries
|
|
938
|
+
`trashed_total` in the response whenever a match exists in the trash, so the
|
|
939
|
+
gap surfaces unasked. Then `yootheme_builder_pages_restore({ layout_id: <id> })`
|
|
940
|
+
brings it back — the ONE correct way, do NOT try `pages_set_status`, which
|
|
941
|
+
refuses a trashed page with a 409 pointing at this tool. **The two platforms
|
|
942
|
+
genuinely diverge here, and the tool says so rather than hiding it**:
|
|
943
|
+
WordPress' `wp_untrash_post()` reinstates BOTH the exact pre-trash status AND
|
|
944
|
+
the pre-trash URL slug (a hand-rolled status write would leave the trash's
|
|
945
|
+
`__trashed` slug suffix in place, silently changing the address) — Joomla
|
|
946
|
+
stores no pre-trash status at all, so a restored article can only become
|
|
947
|
+
`unpublished`; call `pages_set_status` with `status: "published"` afterwards
|
|
948
|
+
to make it visible again.
|
|
949
|
+
|
|
950
|
+
**A trashed Joomla MODULE has no restore tool of its own, and the way out is
|
|
951
|
+
NOT the equivalent of the above — it lands on the opposite side.** Measured:
|
|
952
|
+
`yootheme_builder_modules_update({active:true})` writes `published = 1`, so a
|
|
953
|
+
module comes out of the trash **PUBLISHED** whatever it was before. Joomla
|
|
954
|
+
keeps no pre-trash state for modules either (`#__modules` has one state column
|
|
955
|
+
and the trash writes `-2` over it), but here the single reachable value is the
|
|
956
|
+
VISIBLE one — an unpublished module you trash and bring back is live on the
|
|
957
|
+
site. Its counterpart `{active:false}` is not a second way out: a trashed
|
|
958
|
+
module already reads `active:false`, so that call writes nothing and leaves it
|
|
959
|
+
in the trash, reporting only `changed: []`. Coming back unpublished is
|
|
960
|
+
therefore two calls in order — `{active:true}` then `{active:false}` — and the
|
|
961
|
+
tool says so in `restore_state_note` on the trash row, in the delete preview's
|
|
962
|
+
notes, and in the `notes` of the restoring call itself.
|
|
963
|
+
`local_content_read({kind:"modules"})` lists a trashed module unasked, marked
|
|
964
|
+
`publish_state:"trashed"`.
|
|
965
|
+
|
|
669
966
|
## Workflow 1: Build a hero section
|
|
670
967
|
|
|
671
968
|
**Goal:** Add a fresh hero section (heading + sub-heading + CTA button)
|
|
@@ -681,34 +978,124 @@ to an existing page.
|
|
|
681
978
|
named a specific page, match on `label` (exact then fuzzy).
|
|
682
979
|
3. `yootheme_builder_get_etag()`: fetch the current top-level
|
|
683
980
|
optimistic-lock ETag. Every write tool requires it via `etag`.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
981
|
+
**Chaining writes: every write answers with `etag` AND `new_etag`, and both
|
|
982
|
+
carry the value read AFTER that write.** They are interchangeable; pass
|
|
983
|
+
either into the next write. You only need `get_etag` again after a 412, or
|
|
984
|
+
when you did not just write.
|
|
985
|
+
4. `yootheme_builder_scaffold_section({ layout_id: "<id>", columns: 1, etag: "<etag>" })`:
|
|
986
|
+
build a VALID `section → row → column` hull at the template root in ONE
|
|
987
|
+
atomic call — the correct way to start a layout block. Only `section` may
|
|
988
|
+
sit at the layout root; a bare section accepts only `row`; a bare row
|
|
989
|
+
accepts only `column`; content/leaf elements (headline, text, button, …)
|
|
990
|
+
are rejected anywhere except inside a `column`. `columns: 1` (the
|
|
991
|
+
default) is a single full-width column — the shape a hero needs.
|
|
992
|
+
Returns `{ section_path: "/children/0/children/N", row_path: "…/children/0",
|
|
993
|
+
column_paths: ["…/children/0/children/0"], etag: "<fresh>" }`. Drop every
|
|
994
|
+
content element below into `column_paths[0]`.
|
|
995
|
+
5. (Optional) `yootheme_builder_element_update_settings({ layout_id, element_path: "<section-path>", props: { style: "primary" }, etag: "<fresh-etag>" })`:
|
|
996
|
+
`scaffold_section` does not take section `props`, so set the background
|
|
997
|
+
as a follow-up call when the hero needs one. `style` (enum: `default` /
|
|
998
|
+
`muted` / `primary` / `secondary`) — there is NO `background` key. Writing
|
|
999
|
+
one is ACCEPTED and stored; the answer names it under `warnings` with
|
|
1000
|
+
`code: "unknown_prop_keys"`, and nothing renders. Always call
|
|
1001
|
+
`element_type_get_schema("section")` first to confirm the real prop keys.
|
|
1002
|
+
6. `yootheme_builder_element_add({ layout_id, parent_path: "<column-path>", element_type: "headline", props: { content: "<h1 text>", title_style: "h1" }, etag })`:
|
|
1003
|
+
add a headline INSIDE the column `scaffold_section` returned
|
|
1004
|
+
(`column_paths[0]`) — never directly in the row or the section. `title_style:
|
|
1005
|
+
"h1"` uses the semantic type scale (the tasteful default). See "Typography
|
|
1006
|
+
& heading styles" below before reaching for a `heading-*` display size.
|
|
1007
|
+
7. `yootheme_builder_element_add({ layout_id, parent_path: "<column-path>", element_type: "text", props: { content: "<sub text>" }, etag })`:
|
|
1008
|
+
add a text element into the SAME column.
|
|
1009
|
+
8. `yootheme_builder_element_add({ layout_id, parent_path: "<column-path>", element_type: "button", etag })`
|
|
701
1010
|
then add a `button_item` CHILD for the actual label/link/style:
|
|
702
|
-
`yootheme_builder_element_add({
|
|
1011
|
+
`yootheme_builder_element_add({ layout_id, parent_path: "<button-path>", element_type: "button_item", props: { content: "<cta>", link: "<url>", button_style: "primary" }, etag })`.
|
|
703
1012
|
The `button` element is a CONTAINER — its label, link and style live on
|
|
704
1013
|
a `button_item` child, NOT on the button's own props. Adding a lone
|
|
705
1014
|
`button` with `{content, link}` renders an empty button. (Same
|
|
706
1015
|
container→item pattern as `grid`→`grid_item`, `list`→`list_item`.)
|
|
707
|
-
9. `yootheme_builder_page_save({
|
|
1016
|
+
9. `yootheme_builder_page_save({ layout_id, etag })`:
|
|
708
1017
|
persist the working copy (visible in YOOtheme Customizer preview).
|
|
709
1018
|
`page_save` is a first-class L1 tool; call it directly.
|
|
710
|
-
|
|
1019
|
+
> **`render_preview` cannot see an element whose rendering depends on CMS
|
|
1020
|
+
> runtime state.** On Joomla a `module` / `module_position` subtree is EXCLUDED
|
|
1021
|
+
> from every render probe, whatever the module's state, and a `newsletter`
|
|
1022
|
+
> element is excluded wherever its provider is unconfigured. The response says
|
|
1023
|
+
> so with `fallback_used:true` + `dropped_count:N`, and `dropped_count_note`
|
|
1024
|
+
> names WHICH types were excluded, why, and that they are still in the saved
|
|
1025
|
+
> layout. So an `expect` aimed at such output can never match: a `matched:false`
|
|
1026
|
+
> there is **not** evidence that the element fails to render, and
|
|
1027
|
+
> `match_conclusive:false` marks exactly that case. A HIT is always conclusive.
|
|
1028
|
+
> To check a MODULE, read its placement with
|
|
1029
|
+
> `local_content_read({kind:"modules", include_usage:true, verify_on_urls:[...]})`.
|
|
1030
|
+
> That read answers for modules only; for any other excluded type it returns
|
|
1031
|
+
> nothing, which is why the response points at it only when a module was
|
|
1032
|
+
> actually excluded.
|
|
1033
|
+
|
|
1034
|
+
10. `yootheme_builder_page_publish({ layout_id, etag })`:
|
|
711
1035
|
make the changes live on the front-end. Also a first-class L1 tool.
|
|
1036
|
+
**`published:true` is a statement about the LAYOUT, not about the page.**
|
|
1037
|
+
A WordPress post left in `draft`, or a Joomla article at `state=0` /
|
|
1038
|
+
trashed, or one whose `publish_up` is still in the future, is not served
|
|
1039
|
+
to a visitor afterwards. Read `cms_visibility.findable_by_visitor` before you
|
|
1040
|
+
tell anyone the page is live; `cms_warning` names the exact fix when it is
|
|
1041
|
+
`false`.
|
|
1042
|
+
**For the ordinary case — build it and make it live — pass the CMS status
|
|
1043
|
+
in the same call**: `page_publish({ layout_id, cms_status: "publish" })` on
|
|
1044
|
+
WordPress, `cms_status: "published"` on Joomla, and on Joomla add
|
|
1045
|
+
`menu_item: true` when the article still needs a SEF route. Same words and
|
|
1046
|
+
same refusals as `pages_set_status`; the other platform's word publishes
|
|
1047
|
+
nothing. The response then carries `cms_status_applied` with the STORED
|
|
1048
|
+
status, and `cms_visibility` reflects the new one. Omit `cms_status` and this
|
|
1049
|
+
call changes nothing about who may see the page — that separation stays, it
|
|
1050
|
+
is simply no longer the only way through.
|
|
1051
|
+
> **On Joomla, "not findable" and "not reachable" are two different things, and
|
|
1052
|
+
> only one of them is a privacy control.** Measured anonymously with curl on
|
|
1053
|
+
> 2026-08-04: the STATE decides the 404 (`state` 1 published and 2 archived both
|
|
1054
|
+
> answer **200**, `state` 0 unpublished and -2 trashed both answer **404**) and
|
|
1055
|
+
> the ACCESS LEVEL decides the 403 (a level a guest does not hold answers **403**
|
|
1056
|
+
> at state 1 and at state 2 alike). A missing menu item decides NEITHER: it
|
|
1057
|
+
> costs the SEF url, the breadcrumb and the navigation path, while the article
|
|
1058
|
+
> keeps answering 200 on its own
|
|
1059
|
+
> `?option=com_content&view=article&id=N` url for anyone. So `findable_by_visitor:
|
|
1060
|
+
> false` on a published article usually means "no visitor will FIND it", not
|
|
1061
|
+
> "no visitor can OPEN it". Two consequences worth saying out loud: **archiving
|
|
1062
|
+
> does not hide anything** (it is a 200), and if an article must not be public,
|
|
1063
|
+
> set its state with `yootheme_builder_pages_set_status` or give it an access
|
|
1064
|
+
> level a guest does not hold. Never leave the menu item off and call that
|
|
1065
|
+
> private.
|
|
1066
|
+
|
|
1067
|
+
**The `cms_visibility` block carries the facts, so you do not have to infer
|
|
1068
|
+
them.** It rides on `page_publish`, `page_generate_dynamic` and `pages_set_status`.
|
|
1069
|
+
Read the field that answers YOUR question:
|
|
1070
|
+
|
|
1071
|
+
| field | question it answers | platform |
|
|
1072
|
+
|---|---|---|
|
|
1073
|
+
| `findable_by_visitor` | will a visitor FIND this via a navigation path? (NOT the same as reaching it: an unrouted article still answers on its direct URL) | both |
|
|
1074
|
+
| `status_permits_direct_url` | does the stored state ALONE serve the item's own direct URL? | both |
|
|
1075
|
+
| `state_and_access_permit_direct_url` | state AND access level together: is it served? | Joomla |
|
|
1076
|
+
| `access_is_guest_visible` | does an anonymous visitor hold this access level? | Joomla, when the probe ran |
|
|
1077
|
+
| `article_access` / `post_status` / `article_state` | the raw stored values | per platform |
|
|
1078
|
+
| `publish_up_in_future` | is it scheduled? | both |
|
|
1079
|
+
| `has_route` | does a menu item route it? (WordPress: always equals published) | both |
|
|
1080
|
+
|
|
1081
|
+
> **One row of that table is NOT in the block, and it is the one people
|
|
1082
|
+
> confuse it with:**
|
|
1083
|
+
>
|
|
1084
|
+
> | field | question it answers | where it rides |
|
|
1085
|
+
> |---|---|---|
|
|
1086
|
+
> | `visibility` | do the STORED FIELDS rule this page out for a visitor? | every `pages_list` row |
|
|
1087
|
+
>
|
|
1088
|
+
> `visibility` reads the CMS status plus, on WordPress, password
|
|
1089
|
+
> protection. It never reads the menu route or the access level, so on
|
|
1090
|
+
> Joomla it answers `undetermined` where `findable_by_visitor` gives a verdict.
|
|
1091
|
+
> That is not a contradiction: the per-page field looked at two levers the
|
|
1092
|
+
> listing never touched, so when they seem to disagree, believe the
|
|
1093
|
+
> per-page one.
|
|
1094
|
+
|
|
1095
|
+
On WordPress the first three necessarily agree: a published page resolves at
|
|
1096
|
+
its permalink and there is no separate route step. On Joomla they come
|
|
1097
|
+
apart, and that divergence is the whole reason the extra fields exist. A
|
|
1098
|
+
`null` in any of them means the input was not readable, never "no".
|
|
712
1099
|
|
|
713
1100
|
> **Building a whole page? Prefer ONE atomic `page_save` over ~30 incremental
|
|
714
1101
|
> writes.** The step-by-step `element_add` sequence above is clearest for
|
|
@@ -717,8 +1104,8 @@ to an existing page.
|
|
|
717
1104
|
> (especially when other agents edit the same site — the builder ETag is a
|
|
718
1105
|
> single shared option). For a full page, assemble the complete
|
|
719
1106
|
> `{type:"layout",children:[...]}` tree in memory and persist it in ONE call:
|
|
720
|
-
> `yootheme_builder_page_save({
|
|
721
|
-
>
|
|
1107
|
+
> `yootheme_builder_page_save({ layout_id, layout: <full-tree> })` (ETag is
|
|
1108
|
+
> required — read it via `yootheme_builder_get_etag` first). `dry_run:true` preflights
|
|
722
1109
|
> it first. A fast, robust pattern: `scaffold_section` one block, read it
|
|
723
1110
|
> back to learn the exact node shape, then build the rest of the tree from
|
|
724
1111
|
> that shape and `page_save` it whole.
|
|
@@ -726,16 +1113,35 @@ to an existing page.
|
|
|
726
1113
|
**Common pitfalls:**
|
|
727
1114
|
|
|
728
1115
|
- **Wrong parameter names.** Every tool uses snake_case. Use
|
|
729
|
-
`
|
|
1116
|
+
`layout_id` (not `pageId`), `parent_path` (not `parentPath`),
|
|
730
1117
|
`element_type` (not `type`), `props` (not `settings`), `etag`
|
|
731
1118
|
(not `ifMatch`). The MCP server rejects unknown keys with a
|
|
732
1119
|
Zod-validation error.
|
|
733
1120
|
- **Forgetting `etag`.** Every write tool needs the latest etag. The
|
|
734
1121
|
shared schema marks it required (min length 1). On `412 Precondition
|
|
735
|
-
Failed
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1122
|
+
Failed`, RE-READ the element or page you were writing before you retry.
|
|
1123
|
+
A fresh etag from `yootheme_builder_get_etag` makes the retry succeed,
|
|
1124
|
+
but the etag is site-wide and its hash does not cover per-page layouts,
|
|
1125
|
+
so a 412 cannot tell you whether the write that bumped it was on YOUR
|
|
1126
|
+
page. Retrying on a fresh etag alone overwrites whatever landed.
|
|
1127
|
+
- **Adding content elements directly to a section or a row.** A section
|
|
1128
|
+
accepts only `row`; a row accepts only `column`; headline/text/button and
|
|
1129
|
+
every other content element are rejected anywhere except inside a
|
|
1130
|
+
`column` — a `422 layout_invalid_hierarchy`. Use `scaffold_section` to
|
|
1131
|
+
build the whole `section → row → column` hull in one call and drop content
|
|
1132
|
+
into the returned `column_paths`; don't hand-build a section + row and add
|
|
1133
|
+
content straight into the row. The server returns a structured error with a
|
|
1134
|
+
human-readable hint when you skip a level.
|
|
1135
|
+
- **Adding a child to a leaf, or hitting an empty element registry.** A leaf
|
|
1136
|
+
type — one that never accepts ANY child, e.g. `text`, `headline`, `button`
|
|
1137
|
+
— refuses every child with `422 layout_leaf_has_children`, a distinct code
|
|
1138
|
+
from the mismatch above. Separately, if YOOtheme's element registry answers
|
|
1139
|
+
empty on the site (usually a transient boot-order state, not a fault in
|
|
1140
|
+
your layout), the parent/child check cannot run at all and the write is
|
|
1141
|
+
refused with `422 registry_unavailable` rather than guessed through —
|
|
1142
|
+
section, row, column and the sublayout are decided without the registry
|
|
1143
|
+
and keep working meanwhile. Both leave the tree untouched; for
|
|
1144
|
+
`registry_unavailable`, give the site a moment and retry.
|
|
739
1145
|
- **Saving without publishing.** `page_save` is the equivalent of the
|
|
740
1146
|
YOOtheme Customizer "Save" button. Content lives in the staging
|
|
741
1147
|
copy. Visitors see nothing until `page_publish`.
|
|
@@ -751,19 +1157,27 @@ to an existing page.
|
|
|
751
1157
|
**Worked example (tool-call snippet):**
|
|
752
1158
|
|
|
753
1159
|
```jsonc
|
|
754
|
-
// Step 4.
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
element_type: "section",
|
|
759
|
-
props: { style: "primary" }, // section background = `style`, NOT `background`
|
|
1160
|
+
// Step 4. Build the section → row → column hull in ONE call.
|
|
1161
|
+
yootheme_builder_scaffold_section({
|
|
1162
|
+
layout_id: "home",
|
|
1163
|
+
columns: 1,
|
|
760
1164
|
etag: "abc123" // from yootheme_builder_get_etag
|
|
761
1165
|
})
|
|
762
|
-
// Response: {
|
|
763
|
-
//
|
|
1166
|
+
// Response: { section_path: "/children/0/children/3", row_path: "/children/0/children/3/children/0",
|
|
1167
|
+
// column_paths: ["/children/0/children/3/children/0/children/0"], etag: "def456" }
|
|
1168
|
+
// → content goes into column_paths[0]; next call uses etag "def456"
|
|
1169
|
+
|
|
1170
|
+
// Step 5 (optional). Set the section background — `style`, NOT `background`.
|
|
1171
|
+
yootheme_builder_element_update_settings({
|
|
1172
|
+
layout_id: "home",
|
|
1173
|
+
element_path: "/children/0/children/3", // the section_path above
|
|
1174
|
+
props: { style: "primary" },
|
|
1175
|
+
etag: "def456"
|
|
1176
|
+
})
|
|
1177
|
+
// Response: { rel_path: "/children/0/children/3", etag: "ghi789" }
|
|
764
1178
|
|
|
765
1179
|
// Step 9. page_save is a first-class L1 tool; call it directly.
|
|
766
|
-
yootheme_builder_page_save({
|
|
1180
|
+
yootheme_builder_page_save({ layout_id: "home", etag: "<latest>" })
|
|
767
1181
|
```
|
|
768
1182
|
|
|
769
1183
|
**Edge case:** YOOtheme allows nested sections (rare). If the user
|
|
@@ -774,7 +1188,7 @@ section sits inside another section.
|
|
|
774
1188
|
**Success criterion:** After `page_publish`, navigating to the page
|
|
775
1189
|
URL on the front-end shows the new hero section above the previous
|
|
776
1190
|
content. Re-reading the layout via
|
|
777
|
-
`yootheme_builder_page_get_layout({
|
|
1191
|
+
`yootheme_builder_page_get_layout({ layout_id })` shows the new
|
|
778
1192
|
section as the last child of the template root.
|
|
779
1193
|
|
|
780
1194
|
---
|
|
@@ -848,23 +1262,23 @@ inspection that writes nothing.
|
|
|
848
1262
|
// 1. INSPECT (writes nothing — dry_run defaults true, no etag needed).
|
|
849
1263
|
// Returns the current state, the theme-declared --uk-inverse, risk_class,
|
|
850
1264
|
// confidence, a recommendation and would_change/changes.
|
|
851
|
-
yootheme_builder_header_transparency_set({
|
|
1265
|
+
yootheme_builder_header_transparency_set({ layout_id: "home" })
|
|
852
1266
|
|
|
853
1267
|
// 2. REPAIR with the theme's own declared colour.
|
|
854
1268
|
yootheme_builder_header_transparency_set({
|
|
855
|
-
|
|
1269
|
+
layout_id: "home", mode: "auto", dry_run: false, etag: "<latest>"
|
|
856
1270
|
})
|
|
857
1271
|
|
|
858
1272
|
// 3. Turn it ON deliberately, without asserting a colour.
|
|
859
1273
|
// mode "auto" derives it; over a hero medium it writes NO colour and tells
|
|
860
1274
|
// you to look at the page instead.
|
|
861
1275
|
yootheme_builder_header_transparency_set({
|
|
862
|
-
|
|
1276
|
+
layout_id: "home", mode: "auto", transparent: true, dry_run: false, etag: "<latest>"
|
|
863
1277
|
})
|
|
864
1278
|
|
|
865
1279
|
// 4. A colour you MEASURED over a hero medium (only mode "explicit" takes one).
|
|
866
1280
|
yootheme_builder_header_transparency_set({
|
|
867
|
-
|
|
1281
|
+
layout_id: "home", mode: "explicit", text_color: "light",
|
|
868
1282
|
dry_run: false, etag: "<latest>"
|
|
869
1283
|
})
|
|
870
1284
|
```
|
|
@@ -993,16 +1407,16 @@ never by narrowing the column. Narrowing a single column WITHOUT a matching
|
|
|
993
1407
|
**Easiest correct way — `scaffold_section` builds the whole factory-shaped
|
|
994
1408
|
hull for you (row grid string + per-column responsive widths in one call):**
|
|
995
1409
|
|
|
996
|
-
- `scaffold_section({
|
|
1410
|
+
- `scaffold_section({ layout_id, columns: 3, etag })` → a section whose row
|
|
997
1411
|
carries `layout: "1-3,1-3,1-3"` and whose 3 columns each carry
|
|
998
1412
|
`width_default: "1-1"` + `width_medium: "1-3"` → clean, gap-free, side-by-side
|
|
999
1413
|
from ≥960px. `columns: N` distributes equal widths (`1-N` for N ≤ 6,
|
|
1000
1414
|
`expand` for N > 6).
|
|
1001
1415
|
- For an ASYMMETRIC row pass explicit tokens, one per column:
|
|
1002
|
-
`scaffold_section({
|
|
1416
|
+
`scaffold_section({ layout_id, columns: 2, column_widths: ["2-3", "1-3"], etag })`
|
|
1003
1417
|
→ a row with `layout: "2-3,1-3"` and the matching column widths. A
|
|
1004
1418
|
wrong-length array or a bad token returns a friendly `400` before the write.
|
|
1005
|
-
- `scaffold_section({
|
|
1419
|
+
- `scaffold_section({ layout_id, columns: 1, etag })` → a single full-width
|
|
1006
1420
|
column with no `row.layout` and no column width (the factory single-column
|
|
1007
1421
|
shape).
|
|
1008
1422
|
|
|
@@ -1010,7 +1424,20 @@ hull for you (row grid string + per-column responsive widths in one call):**
|
|
|
1010
1424
|
into them with `element_add({ parent_path: "<column-path>", ... })`. If you
|
|
1011
1425
|
build columns by hand with `element_add` instead, remember to set BOTH
|
|
1012
1426
|
`row.props.layout` (the grid string) AND each column's `width_default` /
|
|
1013
|
-
`width_medium`, or the row reads as "Whole" and shows gaps in the builder
|
|
1427
|
+
`width_medium`, or the row reads as "Whole" and shows gaps in the builder:
|
|
1428
|
+
|
|
1429
|
+
```jsonc
|
|
1430
|
+
// Hand-built alternative to scaffold_section — set the grid string on the row…
|
|
1431
|
+
yootheme_builder_element_add({
|
|
1432
|
+
layout_id, parent_path: "<section-path>", element_type: "row",
|
|
1433
|
+
props: { layout: "1-3,1-3,1-3" }, etag,
|
|
1434
|
+
})
|
|
1435
|
+
// …and the matching responsive width on EACH column.
|
|
1436
|
+
yootheme_builder_element_add({
|
|
1437
|
+
layout_id, parent_path: "<row-path>", element_type: "column",
|
|
1438
|
+
props: { width_default: "1-1", width_medium: "1-3" }, etag,
|
|
1439
|
+
})
|
|
1440
|
+
```
|
|
1014
1441
|
|
|
1015
1442
|
**Success criterion:** in the builder's left panel the row shows N distinct
|
|
1016
1443
|
columns (not one column + an empty gap), and after publish the columns sit
|
|
@@ -1121,7 +1548,7 @@ token rotates (re-login, session timeout, a different tab), the preview
|
|
|
1121
1548
|
renders as a plain page without the builder-data injection and the panel
|
|
1122
1549
|
reports "not available". **Fix: reload the Customizer tab** (or re-open the
|
|
1123
1550
|
builder from Pages → the page → Builder). The stored layout is intact —
|
|
1124
|
-
confirm with `page_get_layout({
|
|
1551
|
+
confirm with `page_get_layout({ layout_id })`, which reads it directly
|
|
1125
1552
|
and is unaffected by the browser session.
|
|
1126
1553
|
|
|
1127
1554
|
---
|
|
@@ -1167,6 +1594,116 @@ schema's own claims: a report to check, not a fact about your build.
|
|
|
1167
1594
|
| `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
1595
|
| `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
1596
|
|
|
1597
|
+
## Placing a module or widget
|
|
1598
|
+
|
|
1599
|
+
A module or widget can reach a page in three ways, and they are not
|
|
1600
|
+
interchangeable: they differ in how many pages the thing shows up on, in how
|
|
1601
|
+
many edits one change costs, and in what happens on a page nobody has thought
|
|
1602
|
+
about yet. `modules_create` therefore REQUIRES a `placement` argument and
|
|
1603
|
+
refuses a call without one as `placement_not_chosen`, answering with exactly
|
|
1604
|
+
the rows below for the platform in question, plus that platform's notes.
|
|
1605
|
+
|
|
1606
|
+
The three ways are the same on both platforms. What differs is the vocabulary,
|
|
1607
|
+
and one behaviour: Joomla can narrow a site-wide placement to chosen pages,
|
|
1608
|
+
WordPress cannot.
|
|
1609
|
+
|
|
1610
|
+
**Joomla** (module, template position):
|
|
1611
|
+
|
|
1612
|
+
| `placement` | Reach | What it does |
|
|
1613
|
+
| --- | --- | --- |
|
|
1614
|
+
| `position` | every page | The module sits in a template position such as top and appears on EVERY page. Pass the position with this call. No layout edit, and one module serves the whole site. To narrow it to chosen pages, set the menu assignment with modules_update. |
|
|
1615
|
+
| `module_element` | one page | A Module element inside a column of ONE page layout embeds this module, and it appears on that page only. Call scaffold_section first for a valid column path. |
|
|
1616
|
+
| `position_element` | one page | The module goes into one of the positions builder-1 to builder-6. On its own it appears NOWHERE. A Position element placed in a page layout is what renders it, and it then appears on that page only. |
|
|
1617
|
+
|
|
1618
|
+
Two notes travel with that answer:
|
|
1619
|
+
|
|
1620
|
+
- modules_create builds two native module types (mod_custom, mod_menu). A
|
|
1621
|
+
builder module (mod_yootheme_builder) must already exist. Once it does, all
|
|
1622
|
+
three placements work for it exactly as they do for a native module.
|
|
1623
|
+
- You may not need a module at all: a YOOtheme element such as alert placed
|
|
1624
|
+
straight into a page layout does the same job, at the cost of a separate copy
|
|
1625
|
+
per page.
|
|
1626
|
+
|
|
1627
|
+
**WordPress** (widget, widget area):
|
|
1628
|
+
|
|
1629
|
+
| `placement` | Reach | What it does |
|
|
1630
|
+
| --- | --- | --- |
|
|
1631
|
+
| `position` | every page | The widget sits in a widget area and appears on EVERY page. Pass the area with this call. No layout edit, and one widget serves the whole site. WordPress has no built-in per-page control, so narrowing it to chosen pages needs a plugin. |
|
|
1632
|
+
| `module_element` | one page | A Widget element inside a column of ONE page layout embeds this widget, and it appears on that page only. Needs a position: builder-1 to builder-6, the areas that render nothing on their own. Call scaffold_section first for a valid column path. |
|
|
1633
|
+
| `position_element` | one page | The widget goes into one of the areas builder-1 to builder-6. On its own it appears NOWHERE. An Area element placed in a page layout is what renders it, and it then appears on that page only. |
|
|
1634
|
+
|
|
1635
|
+
And its two notes:
|
|
1636
|
+
|
|
1637
|
+
- modules_create builds three native widget types (custom_html, text, block). A
|
|
1638
|
+
builder widget must already exist. Once it does, all three placements work
|
|
1639
|
+
for it exactly as they do for a native widget.
|
|
1640
|
+
- You may not need a widget at all: a YOOtheme element such as alert placed
|
|
1641
|
+
straight into a page layout does the same job, at the cost of a separate copy
|
|
1642
|
+
per page.
|
|
1643
|
+
|
|
1644
|
+
Which ways need a `position` argument is a PLATFORM fact since 2026-08-08. On
|
|
1645
|
+
JOOMLA it is two of the three: a real theme position (top, bottom, navbar) for
|
|
1646
|
+
`position`, one of builder-1 to builder-6 for `position_element`, and
|
|
1647
|
+
`module_element` takes none, because a module with no position is a native state
|
|
1648
|
+
there. On WORDPRESS it is ALL THREE — a widget exists only inside a registered
|
|
1649
|
+
area, so there is no positionless widget to create, and `module_element` takes
|
|
1650
|
+
builder-1 to builder-6. A real theme position under `module_element` stays
|
|
1651
|
+
refused on both, because the module would ALSO show site wide and render twice
|
|
1652
|
+
on that page. Every way that needs one is REQUIRED, never defaulted: omit it and
|
|
1653
|
+
the call creates NOTHING and comes back `placement_needs_a_position`. A position you DO name is checked against what
|
|
1654
|
+
this site declares or registers, and the two ways it can be wrong are answered
|
|
1655
|
+
by two different codes: a value this site has never heard of (or one too long
|
|
1656
|
+
to be a position at all) comes back as `placement_position_unknown`, and a
|
|
1657
|
+
value that is real but describes a different outcome than the way you chose
|
|
1658
|
+
comes back as `placement_position_conflict`. Each names which half of the call
|
|
1659
|
+
was meant, instead of guessing.
|
|
1660
|
+
|
|
1661
|
+
Two things the layout itself will not let you do: a module or module_position
|
|
1662
|
+
element belongs inside a `column`, never on the layout root (the root takes
|
|
1663
|
+
`section` only, and a root-level module renders outside the section flow, over
|
|
1664
|
+
the navigation on a transparent header), and a `module_position` element renders
|
|
1665
|
+
with YOOtheme's own default layout (`stack`) when you leave the `layout` prop
|
|
1666
|
+
out. Set it only if you want `grid`. Never copy the `sidebar` value the element
|
|
1667
|
+
schema reports as that field's default: it is not one of the two accepted values
|
|
1668
|
+
and is rejected with a 422.
|
|
1669
|
+
|
|
1670
|
+
**"Every page" above is the assignment a `position` placement makes. On
|
|
1671
|
+
Joomla it takes effect immediately: every module write evicts Joomla's own
|
|
1672
|
+
module-list cache exactly like the native module manager does. On WordPress
|
|
1673
|
+
the widget write goes through `update_option()`, which refreshes WordPress'
|
|
1674
|
+
own options cache the same way any core option save does - if the site runs
|
|
1675
|
+
a persistent object-cache drop-in, this server behaves exactly as WordPress
|
|
1676
|
+
itself does there, no better and no worse. If a write looks invisible
|
|
1677
|
+
anyway, you are almost certainly looking at one of two things. (1) On
|
|
1678
|
+
Joomla, the site caches aggressively and the module was written by an OLDER
|
|
1679
|
+
plugin version or by a tool that bypasses the module manager - Joomla's
|
|
1680
|
+
conservative cache then serves the pre-write module list for up to `cachetime`
|
|
1681
|
+
(15 minutes by default), while the database row is already correct. Clearing
|
|
1682
|
+
the site cache or waiting resolves it; nothing is wrong with the module. (2)
|
|
1683
|
+
The position is one of the `builder-N` slots, which never render standalone -
|
|
1684
|
+
they reach a page only through a Position/Area element. For a quick
|
|
1685
|
+
confirmation either way, call `local_content_read` with `include_usage:true`
|
|
1686
|
+
and `verify_on_urls:["<a page url>"]` and read `usage.observed.urls[].status`
|
|
1687
|
+
(`present`/`absent`/`not_detectable`/`error` - see T1.7 in the local-content
|
|
1688
|
+
chapter), or fetch the page as a guest and search for the module's own
|
|
1689
|
+
signature (`id="<widget-id>"` on WordPress, `id="module-<id>"` on Joomla).
|
|
1690
|
+
|
|
1691
|
+
**Before doing N per-page edits, check whether one site-wide module already
|
|
1692
|
+
covers the need.** If a site-wide Builder module already sits in the header
|
|
1693
|
+
area, adding the new content to that ONE module can replace a separate edit
|
|
1694
|
+
on every page. A `local_content_read({kind:"modules"})` pass (T1.6 shows
|
|
1695
|
+
what `usage` returns) tells you what is already there before you start a
|
|
1696
|
+
per-page loop.
|
|
1697
|
+
|
|
1698
|
+
**A module or widget placed BOTH in its position and as an element on the same
|
|
1699
|
+
page renders twice.** This is not a hypothetical: the T1.7 section of the
|
|
1700
|
+
local-content chapter documents it measured live on
|
|
1701
|
+
`dev5wp.wootsup.com/mcp-module-probe/`, where a widget carrying both an element
|
|
1702
|
+
reference and a position reference served its content twice on one document.
|
|
1703
|
+
Before adding a `module`/`module_position` element for something that is ALSO
|
|
1704
|
+
assigned a rendering position, check its `usage.element_refs` first (T1.6), or
|
|
1705
|
+
drop the position assignment.
|
|
1706
|
+
|
|
1170
1707
|
---
|
|
1171
1708
|
|
|
1172
1709
|
## Workflow 2: Bind a dynamic source to a grid
|
|
@@ -1179,30 +1716,30 @@ renders dynamic items.
|
|
|
1179
1716
|
|
|
1180
1717
|
1. `yootheme_builder_health`: confirm host plugin reachable.
|
|
1181
1718
|
2. `yootheme_builder_pages_list({ fields: ["id", "label"] })` and
|
|
1182
|
-
`yootheme_builder_page_get_layout({
|
|
1719
|
+
`yootheme_builder_page_get_layout({ layout_id: "<id>", flat: false })`:
|
|
1183
1720
|
locate the target Grid. Note its JSON-Pointer `path` (e.g.
|
|
1184
1721
|
`/children/0/children/2/children/0`).
|
|
1185
|
-
3. `yootheme_builder_element_get({
|
|
1722
|
+
3. `yootheme_builder_element_get({ layout_id, element_path })`:
|
|
1186
1723
|
fetch the Grid's current props so you can preserve them. Binding
|
|
1187
1724
|
sets `props.source` and leaves the rest alone.
|
|
1188
1725
|
4. `yootheme_builder_sources_list()`: enumerate available Sources.
|
|
1189
1726
|
Each returns `{ name, label, origin, kind }`. Pick the one the
|
|
1190
1727
|
user asked for.
|
|
1191
|
-
5. `yootheme_builder_element_get_binding({
|
|
1728
|
+
5. `yootheme_builder_element_get_binding({ layout_id, element_path })`
|
|
1192
1729
|
check whether the Grid is already bound (idempotency: skip step
|
|
1193
1730
|
7 if `source_name` already matches the user's intent).
|
|
1194
1731
|
`element_get_binding` is a first-class L1 tool (B4) — call it directly.
|
|
1195
1732
|
6. `yootheme_builder_get_etag()`: fetch the optimistic-lock etag for
|
|
1196
1733
|
the upcoming mutation.
|
|
1197
|
-
7. `yootheme_builder_element_bind_source({
|
|
1734
|
+
7. `yootheme_builder_element_bind_source({ layout_id, element_path, source_name: "<name>", etag: "<etag>" })`
|
|
1198
1735
|
applies the binding. Returns `{ path, etag, has_binding: true }`.
|
|
1199
1736
|
Pass `source_id: "<origin>:<name>"` as well **only** when two
|
|
1200
1737
|
plugins register a source with the same `source_name` (the server
|
|
1201
1738
|
surfaces the ambiguity as an elicitation prompt; if the host
|
|
1202
1739
|
doesn't support elicitation you'll see a structured error listing
|
|
1203
1740
|
the candidates). This is a first-class L1 tool — call it directly.
|
|
1204
|
-
8. `yootheme_builder_page_save({
|
|
1205
|
-
then `yootheme_builder_page_publish({
|
|
1741
|
+
8. `yootheme_builder_page_save({ layout_id, etag: "<fresh>" })`
|
|
1742
|
+
then `yootheme_builder_page_publish({ layout_id, etag: "<fresh>" })`.
|
|
1206
1743
|
|
|
1207
1744
|
**Common pitfalls:**
|
|
1208
1745
|
|
|
@@ -1212,9 +1749,27 @@ renders dynamic items.
|
|
|
1212
1749
|
`relation`/`field`, `arguments` and `directives`; see the Tool Catalog
|
|
1213
1750
|
appendix for the full `element_bind_source` input-key list rather than
|
|
1214
1751
|
guessing a subset.
|
|
1215
|
-
- **Wrong parameter names.** Use `
|
|
1752
|
+
- **Wrong parameter names.** Use `layout_id` (not `pageId`),
|
|
1216
1753
|
`element_path` (not `path`), `source_name` (not `sourceName`),
|
|
1217
1754
|
`etag` (not `ifMatch`).
|
|
1755
|
+
- **A misspelled `field_mappings` KEY is refused, and be glad it is.** The keys
|
|
1756
|
+
are element PROPS; the values are source FIELDS. A key that is not a prop of
|
|
1757
|
+
the target element is rejected with `422 unknown_prop_keys`, listing
|
|
1758
|
+
`valid_prop_keys` and `did_you_mean`, and nothing is written. The reason is
|
|
1759
|
+
the ASYMMETRY, and the honest form of it is two measurements that disagree:
|
|
1760
|
+
on a native `articles` source, `{title, content}` rendered 10 rows while
|
|
1761
|
+
`{titel, content}` rendered **0** — the untouched `content` included; on a
|
|
1762
|
+
`grid_item` at an API-Mapper list source, the same substitution left all 5
|
|
1763
|
+
rows standing and cost only that slot. So a misspelled key sometimes empties
|
|
1764
|
+
the WHOLE binding and sometimes only its own slot, `element_get_binding`
|
|
1765
|
+
reads either back as healthy, and which one you get is not established. A
|
|
1766
|
+
wrong refusal costs you one call and tells you the valid names; a wrong
|
|
1767
|
+
acceptance can cost the page in silence. If you genuinely need a prop this
|
|
1768
|
+
build does not declare, `element_set_source` writes the carriers verbatim and
|
|
1769
|
+
only warns.
|
|
1770
|
+
(This is the opposite of the free `props` path — see Workflow 5, where an
|
|
1771
|
+
undeclared key is stored with a warning, because there real layouts carry
|
|
1772
|
+
such props and refusing them would refuse YOOtheme's own work.)
|
|
1218
1773
|
- **Source not in the list.** API Mapper sources only appear once
|
|
1219
1774
|
they're PUBLISHED in API Mapper (not just saved). If you bind an
|
|
1220
1775
|
unknown `source_name`, `element_bind_source` rejects it and the error
|
|
@@ -1229,8 +1784,9 @@ renders dynamic items.
|
|
|
1229
1784
|
Switcher, Table) accept a source binding. Binding a single-item
|
|
1230
1785
|
element like Headline returns a structured `validation` error.
|
|
1231
1786
|
- **Forgetting `etag`.** Every write requires the optimistic-lock
|
|
1232
|
-
etag. On `412 Precondition Failed
|
|
1233
|
-
|
|
1787
|
+
etag. On `412 Precondition Failed`, re-read what you were writing
|
|
1788
|
+
first — a fresh etag alone makes the retry succeed without telling you
|
|
1789
|
+
whether someone else's change is what you are about to overwrite.
|
|
1234
1790
|
- **Mis-routing bind vs the read helpers.** `element_bind_source` AND
|
|
1235
1791
|
its verify sibling `element_get_binding` (B4) are both first-class L1
|
|
1236
1792
|
tools — call them directly by name. Only `element_unbind_source` stays
|
|
@@ -1243,7 +1799,7 @@ renders dynamic items.
|
|
|
1243
1799
|
// Step 7. Bind a Posts source onto a Grid element. element_bind_source
|
|
1244
1800
|
// is a first-class L1 tool — call it directly.
|
|
1245
1801
|
yootheme_builder_element_bind_source({
|
|
1246
|
-
|
|
1802
|
+
layout_id: "home",
|
|
1247
1803
|
element_path: "/children/0/children/2/children/0",
|
|
1248
1804
|
source_name: "wp_posts",
|
|
1249
1805
|
etag: "abc123"
|
|
@@ -1252,16 +1808,55 @@ yootheme_builder_element_bind_source({
|
|
|
1252
1808
|
// Response: { rel_path: "/children/0/children/2/children/0", etag: "def456", has_binding: true }
|
|
1253
1809
|
// Verify (direct L1 call):
|
|
1254
1810
|
yootheme_builder_element_get_binding({
|
|
1255
|
-
|
|
1811
|
+
layout_id: "home", element_path: "/children/0/children/2/children/0"
|
|
1256
1812
|
})
|
|
1257
1813
|
// → { source_name: "wp_posts", source_config: { ... }, ... }
|
|
1258
1814
|
```
|
|
1259
1815
|
|
|
1260
|
-
**Edge case:** A Source can render zero items at runtime (e.g. empty
|
|
1261
|
-
search filter
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1816
|
+
**Edge case:** A Source can render zero items at runtime (e.g. an empty
|
|
1817
|
+
search filter, or an archive source on a single page that has no term to
|
|
1818
|
+
scope to). The bind call still succeeds; the front-end Grid just shows the
|
|
1819
|
+
YOOtheme "no items" placeholder. That is a DATA state, not a broken binding.
|
|
1820
|
+
|
|
1821
|
+
**But do not verify it by re-reading the binding.** `element_get_binding`
|
|
1822
|
+
answers "is a source attached", which is a different question from "does it
|
|
1823
|
+
deliver rows", and no amount of re-reading it will ever turn into the second
|
|
1824
|
+
answer. A customer once bound, published, looked, re-bound with `limit`,
|
|
1825
|
+
published, looked again, and only then ran `render_preview` — two full cycles
|
|
1826
|
+
for one fact.
|
|
1827
|
+
|
|
1828
|
+
There is ONE thing the re-read does now answer, and it is the one a blank card
|
|
1829
|
+
sends you looking for: if a `field_mappings` VALUE names a source field the
|
|
1830
|
+
source does not emit, `element_get_binding` says so — the same three keys the
|
|
1831
|
+
bind answers with, under the same names: `unknown_source_field_values`,
|
|
1832
|
+
`unknown_source_field_values_available` (what you should have written) and
|
|
1833
|
+
`unknown_source_field_values_hint`. That warning used to appear exactly once, at the
|
|
1834
|
+
moment of writing, so whoever debugged a blank card LATER read the state back
|
|
1835
|
+
and saw a binding indistinguishable from a healthy one. Absence of the keys
|
|
1836
|
+
means "nothing to report OR the source's field set could not be resolved here",
|
|
1837
|
+
never "checked and fine" — and it says nothing about the KEY side: a misspelled
|
|
1838
|
+
element prop is still read back as healthy, which is why the bind refuses it up
|
|
1839
|
+
front instead.
|
|
1840
|
+
|
|
1841
|
+
What the tools give you instead:
|
|
1842
|
+
|
|
1843
|
+
- Every bind answers with a `checks` block: `source_exists`, `field_mappings`,
|
|
1844
|
+
`field_mapping_props_checked`, `container_item_ok`. It costs nothing and is
|
|
1845
|
+
always there. It also states its own limit — none of them says whether rows
|
|
1846
|
+
arrive. Read the pair `field_mappings` + `field_mapping_props_checked`
|
|
1847
|
+
together: the first is a COUNT, not a verdict (a customer read
|
|
1848
|
+
`field_mappings: 2` as confirmation while one of the two was a typo), the
|
|
1849
|
+
second says whether those keys were measured against the element's declared
|
|
1850
|
+
props at all — `false` means the type's field list did not resolve here, not
|
|
1851
|
+
that a check failed.
|
|
1852
|
+
- `element_bind_source({… , probe: true})` renders the binding and answers
|
|
1853
|
+
`probe: {rows, rendered, empty, probed_path}`; at zero rows it adds a warning
|
|
1854
|
+
naming the likely causes. It costs a real render (~0.3-0.6s), so it is off by
|
|
1855
|
+
default. `probed_path` is the node that was rendered — the nearest container,
|
|
1856
|
+
because a `grid_item` renders nothing outside its `grid`.
|
|
1857
|
+
- `page_publish` / `page_save` report `binding-renders-nothing` in `quick_check`
|
|
1858
|
+
when the page's render produced zero rows and something on it was supposed to
|
|
1859
|
+
iterate. That one is free: the publish already renders for its own precheck.
|
|
1265
1860
|
|
|
1266
1861
|
**Success criterion:** After publish, the Grid on the front-end shows
|
|
1267
1862
|
items from the Source (verify by item count and at least one
|
|
@@ -1322,44 +1917,50 @@ a row layout. (Cross-template duplication is **not** supported by
|
|
|
1322
1917
|
`element_clone`. See "Important scope note" below.)
|
|
1323
1918
|
|
|
1324
1919
|
**Important scope note:** `yootheme_builder_element_clone` is
|
|
1325
|
-
**
|
|
1326
|
-
`{
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1920
|
+
**intra-template**. Its real schema is
|
|
1921
|
+
`{ layout_id, element_path, etag, to_parent_path?, to_index? }`.
|
|
1922
|
+
There is **no** `destPageId` and no cross-template argument. Omit
|
|
1923
|
+
`to_parent_path` and the copy lands at the same parent, right after
|
|
1924
|
+
the source; pass it and the copy lands under that parent instead
|
|
1925
|
+
(`to_index` picks the slot, default append) — the follow-up
|
|
1926
|
+
`element_move` this used to require is no longer needed. To duplicate
|
|
1927
|
+
into a DIFFERENT template, flag to the user that cross-template clone
|
|
1928
|
+
is not currently supported and suggest a CMS-level template
|
|
1929
|
+
duplication (in wp-admin or Joomla administrator).
|
|
1333
1930
|
|
|
1334
1931
|
**Canonical tool-call sequence (real parameter names, snake_case):**
|
|
1335
1932
|
|
|
1336
1933
|
1. `yootheme_builder_health`: confirm host plugin reachable.
|
|
1337
1934
|
2. `yootheme_builder_pages_list({ fields: ["id", "label"] })`:
|
|
1338
1935
|
locate the template by `label`. Note its `id`.
|
|
1339
|
-
3. `
|
|
1340
|
-
returns a flat
|
|
1341
|
-
element path + type. Pick the JSON-Pointer path of the
|
|
1342
|
-
to clone. `
|
|
1936
|
+
3. `yootheme_builder_element_list({ layout_id })`
|
|
1937
|
+
returns a flat view (lighter than `page_get_layout`) showing every
|
|
1938
|
+
element path + type + label + binding. Pick the JSON-Pointer path of the
|
|
1939
|
+
section to clone. `element_list` is L1; call it directly, no gateway.
|
|
1343
1940
|
4. `yootheme_builder_get_etag()`: fetch the optimistic-lock etag.
|
|
1344
|
-
5. `yootheme_builder_element_clone({
|
|
1941
|
+
5. `yootheme_builder_element_clone({ layout_id, element_path: "<src-path>", etag: "<etag>" })`
|
|
1345
1942
|
clones as sibling. Returns `{ path: "<new-path>", etag: "<fresh>" }`.
|
|
1346
1943
|
The new path is at the same parent, immediately after the source.
|
|
1347
|
-
6. (Optional)
|
|
1348
|
-
|
|
1349
|
-
|
|
1944
|
+
6. (Optional) To land the copy somewhere other than beside its source,
|
|
1945
|
+
pass the destination to step 5 directly:
|
|
1946
|
+
`yootheme_builder_element_clone({ layout_id, element_path: "<src-path>", to_parent_path: "<other-parent>", to_index: 0, etag: "<etag>" })`.
|
|
1947
|
+
`yootheme_builder_element_move` remains the way to relocate an
|
|
1948
|
+
element that already exists.
|
|
1949
|
+
7. `yootheme_builder_element_update_settings({ layout_id, element_path: "<final-path>", props: { ... }, etag: "<fresh>" })`
|
|
1350
1950
|
replaces the `props` on the clone. **Existing props NOT in the
|
|
1351
1951
|
request are removed** (update_settings is a full replace by default;
|
|
1352
1952
|
pass `merge: true` to apply a server-side deep-merge instead).
|
|
1353
1953
|
Read the current props first via `yootheme_builder_element_get`
|
|
1354
1954
|
if you only want to tweak a subset.
|
|
1355
|
-
8. `yootheme_builder_page_save({
|
|
1356
|
-
then `yootheme_builder_page_publish({
|
|
1955
|
+
8. `yootheme_builder_page_save({ layout_id, etag })`
|
|
1956
|
+
then `yootheme_builder_page_publish({ layout_id, etag })`.
|
|
1357
1957
|
|
|
1358
1958
|
**Common pitfalls:**
|
|
1359
1959
|
|
|
1360
1960
|
- **Inventing destination parameters.** `element_clone` does NOT
|
|
1361
|
-
accept `destPageId
|
|
1362
|
-
|
|
1961
|
+
accept `destPageId` or any cross-template argument. Its destination
|
|
1962
|
+
is optional and stays within ONE template: `to_parent_path` plus
|
|
1963
|
+
`to_index`, both snake_case.
|
|
1363
1964
|
- **Treating `element_update_settings` as a merge by default.** The handler
|
|
1364
1965
|
REPLACES the entire `props` object on the element unless you pass
|
|
1365
1966
|
`merge: true`. Read the existing shape via `yootheme_builder_element_get`
|
|
@@ -1370,16 +1971,422 @@ CMS-level template duplication (in wp-admin or Joomla administrator).
|
|
|
1370
1971
|
at roughly 200 characters with a visible `…(+N chars)` marker when
|
|
1371
1972
|
truncated, never a silent drop. There's no need to cross-check via
|
|
1372
1973
|
`element_list` just to see what a specific prop is currently set to.
|
|
1974
|
+
- **A `module`/`module_position` element is not a dead end either (T1.4).**
|
|
1975
|
+
On its own, a WordPress Widget or Joomla Module element's `props` are just
|
|
1976
|
+
a reference (`{widget: "custom_html-1"}` / `{module: 42}`) — no title, no
|
|
1977
|
+
content, no settings. `element_get` on that node ALSO carries a `resolved`
|
|
1978
|
+
field: the referenced widget/module's own row (title/type/settings/
|
|
1979
|
+
theme_overlay — the SAME shape `local_content_read(kind:"modules")`
|
|
1980
|
+
returns), or `resolved: null` plus a `resolved_reason` string when it can't
|
|
1981
|
+
be resolved (missing reference, deleted id, empty position) — never a
|
|
1982
|
+
throw. A `module_position` node resolves to `{position, modules: [...]}`,
|
|
1983
|
+
every widget assigned there. There's no need to separately call
|
|
1984
|
+
`local_content_read` just to see what a module element actually shows.
|
|
1985
|
+
- **"Where does this widget/module appear?" — `include_usage:true` (T1.6).**
|
|
1986
|
+
`local_content_read({ kind: "modules", include_usage: true })` adds a `usage`
|
|
1987
|
+
object per row: `element_refs` is the EXACT `{layout_id, path,
|
|
1988
|
+
via:"element"|"position"}` list of every layout location that reaches this
|
|
1989
|
+
widget/module — `via:"element"` is a DIRECT `module` element reference,
|
|
1990
|
+
`via:"position"` is an INDIRECT one (a `module_position` element pointing at
|
|
1991
|
+
this row's own `position`) — so you know whether to edit the element or the
|
|
1992
|
+
position assignment. `declared.position_visibility` classifies the row's
|
|
1993
|
+
`position` into `site-wide` (a theme panel/area — appears wherever that
|
|
1994
|
+
position renders), `never-standalone` (a `builder-1`..`builder-6` position —
|
|
1995
|
+
RENDERS ONLY through a `module_position` element, never on its own — live
|
|
1996
|
+
A/B-proven on Joomla), or `not-on-builder-pages` (`sidebar` — not rendered on
|
|
1997
|
+
Builder pages); null when the row has no position at all.
|
|
1998
|
+
`declared.assigned_pages` is Joomla-only real assignment info (`mode`
|
|
1999
|
+
`all`/`except`/`selected`/`none` resolved to concrete menu-item titles); on
|
|
2000
|
+
WordPress it is the explicit platform fact `{applicable:false, reason:
|
|
2001
|
+
"WordPress widget areas are global — widgets are not assigned to individual
|
|
2002
|
+
pages."}` — never simply absent. `declared` states what the CONFIGURATION
|
|
2003
|
+
claims, not what a visitor actually sees — a visibility plugin (Advanced
|
|
2004
|
+
Module Manager, Widget Context, …) or a time-gated rule can still override
|
|
2005
|
+
it. Default `false`: the layout scan this costs is never paid unasked.
|
|
2006
|
+
**A row with NO `usage` is not automatically a row you did not ask about.**
|
|
2007
|
+
There are THREE states and the key `usage_unavailable` is what separates the
|
|
2008
|
+
last two: neither `usage` nor `usage_unavailable` means `include_usage` was
|
|
2009
|
+
not requested; `usage` present means the enrichment ran; `usage_unavailable`
|
|
2010
|
+
present means it WAS requested and FAILED for that row. The row's primary
|
|
2011
|
+
fields (id, title, position, active, ...) stay fully trustworthy either way:
|
|
2012
|
+
an optional enrichment never deletes the answer it was decorating. Read
|
|
2013
|
+
`usage_unavailable` before concluding "this widget is used nowhere": on such
|
|
2014
|
+
a row nothing at all is known about where it appears.
|
|
2015
|
+
- **"What does a GUEST actually get served?" — `verify_on_urls` (T1.7).** Pair
|
|
2016
|
+
it with `include_usage:true` to add `usage.observed.urls[]` per row: fetches
|
|
2017
|
+
each url ONCE, anonymously, and checks whether this row's DOM signature
|
|
2018
|
+
(`id="<widget-id>"` on WordPress, `id="module-<id>"` on Joomla) appears —
|
|
2019
|
+
PLUGIN-AGNOSTIC, since it reads the rendered result, never any plugin's
|
|
2020
|
+
rules, so every visibility mechanism is covered without a line of
|
|
2021
|
+
plugin-specific code. Per url: `present`, `absent`, `not_detectable`, or
|
|
2022
|
+
`error`. `not_detectable` — NOT `absent` — covers FOUR cases, all meaning a
|
|
2023
|
+
missing signature proves nothing. **(1)** A `builder-1`..`builder-6`
|
|
2024
|
+
(`never-standalone`) position with no `via:"position"` reference: such a
|
|
2025
|
+
position renders EXCLUSIVELY through an explicit `module_position`
|
|
2026
|
+
element, so with none swept, YOOtheme's element-bound (DOM-anonymous, no id
|
|
2027
|
+
at all) rendering can't be ruled out. **(2)** The row sits in no position at
|
|
2028
|
+
all (empty `position` — an unplaced WordPress widget, Joomla's "NONE"
|
|
2029
|
+
group): nothing is expected on any url either way, so a missing signature
|
|
2030
|
+
says nothing about it. **(3)** The row is ALSO placed as a `module` ELEMENT
|
|
2031
|
+
**on the template THIS url renders**: that path carries no DOM id either, so
|
|
2032
|
+
even on an otherwise checkable position a not-found signature cannot tell
|
|
2033
|
+
"not rendered" from "rendered anonymously". MEASURED live on
|
|
2034
|
+
`dev5wp.wootsup.com/mcp-module-probe/`: `custom_html-1` has both an
|
|
2035
|
+
`element` and a `position` reference, and one document served its content
|
|
2036
|
+
**twice** while `id="custom_html-1"` appeared **once** — the element-rendered
|
|
2037
|
+
copy is `<div class="uk-panel widget widget_custom_html">`, with no instance
|
|
2038
|
+
attribute at all. The signature is not widened to cover it because the only
|
|
2039
|
+
token there is the widget TYPE class, which occurs 3× on that same page: a
|
|
2040
|
+
wider match would trade a false `absent` for a false `present`.
|
|
2041
|
+
**(4)** Where that element renders **could not be determined** — either the
|
|
2042
|
+
url did not map to a template (`pages_resolve` declines it; a Joomla site
|
|
2043
|
+
root served by a theme template is the common case), or the element sits on
|
|
2044
|
+
a THEME template, which governs many urls and is not enumerated here.
|
|
2045
|
+
**Cases (3) and (4) are decided PER URL, not per row.** An element reference
|
|
2046
|
+
on ANOTHER page cannot render on this url — a per-page layout renders on
|
|
2047
|
+
exactly its own page — so there you get the measured `present`/`absent`
|
|
2048
|
+
instead of a withheld answer. MEASURED on both platforms: `custom_html-1`'s
|
|
2049
|
+
element reference is on `page:2987` while the queried url was the site root
|
|
2050
|
+
(`page:1`), an anonymous fetch found 0 signature hits and 0 content hits, and
|
|
2051
|
+
`absent` is the correct decidable answer. The `reason` on every
|
|
2052
|
+
`not_detectable` names which case applies and, for (3)/(4), which template.
|
|
2053
|
+
Any OTHER
|
|
2054
|
+
position (`site-wide` — a real theme panel like navbar/header/a template's
|
|
2055
|
+
own `bottom`/`top` — or `not-on-builder-pages`) renders
|
|
2056
|
+
DIRECTLY from the theme with no element needed at all, so it's checkable
|
|
2057
|
+
even with completely empty `element_refs` — provided it has no `element`
|
|
2058
|
+
reference either. `error` = no observation was
|
|
2059
|
+
obtained from this url: a network failure, a timeout, a non-2xx, or a url
|
|
2060
|
+
the same-origin guard refused (off-site host, private address, disallowed
|
|
2061
|
+
scheme) — never treated as `absent`, never a throw. An `absent` result on a
|
|
2062
|
+
row `declared` calls unconditionally
|
|
2063
|
+
visible (`builder_visible:true`) adds a `discrepancy` — a named SUSPICION
|
|
2064
|
+
(a visibility plugin, a time-gated rule, a stale cache), never phrased as a
|
|
2065
|
+
diagnosis, and never attached to `not_detectable`/`error`.
|
|
2066
|
+
**Redirects are followed by this tool, bounded, and REPORTED.** When a url
|
|
2067
|
+
ends somewhere else, its entry carries `final_url` — the page whose markup
|
|
2068
|
+
was actually searched. That changes what the result means: `/contact` → 301
|
|
2069
|
+
→ `/kontakt` scans the KONTAKT page, so a module assigned to `/contact`'s
|
|
2070
|
+
own menu item is not expected in it, and the `discrepancy` then LEADS with
|
|
2071
|
+
the measured redirect instead of naming a visibility plugin. Re-check with
|
|
2072
|
+
the `final_url` before suspecting anything else. Every hop is re-checked
|
|
2073
|
+
against the same-origin guard, so a redirect off-site is refused with its
|
|
2074
|
+
own reason rather than silently fetched. Momentary
|
|
2075
|
+
snapshot per url, a sample (max 10 urls) — never a full crawl. Omit
|
|
2076
|
+
(default): zero requests, `observed` stays absent.
|
|
2077
|
+
- **"What's on THIS page from widgets/modules?" — `include_modules` (T1.8), the
|
|
2078
|
+
reverse of T1.6/T1.7.** Where `local_content_read`'s `usage` answers "for
|
|
2079
|
+
this ONE module, which pages?", `include_modules:true` on
|
|
2080
|
+
`yootheme_builder_page_get_layout` (→ `modules_on_page`) and
|
|
2081
|
+
`yootheme_builder_pages_audit` (→ `modules_by_page[layout_id]`) answers
|
|
2082
|
+
"for THIS page, which modules?" — the SAME D11 index, read backwards, never
|
|
2083
|
+
a second scan. Each result splits `from_elements` (a `module`/
|
|
2084
|
+
`module_position` element placed DIRECTLY in this page's own layout) from
|
|
2085
|
+
`from_positions` (a `site-wide` theme position — navbar/header/footer —
|
|
2086
|
+
rendering here with NO element in this page's layout at all). On Joomla, a
|
|
2087
|
+
`site-wide` module can still be assignment-EXCLUDED from one specific page
|
|
2088
|
+
(`except`); when that decision can't be resolved for this page (no menu
|
|
2089
|
+
item addresses it, or more than one does), the row lands in
|
|
2090
|
+
`from_positions_undecidable` instead of being guessed in or out. Default
|
|
2091
|
+
`false`: the layout sweep this costs is never paid unasked, and the
|
|
2092
|
+
response is byte-identical without it.
|
|
2093
|
+
- **A short list is never silently a FAILED list — two presence-only keys say
|
|
2094
|
+
so.** `unresolved_positions` appears when a `module_position` element on
|
|
2095
|
+
that page could not be resolved (a database error, unavailable widget
|
|
2096
|
+
APIs), and `unresolved_module_universe` appears when the site's module
|
|
2097
|
+
store could not be read AT ALL — which makes `from_positions` unknowable
|
|
2098
|
+
rather than empty. Both are emitted ONLY on failure, so a healthy answer
|
|
2099
|
+
omits them and is byte-identical to before. If you see either, the answer
|
|
2100
|
+
is INCOMPLETE: do not conclude "this page has no modules", and above all do
|
|
2101
|
+
not create a replacement module to "fix" it.
|
|
2102
|
+
- **The same rule for reading, one level up: an empty `local_content_read` is
|
|
2103
|
+
never a failed one.** A read that did not run to completion answers
|
|
2104
|
+
`error_code: local_content_unavailable` (HTTP 503), never a 200 with
|
|
2105
|
+
`{items:[], total:0}` — so an empty-and-OK response can only mean the site
|
|
2106
|
+
genuinely has none of that kind. The landscape tool
|
|
2107
|
+
(`local_content_types`) is the deliberate exception: it is a composite of
|
|
2108
|
+
independent sub-reads, so instead of failing the whole call it keeps the
|
|
2109
|
+
parts it could read and lists the rest under `degraded` — every count named
|
|
2110
|
+
there is a placeholder, not a measurement.
|
|
2111
|
+
- **"Now change what you found" — `modules_update` (W2), the write
|
|
2112
|
+
counterpart of `local_content_read(kind:"modules")` / `include_modules`.**
|
|
2113
|
+
Updates a widget's (WordPress) or module's (Joomla) SAFE writable subset —
|
|
2114
|
+
`title`, `position`, `active`, `theme_overlay` on BOTH platforms; `showtitle`,
|
|
2115
|
+
`ordering`, `access`, `assignment` on JOOMLA ONLY (WordPress rejects those
|
|
2116
|
+
four with a 400). `settings`/`params`/`content` are NOT in the schema at all
|
|
2117
|
+
— content editing stays the CMS editor's job; this tool is the
|
|
2118
|
+
display/placement layer.
|
|
2119
|
+
`dry_run` DEFAULTS true: a bare call inspects and writes NOTHING, returning
|
|
2120
|
+
the `{before, after, changed, notes, after_read_back}` diff of what a real
|
|
2121
|
+
write WOULD do.
|
|
2122
|
+
Every field you do NOT pass stays byte-identical — proven at the SQL/option-write
|
|
2123
|
+
level, not merely "looks unchanged".
|
|
2124
|
+
**A real write REPORTS WHAT IT READ BACK, not what you asked for.** After
|
|
2125
|
+
committing, the Joomla arm re-reads the stored record and fills `after` and
|
|
2126
|
+
`changed` from that read. So a field that did not land is reported at its
|
|
2127
|
+
STORED value, with a `notes` entry naming the divergence — never echoed back
|
|
2128
|
+
as if it had succeeded. This is the ONE way a dry run's answer and a real
|
|
2129
|
+
write's answer can differ, and it is deliberate: measured on Joomla,
|
|
2130
|
+
`modules_update({showtitle:false, ordering:3})` once answered
|
|
2131
|
+
`after.showtitle:false` about a row that held `3`. If the verification read
|
|
2132
|
+
itself fails, the write still stands and `notes` says the answer is
|
|
2133
|
+
unverified rather than pretending it was checked. That holds per FIELD, not
|
|
2134
|
+
only for the whole row: `assignment` is read back from a second table, and
|
|
2135
|
+
when THAT read fails the requested assignment is kept and named unverified,
|
|
2136
|
+
never replaced by the `none` an unread table looks like.
|
|
2137
|
+
**`after_read_back` tells a MEASURED `changed` entry from a merely REQUESTED
|
|
2138
|
+
one.** It lists exactly the keys whose `after` came out of storage. A key
|
|
2139
|
+
named in `changed` but ABSENT from `after_read_back` is a transition this
|
|
2140
|
+
call asked for and did not measure — the write committed, so `changed` keeps
|
|
2141
|
+
naming it, but check it before treating it as landed. Measured on Joomla with
|
|
2142
|
+
the `#__modules_menu` read-back failing after a committed write:
|
|
2143
|
+
`changed: ["title","ordering","assignment"]` with
|
|
2144
|
+
`after_read_back: ["title","ordering"]`. Before this field existed those three
|
|
2145
|
+
were indistinguishable, and the only qualification was a `notes` sentence.
|
|
2146
|
+
A DIVERGING read still counts as read back: this list answers "did we look?",
|
|
2147
|
+
never "did we like what we saw?". It is `[]` for every dry run (nothing was
|
|
2148
|
+
written, so nothing could be read back) and always `[]` on WordPress — a
|
|
2149
|
+
plain statement, never a failure signal.
|
|
2150
|
+
**On WORDPRESS there is no read-back, and `after` is what you REQUESTED.**
|
|
2151
|
+
Not an oversight: a widget's instance is one serialised `wp_options` value,
|
|
2152
|
+
so the per-field storage divergence the Joomla read-back exists to catch
|
|
2153
|
+
cannot arise, and the failure that CAN arise, the write not reaching storage,
|
|
2154
|
+
is raised as an ERROR rather than shown as a diverging `after`. So on
|
|
2155
|
+
WordPress do not read `after` as "checked against storage"; read it as "this
|
|
2156
|
+
is what was asked, and it did not fail".
|
|
2157
|
+
**Every value is TYPE-CHECKED, never coerced.** Send real JSON types: the
|
|
2158
|
+
string `"false"` is NOT the boolean `false` (PHP reads a non-empty string as
|
|
2159
|
+
TRUE, so a coerced `"false"` would have PUBLISHED the module), `"7abc"` is
|
|
2160
|
+
not the integer `7`, and an object is not a `title`. A wrong type is
|
|
2161
|
+
REFUSED with a 400 naming the expected one — this tool never rewrites a
|
|
2162
|
+
value it did not understand.
|
|
2163
|
+
**`notes` is the channel for "correctly did nothing, and that is not the
|
|
2164
|
+
end of it".** It is a list of plain sentences, usually empty. The case that
|
|
2165
|
+
motivated it: a Joomla module with `published = 1` whose `publish_up` is in
|
|
2166
|
+
the FUTURE reads `active:false`, so `{active:false}` is a genuine no-op with
|
|
2167
|
+
`changed: []` — and the module still goes live on schedule. `notes` says so
|
|
2168
|
+
instead of letting an empty `changed` read as "already handled".
|
|
2169
|
+
**Read `notes` whenever it is NON-EMPTY — an empty `changed` is not the
|
|
2170
|
+
trigger.** This used to say "read it whenever `changed` comes back empty",
|
|
2171
|
+
and that sent readers past the note that matters most: the one saying a field
|
|
2172
|
+
in a NON-empty `changed` was never read back. A note is attached exactly when
|
|
2173
|
+
something needs saying, so its presence is the signal.
|
|
2174
|
+
**One consequence is REPORTED in `notes`, not refused: the module will render
|
|
2175
|
+
TWICE.** If a Module/Widget element already embeds this module and you move it
|
|
2176
|
+
to a position that renders it on its own — a real theme position or widget
|
|
2177
|
+
area, not `builder-1`..`builder-6` — it appears once through each element and
|
|
2178
|
+
once through the position. `modules_create` refuses that same combination up
|
|
2179
|
+
front (`placement_position_conflict`); this verb has no `placement` argument
|
|
2180
|
+
to refuse it with, and both CMSs allow the move in their own UI, so blocking
|
|
2181
|
+
it here would be this tool inventing a restriction. The note names the
|
|
2182
|
+
elements and both ways back to one appearance. It rides on a `dry_run` too,
|
|
2183
|
+
which is where it is worth most.
|
|
2184
|
+
**`position_verified: false` reports on a check that did NOT happen, and it
|
|
2185
|
+
means here exactly what it means on `modules_create`.** Both verbs ask the
|
|
2186
|
+
same rule the same question, so the same site answers the same way: when the
|
|
2187
|
+
site's position list could not be read from there AND the name is not one of
|
|
2188
|
+
`builder-1` to `builder-6` (those are taken by name and need no list), the
|
|
2189
|
+
position is WRITTEN and the answer carries this field. The move happened; the
|
|
2190
|
+
check behind "an unknown position is refused BY NAME" did not, so a typo is
|
|
2191
|
+
still a typo. The field is ABSENT in the two cases with nothing to report —
|
|
2192
|
+
the value WAS vouched for, or this call wrote no position at all — so read
|
|
2193
|
+
`after.position` to tell those apart. A `dry_run` never carries it, and
|
|
2194
|
+
neither does an update that names no position: it reports on a position THIS
|
|
2195
|
+
call wrote, never on the one the module already had.
|
|
2196
|
+
On WordPress, `position` must be a REGISTERED widget area
|
|
2197
|
+
(`$wp_registered_sidebars` — on a YOOtheme site that is the 13 theme
|
|
2198
|
+
positions plus `builder-1`..`builder-6`); WordPress core does NOT validate
|
|
2199
|
+
this itself, so a typo used to park the widget in a phantom area that
|
|
2200
|
+
renders nowhere while the response reported success. `theme_overlay` MERGES onto the stored overlay: a
|
|
2201
|
+
key you OMIT keeps its current value; only the keys you explicitly include
|
|
2202
|
+
are written (so calling `theme_overlay:{style:"card-alt"}` never resets
|
|
2203
|
+
`visibility` or any other already-set key). The overlay's key list is
|
|
2204
|
+
PLATFORM-SPECIFIC — WordPress `_theme` (17 keys, incl. `showtitle`) vs.
|
|
2205
|
+
Joomla `params.yoo_config` (18 keys, incl. `language_parent_icon`/
|
|
2206
|
+
`language_icon` INSTEAD of `showtitle`) — an unknown or wrong-platform key
|
|
2207
|
+
(or a wrong value type) is REJECTED with a 400 naming every allowed key,
|
|
2208
|
+
never silently dropped, never coerced. Two further writes are REFUSED
|
|
2209
|
+
outright rather than half-kept, because the stored state cannot be
|
|
2210
|
+
expressed by the field you sent: a `title` on a WordPress `block` widget
|
|
2211
|
+
(that type has NO title — its instance is `{content}` only, WordPress never
|
|
2212
|
+
renders one, and storing it would additionally SUPPRESS the preview title
|
|
2213
|
+
derived from the block markup), and an `active:true` on a Joomla module
|
|
2214
|
+
whose publish window would still leave it `scheduled`/`expired`. On Joomla
|
|
2215
|
+
`active` means the `published` column AND the `publish_up`/`publish_down`
|
|
2216
|
+
window — the SAME derivation `local_content_read` uses — so a module
|
|
2217
|
+
scheduled for the future already reads `active:false`, and "deactivating"
|
|
2218
|
+
it is correctly a no-op instead of silently cancelling the schedule — with
|
|
2219
|
+
a `notes` entry so the no-op is not mistaken for "already handled".
|
|
2220
|
+
`access` (Joomla only) is the module's VIEW LEVEL, not a flag: Joomla
|
|
2221
|
+
filters modules by `access IN (authorised view levels)`, and `access:0` is
|
|
2222
|
+
in NOBODY's set — not even a Super User's — so such a module renders for no
|
|
2223
|
+
one while looking published. That is what `local_content_read` reports as
|
|
2224
|
+
`builder_visible:false`; `modules_update({access: 1})` is the repair.
|
|
2225
|
+
`access:0` itself is refused, because 0 is the very state being repaired.
|
|
2226
|
+
Advanced/gateway-only.
|
|
2227
|
+
- **"Build one from scratch" — `modules_create` (W3), then `element_add` —
|
|
2228
|
+
the two-step recipe.** `modules_create` supports exactly FIVE known types:
|
|
2229
|
+
WordPress `custom_html` (title+content), `text` (title+text), `block`
|
|
2230
|
+
(content only, NO title); Joomla `mod_custom` (title+content COLUMN+
|
|
2231
|
+
showtitle), `mod_menu` (title + the params keys menutype/base/startLevel/
|
|
2232
|
+
endLevel/showAllChildren/tag_id/class_sfx/window_open/layout/
|
|
2233
|
+
moduleclass_sfx/cache/cache_time/cachemode). An unknown type is rejected
|
|
2234
|
+
with this exact list. `placement` is REQUIRED (`position`/`module_element`/
|
|
2235
|
+
`position_element`, i.e. how the module reaches a page); a call that omits it
|
|
2236
|
+
creates NOTHING and instead returns `ways`, every way for this platform with
|
|
2237
|
+
its reach and a summary, plus `notes`. `content`/`text` are stored
|
|
2238
|
+
byte-for-byte, same no-sanitization guarantee as `modules_update`.
|
|
2239
|
+
**`position` is ONE argument with ONE meaning on both platforms**: the
|
|
2240
|
+
template position or widget area the module goes into, validated SERVER-side
|
|
2241
|
+
against the positions this site declares or registers, because the set of
|
|
2242
|
+
areas is a property of the SITE and no schema here can know it. **Joomla
|
|
2243
|
+
takes it AT CREATE TIME**, exactly as WordPress does; it stopped being a
|
|
2244
|
+
`modules_update`-only concern on 2026-08-06. The value must agree with
|
|
2245
|
+
`placement` or the call is refused BY NAME rather than guessed:
|
|
2246
|
+
`placement_needs_a_position` for `placement:"position"` OR
|
|
2247
|
+
`placement:"position_element"` with no position (both ways need one and they
|
|
2248
|
+
need different values, so neither is defaulted),
|
|
2249
|
+
`placement_position_conflict` for a `builder-N` position under
|
|
2250
|
+
`placement:"position"` (a builder position renders nothing on its own), for a
|
|
2251
|
+
REAL theme position under `placement:"module_element"` (measured on both
|
|
2252
|
+
platforms: the module then renders TWICE on that page), or for a non-builder
|
|
2253
|
+
position under `placement:"position_element"`; and
|
|
2254
|
+
`placement_position_unknown` for a value this site does not declare at all,
|
|
2255
|
+
or that could not be a position at all because it is longer than 50
|
|
2256
|
+
characters. That 50 is the width of the JOOMLA column that stores a position;
|
|
2257
|
+
WordPress keeps widget areas as option keys with no width of their own, and
|
|
2258
|
+
the bound is enforced on both platforms anyway, because `position` is one
|
|
2259
|
+
argument with one meaning and the narrower platform is what bounds it. That
|
|
2260
|
+
last one is answered by the SAME
|
|
2261
|
+
rule on both platforms, and when the site's list could be read the refusal
|
|
2262
|
+
carries it as `declared_positions` as well as naming it in the message.
|
|
2263
|
+
Under `placement:"module_element"` a Joomla module is created with no position
|
|
2264
|
+
and assigned to ALL pages, so it is immediately visible to `element_add`. On
|
|
2265
|
+
WordPress the same placement REQUIRES a builder area since 2026-08-08: a
|
|
2266
|
+
widget exists only inside a registered area, and this tool asks which one
|
|
2267
|
+
instead of picking `builder-1` for you.
|
|
2268
|
+
**A `position` is not by itself a rendering.** `builder-1`..`builder-6` is
|
|
2269
|
+
classified `never-standalone` on BOTH platforms (that is what
|
|
2270
|
+
`local_content_read(include_usage:true)` reports back), so a `builder-N`
|
|
2271
|
+
position means "it now has a home", not "it now shows up". The
|
|
2272
|
+
live A/B that proved a `builder-N` module produces zero frontend output was
|
|
2273
|
+
measured on Joomla; on WordPress the classifier reports the same value and
|
|
2274
|
+
the same rule is what `next_step` assumes. Either way, step 2 (`element_add`)
|
|
2275
|
+
is what actually puts it on a page. Do not read a freshly created row as
|
|
2276
|
+
visible until an element references it.
|
|
2277
|
+
**`builder_visible` can come back as the STRING `unknown_until_next_request`
|
|
2278
|
+
on a real WordPress create.** WordPress builds `$wp_registered_widgets` once
|
|
2279
|
+
per request, so the widget this very call just wrote is not in it yet: the
|
|
2280
|
+
honest answer is neither `true` nor `false`. It is a timing artefact of the
|
|
2281
|
+
same request, not a fault and not a warning. Read the row again (any later
|
|
2282
|
+
request, e.g. `local_content_read({kind:"modules"})`) to get the real
|
|
2283
|
+
boolean, and never branch on it as if it were `false`.
|
|
2284
|
+
`dry_run` DEFAULTS true, same discipline as `modules_update`: once
|
|
2285
|
+
`placement` is chosen, a bare call previews `{type, position, fields}` and
|
|
2286
|
+
creates nothing. A REAL create
|
|
2287
|
+
(`dry_run:false`) returns the full created `row` (the SAME
|
|
2288
|
+
`local_content_read(kind:'modules')` D2 shape) PLUS `next_step` — the
|
|
2289
|
+
EXACT follow-up call **for the placement you chose**, one shape per choice:
|
|
2290
|
+
| `placement` | `next_step` |
|
|
2291
|
+
|---|---|
|
|
2292
|
+
| `module_element` | `element_add{element_type:"module", props:{widget:"custom_html-3"}}` on WordPress, `props:{module:"122"}` on Joomla |
|
|
2293
|
+
| `position_element` | `element_add{element_type:"module_position", props:{content:"builder-1"}}` |
|
|
2294
|
+
| `position` | Joomla: `modules_update{id, assignment}` to narrow it to chosen pages. WordPress: `local_content_read{kind:"modules", include_usage:true}` — read-only, because WordPress has no per-page control to narrow it with; add `verify_on_urls` to PROVE guest-visible rendering rather than trust `active`/`builder_visible` alone |
|
|
2295
|
+
**Two live-measured details, both baked into `next_step` so you never have
|
|
2296
|
+
to remember them:**
|
|
2297
|
+
1. **Joomla's `props.module` is a STRING** (`"122"`), never a bare
|
|
2298
|
+
integer — a mismatch here silently fails to resolve, it does not error.
|
|
2299
|
+
2. **A `module_position` element names the POSITION, not one module** — it
|
|
2300
|
+
renders every published entry sitting there. That element accepts an
|
|
2301
|
+
optional `layout` prop from its own enum (`stack`/`grid`) — leaving it
|
|
2302
|
+
out is safe and defaults to `stack`; set it only if you want `grid`.
|
|
2303
|
+
`next_step` therefore leaves the key out. Never copy the `sidebar` value
|
|
2304
|
+
the element schema reports as that field's default: it is not one of the
|
|
2305
|
+
two accepted values and is rejected with a 422. This does NOT apply to
|
|
2306
|
+
the `module` form.
|
|
2307
|
+
Copy `next_step` verbatim, add `layout_id` + `etag` + `parent_path` when it
|
|
2308
|
+
is an `element_add` (that tool REQUIRES `layout_id` and `etag`, and unlike
|
|
2309
|
+
the fourteen tools that take an `element_path` it names its parent argument
|
|
2310
|
+
`parent_path`), then make the call. A `null` here is an answer, not a gap:
|
|
2311
|
+
the widget is already live site wide and nothing further is needed.
|
|
2312
|
+
**One more field the answer can carry, and it is the only one that reports on
|
|
2313
|
+
something that did NOT happen: `position_verified: false`.** It appears when a
|
|
2314
|
+
position was STORED that the server could not vouch for — the site's position
|
|
2315
|
+
list was not readable from there AND the name is not one of `builder-1` to
|
|
2316
|
+
`builder-6`, which are taken by name and need no list. The write happened; the
|
|
2317
|
+
check behind this tool's "an unknown position is refused BY NAME" promise did
|
|
2318
|
+
not, so the value is in place unexamined and a typo is still a typo. The field
|
|
2319
|
+
is ABSENT in the two cases where there is nothing to report — the stored
|
|
2320
|
+
position WAS vouched for, or no position was stored at all — so absence alone
|
|
2321
|
+
does not mean "checked". Read `preview.position` to tell those two apart: it
|
|
2322
|
+
is empty only in the second. A `dry_run` never carries the field, so a preview
|
|
2323
|
+
cannot warn you that the position it shows will be stored unverified.
|
|
2324
|
+
**The answer also names what will render the module before you asked for
|
|
2325
|
+
it: `element_refs`.** Each entry is `{layout_id, path, via}` and there are
|
|
2326
|
+
exactly two ways to get one. `via: "element"` means a `module` element in
|
|
2327
|
+
some layout ALREADY names the id this call just took — WordPress reuses a
|
|
2328
|
+
deleted widget's number (the allocator is highest-instance-plus-one), so a
|
|
2329
|
+
new widget can inherit a reference nobody meant for it. `via: "position"`
|
|
2330
|
+
means a `module_position` element already renders the position you created
|
|
2331
|
+
into; that needs no reuse at all, happens on both platforms, and is the
|
|
2332
|
+
common one. The field is ALWAYS present, so `[]` is the answer "nothing
|
|
2333
|
+
renders it yet" rather than a build without the concept. A `dry_run` carries
|
|
2334
|
+
it too, and that is where it is worth most, since `dry_run` defaults to true
|
|
2335
|
+
and is the first call you make. On JOOMLA a `dry_run` can report only
|
|
2336
|
+
`via: "position"`: the module id is assigned by the database at insert time,
|
|
2337
|
+
so on a preview there is no id for a layout to reference. That is a plain
|
|
2338
|
+
statement about that arm, not a failure.
|
|
2339
|
+
**One asymmetry to know about that resolution step:** on the plugin's own MCP
|
|
2340
|
+
transport a large answer is trimmed to a response budget, top-level keys are
|
|
2341
|
+
dropped largest first, and `preview` is one of the large ones while this flag
|
|
2342
|
+
is the smallest. So a widget with several KB of content can hand you
|
|
2343
|
+
`position_verified: false` and no `preview` to resolve it with. Every dropped
|
|
2344
|
+
key is named in `omitted_keys`, so check there before reading an absent
|
|
2345
|
+
`preview` as "no position was stored". (The `retry_hint` that comes with a
|
|
2346
|
+
trimmed answer suggests raising `max_chars`; `modules_create` has no such
|
|
2347
|
+
argument, so that hint does not apply here.)
|
|
2348
|
+
Advanced/gateway-only.
|
|
2349
|
+
- **The fifteen typed failures of `modules_update` / `modules_create` /
|
|
2350
|
+
`modules_delete`, and what each one licenses you to do next.** These are their
|
|
2351
|
+
OWN family, separate from the `pages_audit`/`heading_outline` target-error
|
|
2352
|
+
table above. Three of
|
|
2353
|
+
the four `placement_*` codes belong to `modules_create` alone. The fourth,
|
|
2354
|
+
`placement_position_unknown`, is also what `modules_update` answers for a
|
|
2355
|
+
position this site does not declare, so that one mistake reads the same
|
|
2356
|
+
whichever verb you sent it with. All four are the ones a caller meets first,
|
|
2357
|
+
because they are decided before anything is written:
|
|
2358
|
+
|
|
2359
|
+
| `error_code` | HTTP | What it means | What to do |
|
|
2360
|
+
| --- | --- | --- | --- |
|
|
2361
|
+
| `modules.placement_not_chosen` | 400 | `modules_create` only. `placement` was missing or not one of `position` / `module_element` / `position_element`. Nothing was created, not even a preview: the check runs before `dry_run` is honoured. | The error carries BOTH halves of the answer, `ways` and `notes`. Read them, pick a way, resend with `placement` set. |
|
|
2362
|
+
| `modules.placement_needs_a_position` | 400 | `modules_create` only. The way you chose needs a `position` and the call carried none. WHICH ways need one is a platform fact since 2026-08-08: all three on WordPress, where a widget exists only inside a registered area, and two on Joomla, where a module with no position is a native state. They take different values, and none is defaulted. | Resend the SAME `placement` with `position` set. Switching placement to get past this builds something other than what you asked for. |
|
|
2363
|
+
| `modules.placement_position_unknown` | 400 | `modules_create` AND `modules_update`, the one code of this family both verbs send. The `position` named is not one this site declares, or could not be a position at all (longer than 50 characters, the width of the Joomla column that stores it; the bound is enforced on BOTH platforms so one argument keeps one meaning). The SAME code on both platforms and on both verbs. What DIFFERS is the offer inside the message, and with it the advice: `modules_update` has no `placement` argument, so its message names the positions this site declares plus `builder-1` to `builder-6` and its runtime `hint` says exactly that. The advice cell to the right is the `modules_create` one. | The MESSAGE already names the values this call can use, narrowed to the placement you chose, so take the next value from there: `placement: "position"` takes a template position this site declares that is at most 50 characters long, `placement: "position_element"` takes `builder-1` to `builder-6` and nothing else, and `placement: "module_element"` takes no template position at all and only `builder-1` to `builder-6` if you name one. `declared_positions` rides alongside as the raw fact of everything this site declares, which is more than any one placement takes: it can even hold a value no call accepts, because a site may declare a position wider than the column that stores it. So when the two differ the message is the shorter, safer list. A value taken from `declared_positions` under the wrong placement is refused again as `placement_position_conflict`. When the error carries no list, the site's list could not be read and the value has to come from the template itself. |
|
|
2364
|
+
| `modules.placement_position_conflict` | 400 | `modules_create` only. `position` and `placement` are each fine alone and describe DIFFERENT outcomes together: a `builder-N` under `placement:"position"`, a real theme position under `placement:"module_element"`, a non-builder position under `placement:"position_element"`. | Change one of the two and resend. The message names the combination and the call that does what you asked for. |
|
|
2365
|
+
| `modules.invalid_field` | 400 | A field is unknown, unwritable (`settings`/`params`/`content`), wrong-platform, or the wrong JSON TYPE. The message names the allowed keys or the expected type. | Fix the call. Nothing was written; the module is untouched. |
|
|
2366
|
+
| `modules.field_value_invalid` | 400 | The KEY is right and the VALUE is not: a wrong JSON type (the string `"false"` is not the boolean `false`), a value outside a closed set (an unknown widget/module type, an unrecognised `assignment.mode`), or outside a permitted range (`menu_ids` must be positive). The largest of these classes. | Correct the VALUE of the named key and resend. Renaming the key will not help: the key is already right. The message names the expected type or the allowed values. |
|
|
2367
|
+
| `modules.field_not_on_this_type` | 400 | The field name is fine and this tool can write it, but THIS widget/module type has no such field. Nothing to rename. | The message names the types that DO carry it, and where this type keeps the equivalent instead (block markup, `theme_overlay`, or the type-specific params). Pick another type, or put the value where this type keeps it. |
|
|
2368
|
+
| `modules.field_combination_invalid` | 400 | Every field is valid ALONE; together they contradict each other, or one needs a partner the call did not carry. | Change the COMBINATION, not a value: add the missing field or drop one of the pair. The message names which of the two it is and in which direction. |
|
|
2369
|
+
| `modules.position_not_clearable` | 400 | **WordPress only.** You sent `position: ""` to take the widget out of every area, and on WordPress a widget exists only while it sits in one. Joomla accepts the same value, because a module there can genuinely sit in none. So this is about the platform, not about the value being malformed. | Send `active: false` instead: it parks the widget in `wp_inactive_widgets`, and naming a position again brings it back. Nothing was written. |
|
|
2370
|
+
| `modules.blocked_by_cms_state` | 409 | Name, value and combination are all correct. The CMS is in a state that would make the outcome untrue anyway, and this tool deliberately does not write the columns that would change it. | Nothing to fix in the call. The message names the exact field and value standing in the way; change it in the CMS module manager and repeat the call. |
|
|
2371
|
+
| `modules.invalid_id` | 400 | Joomla only, and only for the id-addressed verbs: `moduleId` was not a positive integer. WordPress has no counterpart, because its widget ids are strings such as `custom_html-2`. | Read the id off `local_content_read({kind:"modules"})` rather than constructing it. |
|
|
2372
|
+
| `modules.not_found` | 404 | The store WAS read and this id is genuinely not in it. | The module really is gone. Creating a replacement is legitimate here. |
|
|
2373
|
+
| `modules.element_refs_present` | 409 | `modules_delete` only. The request is fine; the STATE is not. Layout elements still reference this module directly, and the message names each one. | Delete those elements with `element_delete`, or repeat with `acknowledge_element_refs: true` to accept the dangling references. Nothing was removed. |
|
|
2374
|
+
| `modules.write_failed` | 500 | A write was ATTEMPTED and did not land. Every writing verb on both platforms answers this way: the create that stored nothing, the update whose change never reached the column, the delete that removed nothing. Reporting success would have been a positive false statement about the state of the store. | Re-read the module before retrying: the message names what did and did not reach storage. On Joomla the whole call shares one transaction, so it is all or nothing; on WordPress the widget instance and its placement are separate writes, so the message may report a half-applied call. |
|
|
2375
|
+
| `modules.unavailable` | 503 | The widget/module store could not be reached AT ALL. | **Never create a replacement.** Absence of an answer is not absence of the module. Retry later, or tell the user the store is down. |
|
|
2376
|
+
|
|
2377
|
+
The 404-vs-503 split is the load-bearing one: before it existed, an
|
|
2378
|
+
unreachable store answered 404 and an agent dutifully created a duplicate
|
|
2379
|
+
for a module that was there the whole time.
|
|
1373
2380
|
- **Clone-then-update path drift.** The clone returns a path that's
|
|
1374
2381
|
correct at the moment of the call. If you fire off many ops in
|
|
1375
2382
|
parallel, a concurrent edit may shift indices. Refresh via
|
|
1376
|
-
`get_etag` + `
|
|
2383
|
+
`get_etag` + `element_list` between independent batches.
|
|
1377
2384
|
- **Cloning a bound element keeps the binding.** `element_clone`
|
|
1378
2385
|
copies the entire element including `props.source`. If the user
|
|
1379
2386
|
wanted a "data-free" copy, call
|
|
1380
2387
|
`yootheme_builder_advanced({ tool: "yootheme_builder_element_unbind_source", arguments: { ... } })`
|
|
1381
2388
|
on the new path afterwards.
|
|
1382
|
-
- **Wrong parameter names.** Use `
|
|
2389
|
+
- **Wrong parameter names.** Use `layout_id`, `element_path`,
|
|
1383
2390
|
`etag` (NOT `pageId`, `srcPath`, `ifMatch`).
|
|
1384
2391
|
|
|
1385
2392
|
**Worked example (tool-call snippet):**
|
|
@@ -1387,7 +2394,7 @@ CMS-level template duplication (in wp-admin or Joomla administrator).
|
|
|
1387
2394
|
```jsonc
|
|
1388
2395
|
// Step 5. Clone the section element as a sibling.
|
|
1389
2396
|
yootheme_builder_element_clone({
|
|
1390
|
-
|
|
2397
|
+
layout_id: "home",
|
|
1391
2398
|
element_path: "/children/0/children/2", // the hero section to duplicate
|
|
1392
2399
|
etag: "abc123"
|
|
1393
2400
|
})
|
|
@@ -1395,11 +2402,11 @@ yootheme_builder_element_clone({
|
|
|
1395
2402
|
|
|
1396
2403
|
// Step 7. Tweak the clone (replace props entirely, or pass merge: true).
|
|
1397
2404
|
const current = yootheme_builder_element_get({
|
|
1398
|
-
|
|
2405
|
+
layout_id: "home",
|
|
1399
2406
|
element_path: "/children/0/children/3",
|
|
1400
2407
|
});
|
|
1401
2408
|
yootheme_builder_element_update_settings({
|
|
1402
|
-
|
|
2409
|
+
layout_id: "home",
|
|
1403
2410
|
element_path: "/children/0/children/3",
|
|
1404
2411
|
props: { ...current.props, style: "secondary" }, // section background = `style`
|
|
1405
2412
|
etag: "def456"
|
|
@@ -1413,7 +2420,7 @@ copy, follow up with
|
|
|
1413
2420
|
on the new path. Verify with a direct `element_get_binding` call.
|
|
1414
2421
|
|
|
1415
2422
|
**Success criterion:** After publish,
|
|
1416
|
-
`
|
|
2423
|
+
`yootheme_builder_element_list({ layout_id })`
|
|
1417
2424
|
shows the new section at the cloned path with the user's tweaks reflected in
|
|
1418
2425
|
`element_get` on that path.
|
|
1419
2426
|
|
|
@@ -1426,34 +2433,91 @@ guessing, and without rotating the user's key unnecessarily.
|
|
|
1426
2433
|
|
|
1427
2434
|
**Canonical tool-call sequence:**
|
|
1428
2435
|
|
|
1429
|
-
1. `yootheme_builder_diagnose` is a single
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
Call this **before** any other tool when you see
|
|
1435
|
-
|
|
2436
|
+
1. `yootheme_builder_diagnose` is a single self-diagnostic. Returns
|
|
2437
|
+
`{ platform, plugin_version, site_url, kid, scopes, token_scope,
|
|
2438
|
+
served_areas, capabilities, auth_bound, etag_ok, resolved_user_id,
|
|
2439
|
+
effective_capabilities, capability_ok, capability_warning, library_key,
|
|
2440
|
+
active_site }`. EVERY field is optional: one is absent when the host does
|
|
2441
|
+
not report it. Call this **before** any other tool when you see auth
|
|
2442
|
+
errors. (Takes no arguments beyond the optional `site_id`.)
|
|
2443
|
+
|
|
2444
|
+
`served_areas` and `effective_capabilities` name similar-sounding, DIFFERENT
|
|
2445
|
+
things: `served_areas` lists the tool AREAS this server serves (`pages`,
|
|
2446
|
+
`elements`, `sources`, `inspection`) — a server property, the same list
|
|
2447
|
+
`yootheme_builder_health` carries under the same name. `effective_capabilities`
|
|
2448
|
+
is what THIS caller's resolved CMS user may actually do
|
|
2449
|
+
(`can_create`/`can_edit`/`can_delete`) — see the bullet below. `capabilities`
|
|
2450
|
+
is `served_areas`' RETIRING name, kept one release as a byte-identical alias.
|
|
2451
|
+
|
|
2452
|
+
This list used to be longer, and the extra names never arrived — measured on
|
|
2453
|
+
four live installs. `plugin_error`, `yootheme_loaded`, `yootheme_version`,
|
|
2454
|
+
`endpoint_count`, `bearer_valid`, `bearer_error`, `summary` and `home_url`
|
|
2455
|
+
belonged to an older transport that assembled this report from two HTTP
|
|
2456
|
+
probes; today the plugin answers it in-process. For install facts (YOOtheme
|
|
2457
|
+
version, endpoint list, `home_url`) call `yootheme_builder_health`, which
|
|
2458
|
+
does carry them. For the bearer verdicts, read `auth_bound` and `etag_ok`
|
|
2459
|
+
right here — they are the successors of `bearer_valid` / `bearer_error`.
|
|
2460
|
+
|
|
2461
|
+
The two verdicts to read first are `auth_bound` (the Bearer key was accepted
|
|
2462
|
+
and resolved to a user) and `etag_ok` (a layout state was readable with it).
|
|
2463
|
+
There is no `plugin_reachable` field, and there is nothing to miss: the
|
|
2464
|
+
diagnostic is assembled by the plugin that received your call, so an answer
|
|
2465
|
+
at all IS the reachability answer. An unreachable plugin produces a
|
|
2466
|
+
transport error, never a payload.
|
|
2467
|
+
|
|
2468
|
+
Four of those are easy to skip past and answer questions you would
|
|
2469
|
+
otherwise take several calls to answer:
|
|
2470
|
+
|
|
2471
|
+
- `token_scope` (`read` / `write`). A `read` key fails every write tool
|
|
2472
|
+
with `read_only_token`, and that is a key problem, not a permissions one.
|
|
2473
|
+
- `effective_capabilities` + `capability_ok` + `capability_warning`. The
|
|
2474
|
+
CMS user the key resolves to, and what that user may actually do. A
|
|
2475
|
+
write-scoped key mapped to a user without edit rights still 403s;
|
|
2476
|
+
`capability_ok: false` is the only field that says so before you try.
|
|
2477
|
+
- `active_site` `{ site_id, via, process_scoped }`. Which site this call
|
|
2478
|
+
actually reached, and by which decision: `explicit` (you passed `site_id`
|
|
2479
|
+
— isolated from other clients of this server process), `use_site` (a
|
|
2480
|
+
runtime switch this process is on) or `registry_default` (the default in
|
|
2481
|
+
the sites file). The two inherited paths are kept apart deliberately: a
|
|
2482
|
+
call that reaches the wrong install answers just as confidently as one
|
|
2483
|
+
that reaches the right one. The field rides on EVERY tool call, not only
|
|
2484
|
+
this one. It replaces `process_default_site_id` + `resolved_via`, which
|
|
2485
|
+
this chapter kept naming after they had stopped shipping.
|
|
2486
|
+
- `site_url`. WHICH install answered, as the plugin reports it. `kid` and
|
|
2487
|
+
`auth_bound` cannot show that a perfectly valid key simply belongs to a
|
|
2488
|
+
different install, and that is the commonest cause of the refusal you are
|
|
2489
|
+
triaging. Read it against `active_site.site_id`, which names the registry
|
|
2490
|
+
entry your own config picked: when those two disagree, the disagreement
|
|
2491
|
+
is the finding.
|
|
2492
|
+
- `library_key`. Why the Layout Library tools can or cannot reach
|
|
2493
|
+
`api.yootheme.com`, as `{ configured, state, hint }`. Read `state`
|
|
2494
|
+
before you tell anyone to check their apikey field: see the Layout
|
|
2495
|
+
Library prerequisite section for the five states and who has to act on
|
|
2496
|
+
each. Never contains the key.
|
|
1436
2497
|
2. **Interpret the result:**
|
|
1437
|
-
-
|
|
1438
|
-
|
|
1439
|
-
Plugins → activate "YT Builder MCP"**
|
|
1440
|
-
administrator → Extensions → Plugins → enable
|
|
1441
|
-
MCP" and the matching webservices + component
|
|
1442
|
-
Do not retry until they confirm.
|
|
1443
|
-
- `
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
2498
|
+
- **The call itself failed** (a transport error, not a payload) → the
|
|
2499
|
+
WordPress / Joomla install is down OR the host plugin is deactivated.
|
|
2500
|
+
Send the user to **wp-admin → Plugins → activate "YT Builder MCP"**
|
|
2501
|
+
(WordPress) or **Joomla administrator → Extensions → Plugins → enable
|
|
2502
|
+
"System - YT Builder MCP" and the matching webservices + component
|
|
2503
|
+
entries** (Joomla). Do not retry until they confirm.
|
|
2504
|
+
- `auth_bound: false` → the Bearer key is wrong (typo, revoked, or wrong
|
|
2505
|
+
key for this install). Check `site_url` first: a key that is valid
|
|
2506
|
+
somewhere else fails here for a reason nothing about the key will show.
|
|
2507
|
+
For the upstream HTTP status call `yootheme_builder_sites_test`, which
|
|
2508
|
+
probes `/etag` itself and answers `bearer_valid` + `summary`; diagnose
|
|
2509
|
+
names no status of its own, because the request it is answering already
|
|
2510
|
+
got through. Then send the user to:
|
|
1447
2511
|
- **WordPress:** wp-admin → Tools → "YT Builder MCP" → Bearer Keys
|
|
1448
2512
|
→ copy the existing key into their MCP client config, or
|
|
1449
2513
|
generate a new one.
|
|
1450
2514
|
- **Joomla:** Components → YT Builder MCP → Bearer Keys → same.
|
|
1451
|
-
- `
|
|
2515
|
+
- `auth_bound: true, etag_ok: true` but the original
|
|
1452
2516
|
tool returned a 403 → the key works but the scope is too low for
|
|
1453
2517
|
the tool's required scope (`write` for mutations, `admin` for
|
|
1454
2518
|
destructive operations). Ask the user to regenerate the key with
|
|
1455
2519
|
a higher scope and restart the AI client.
|
|
1456
|
-
- `
|
|
2520
|
+
- `auth_bound: true, capability_ok: false` →
|
|
1457
2521
|
the Bearer key's scope IS write, but the CMS user it resolves to
|
|
1458
2522
|
(`effective_capabilities.can_edit`, `can_create`, `can_delete`, all
|
|
1459
2523
|
false) has no write capability at all, so writes will still 403
|
|
@@ -1472,7 +2536,8 @@ guessing, and without rotating the user's key unnecessarily.
|
|
|
1472
2536
|
- "Restart Claude / Cursor / Zed / Continue / Cline / Roo Code /
|
|
1473
2537
|
Claude Code / Codex CLI."
|
|
1474
2538
|
- "Confirm with `yootheme_builder_diagnose` that
|
|
1475
|
-
`
|
|
2539
|
+
`auth_bound: true` and `etag_ok: true` before retrying the original
|
|
2540
|
+
task."
|
|
1476
2541
|
|
|
1477
2542
|
**Common pitfalls:**
|
|
1478
2543
|
|
|
@@ -1499,29 +2564,33 @@ guessing, and without rotating the user's key unnecessarily.
|
|
|
1499
2564
|
yootheme_builder_diagnose({})
|
|
1500
2565
|
// Response shape:
|
|
1501
2566
|
// {
|
|
1502
|
-
//
|
|
2567
|
+
// platform: "wordpress",
|
|
1503
2568
|
// plugin_version: "1.2.0",
|
|
1504
|
-
//
|
|
1505
|
-
//
|
|
1506
|
-
//
|
|
1507
|
-
//
|
|
1508
|
-
//
|
|
1509
|
-
// site_url: "https://example.com",
|
|
1510
|
-
//
|
|
2569
|
+
// kid: "5cb829a21f4d5e4f",
|
|
2570
|
+
// scopes: ["read", "write"],
|
|
2571
|
+
// token_scope: "write",
|
|
2572
|
+
// auth_bound: false, // ← key is bad
|
|
2573
|
+
// etag_ok: false,
|
|
2574
|
+
// site_url: "https://example.com", // ← check this FIRST: right key, wrong install?
|
|
2575
|
+
// active_site: { site_id: "default", via: "registry_default", process_scoped: true }
|
|
1511
2576
|
// }
|
|
1512
2577
|
// → diagnosis: rotate the key. Send user to Tools/Components → YT Builder MCP.
|
|
2578
|
+
// → for the upstream HTTP status: yootheme_builder_sites_test({ site_id: "default" }).
|
|
1513
2579
|
```
|
|
1514
2580
|
|
|
1515
|
-
**Edge case:** `
|
|
1516
|
-
|
|
1517
|
-
isn't active.
|
|
2581
|
+
**Edge case:** `yootheme_builder_health` answers but reports
|
|
2582
|
+
`yootheme_loaded: false` — the user installed the MCP host plugin but YOOtheme
|
|
2583
|
+
Pro itself isn't active. Read that off **health**, not diagnose: diagnose used
|
|
2584
|
+
to declare the field and never filled it. (Health's WordPress arm reports it;
|
|
2585
|
+
the Joomla health answer is assembled before the builder bootstraps and does
|
|
2586
|
+
not carry it, so its absence there means "not knowable here", never `false`.) The MCP server still answers, but every tool that
|
|
1518
2587
|
touches the YOOtheme layout returns an empty/error response. Surface
|
|
1519
2588
|
the mismatch ("YOOtheme Pro is not active on this install") instead
|
|
1520
2589
|
of retrying. On Joomla this can also surface as a "YOOtheme Pro
|
|
1521
2590
|
required" admin notice in the component dashboard.
|
|
1522
2591
|
|
|
1523
2592
|
**Success criterion:** A subsequent `yootheme_builder_diagnose`
|
|
1524
|
-
returns `
|
|
2593
|
+
returns `auth_bound: true` AND `etag_ok: true`. The
|
|
1525
2594
|
original tool now returns a non-auth response.
|
|
1526
2595
|
|
|
1527
2596
|
---
|
|
@@ -1546,22 +2615,28 @@ an instance with a sensible default props payload.
|
|
|
1546
2615
|
`element_type` (`type_name` is a DEPRECATED alias, kept only for
|
|
1547
2616
|
1.0.x back-compatibility).
|
|
1548
2617
|
4. `yootheme_builder_pages_list({ fields: ["id", "label"] })` and
|
|
1549
|
-
`yootheme_builder_page_get_layout({
|
|
2618
|
+
`yootheme_builder_page_get_layout({ layout_id, flat: false })`
|
|
1550
2619
|
locate the `parent_path` (JSON-Pointer) where the new element
|
|
1551
|
-
should land.
|
|
2620
|
+
should land. A content/leaf type is only accepted inside a `column`
|
|
2621
|
+
(never directly in a `section` or a `row`) — if the layout has none yet,
|
|
2622
|
+
call `yootheme_builder_scaffold_section` first and use its
|
|
2623
|
+
`column_paths` as `parent_path` instead of hunting for one by hand.
|
|
1552
2624
|
5. `yootheme_builder_get_etag()`: fetch the optimistic-lock etag.
|
|
1553
|
-
6. `yootheme_builder_element_add({
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
`did_you_mean` suggestion when a close field
|
|
1558
|
-
to `content`). Read
|
|
1559
|
-
|
|
2625
|
+
6. `yootheme_builder_element_add({ layout_id, parent_path: "<path>", element_type: "<picked-name>", props: { ... }, etag })`
|
|
2626
|
+
checks `props` against the live type schema. On a type with a known
|
|
2627
|
+
schema, a prop key the type does not declare is ACCEPTED and stored —
|
|
2628
|
+
the answer carries a `warnings` entry with `code: "unknown_prop_keys"`
|
|
2629
|
+
naming the key, plus a `did_you_mean` suggestion when a close field
|
|
2630
|
+
exists (e.g. `contnet` to `content`). Read that and correct; do not
|
|
2631
|
+
re-guess. The VALUE side is stricter: an invalid enum member, an
|
|
2632
|
+
emptied required field, or a `<p>`-wrapped headline `content` is
|
|
2633
|
+
REFUSED with 422 before anything is written.
|
|
2634
|
+
7. (Optional) `yootheme_builder_element_update_settings({ layout_id, element_path: "<new-path>", props: { ... }, etag })`
|
|
1560
2635
|
iterates on the props. **Note: this REPLACES `props` entirely by
|
|
1561
2636
|
default; pass `merge: true` for a server-side deep-merge.** When
|
|
1562
2637
|
replacing, include every key you want to keep.
|
|
1563
|
-
8. `yootheme_builder_page_save({
|
|
1564
|
-
then `yootheme_builder_page_publish({
|
|
2638
|
+
8. `yootheme_builder_page_save({ layout_id, etag })`
|
|
2639
|
+
then `yootheme_builder_page_publish({ layout_id, etag })`.
|
|
1565
2640
|
|
|
1566
2641
|
**Common pitfalls:**
|
|
1567
2642
|
|
|
@@ -1570,14 +2645,22 @@ an instance with a sensible default props payload.
|
|
|
1570
2645
|
still works as a DEPRECATED alias, but prefer `element_type` to match
|
|
1571
2646
|
every other tool in this domain. The server's Zod schema rejects any
|
|
1572
2647
|
other key.
|
|
1573
|
-
- **Unknown prop key =
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
2648
|
+
- **Unknown prop key = a warning, not a rejection — and not silence
|
|
2649
|
+
either.** On a type with a known schema, a prop key the schema does not
|
|
2650
|
+
declare is STORED and reported: `warnings: [{ code: "unknown_prop_keys",
|
|
2651
|
+
message }]`, with a `did_you_mean` suggestion inside the message when a
|
|
2652
|
+
close field exists. It is a warning rather than a refusal because an
|
|
2653
|
+
undeclared key is not always wrong — YOOtheme itself stores props a type
|
|
2654
|
+
does not declare (a `column`'s `order_first` is written by the ROW control
|
|
2655
|
+
and is read at render), and a layout authored on a newer YOOtheme carries
|
|
2656
|
+
props the installed build has not declared yet. Refusing them would refuse
|
|
2657
|
+
layouts YOOtheme wrote. So: read the warning, and either fix the typo with
|
|
2658
|
+
`unset: ["<key>"]` — valid WITHOUT a `props` body, in which case only those keys go and every other prop stays — or keep the key knowingly. To bind a source, use
|
|
2659
|
+
`yootheme_builder_element_bind_source` rather than writing `source` /
|
|
2660
|
+
`source_extended` through `props` — those carriers are not filtered out of
|
|
2661
|
+
a props write, so a hand-written one silently competes with the bind
|
|
2662
|
+
tool's.
|
|
2663
|
+
- **Wrong parameter names on `element_add`.** Use `layout_id`
|
|
1581
2664
|
(not `pageId`), `parent_path` (not `parentPath`), `element_type`
|
|
1582
2665
|
(not `type` / `name`), `props` (not `settings`), `etag` (not
|
|
1583
2666
|
`ifMatch`).
|
|
@@ -1619,13 +2702,13 @@ yootheme_builder_element_type_get_schema({ element_type: "headline" })
|
|
|
1619
2702
|
// <p> block — see "Typography & heading styles"). If you omit title_element it
|
|
1620
2703
|
// DEFAULTS to h1, so set it explicitly on every headline to avoid multiple h1s.
|
|
1621
2704
|
yootheme_builder_element_add({
|
|
1622
|
-
|
|
1623
|
-
parent_path: "/children/0/children/2", // row inside section
|
|
2705
|
+
layout_id: "home",
|
|
2706
|
+
parent_path: "/children/0/children/2/children/0", // a column inside a row inside a section
|
|
1624
2707
|
element_type: "headline",
|
|
1625
2708
|
props: { content: "Welcome", title_element: "h1" },
|
|
1626
2709
|
etag: "abc123"
|
|
1627
2710
|
})
|
|
1628
|
-
// Response: { rel_path: "/children/0/children/2/children/0", etag: "def456" }
|
|
2711
|
+
// Response: { rel_path: "/children/0/children/2/children/0/children/0", etag: "def456" }
|
|
1629
2712
|
```
|
|
1630
2713
|
|
|
1631
2714
|
**Edge case:** A child theme can override a built-in element's
|
|
@@ -1657,13 +2740,14 @@ exposes. Never analyze findings by hand and never bulk-apply a fix.
|
|
|
1657
2740
|
write-approval prompt). Omit `checks`/`types` to run every category
|
|
1658
2741
|
against every scanned template.
|
|
1659
2742
|
2. Read the response's `findings` array. Each finding carries a `code`,
|
|
1660
|
-
`category`, `severity`,
|
|
2743
|
+
`category`, `severity`, an `affected_pages` total, up to 30 affected
|
|
2744
|
+
`pages`, and
|
|
1661
2745
|
(where a safe automatic fix exists) a `fix_hint` naming the exact
|
|
1662
2746
|
write tool and args template to run. `duplicate-layout` and
|
|
1663
2747
|
`title-collision` never carry a `fix_hint` — they need a human
|
|
1664
2748
|
decision (rename, merge, or intentionally keep as-is), not an
|
|
1665
2749
|
automatic patch.
|
|
1666
|
-
3. `yootheme_builder_template_summary({
|
|
2750
|
+
3. `yootheme_builder_template_summary({ layout_id, include_nodes: true })`
|
|
1667
2751
|
drills into one page a finding names. It carries the same
|
|
1668
2752
|
`heading_levels`, `images_missing_alt`, and `disabled_elements` counters
|
|
1669
2753
|
the audit used, plus (with `include_nodes:true` — opt-in, omit it for
|
|
@@ -1673,18 +2757,22 @@ exposes. Never analyze findings by hand and never bulk-apply a fix.
|
|
|
1673
2757
|
is disabled or just hidden on mobile, in one call instead of pulling
|
|
1674
2758
|
the raw `page_get_layout`. Page a large `content_nodes` list with
|
|
1675
2759
|
`limit`/`cursor`; drill into several findings' pages in ONE call with
|
|
1676
|
-
`
|
|
2760
|
+
`layout_ids:[...]` instead of `layout_id` (returns `summaries` keyed
|
|
1677
2761
|
by id + `not_found`). `heading_levels` rides alongside a
|
|
1678
2762
|
`heading_levels_note` sibling stating exactly which sources feed the
|
|
1679
2763
|
census: every element whose title/meta channel resolves to a heading
|
|
1680
2764
|
level, PLUS real `<h1>`-`<h6>` tags found inside `html`/`text`/`code`
|
|
1681
2765
|
element markup. It does NOT count `module`/widget output — a WordPress
|
|
1682
2766
|
widget or Joomla module has no content field this counter can parse.
|
|
2767
|
+
Since T1.4 that content is still readable, just not counted here:
|
|
2768
|
+
`yootheme_builder_element_get` on the `module`/`module_position` element
|
|
2769
|
+
resolves its referenced widget/module via a `resolved` field (see
|
|
2770
|
+
Workflow 3) — this counter is unaffected either way.
|
|
1683
2771
|
4. Fix that ONE page via the write tool named in `fix_hint.tool`, with
|
|
1684
2772
|
`fix_hint.args_template` as a starting point (`element_update_settings`,
|
|
1685
2773
|
`element_add`, etc), ETag-locked like every other write in this
|
|
1686
2774
|
server.
|
|
1687
|
-
5. `yootheme_builder_page_publish({
|
|
2775
|
+
5. `yootheme_builder_page_publish({ layout_id, etag })` republishes.
|
|
1688
2776
|
The response's `quick_check` block (default on) re-checks THIS page
|
|
1689
2777
|
with the same per-page codes, so you can confirm the fix landed
|
|
1690
2778
|
without a second full sweep.
|
|
@@ -1692,7 +2780,7 @@ exposes. Never analyze findings by hand and never bulk-apply a fix.
|
|
|
1692
2780
|
**Disabling instead of editing.** Sometimes the right remediation is to
|
|
1693
2781
|
exclude an element entirely rather than edit its content (a stray leftover
|
|
1694
2782
|
section, a duplicate block). `yootheme_builder_element_set_status({
|
|
1695
|
-
|
|
2783
|
+
layout_id, element_path, status: false, etag })` writes `props.status:
|
|
1696
2784
|
'disabled'` — the SAME field YOOtheme's own native "Disable element"
|
|
1697
2785
|
checkbox writes, so the element is excluded from BOTH the live frontend
|
|
1698
2786
|
render AND the NEXT `pages_audit`/`template_summary` (both read the same
|
|
@@ -1703,7 +2791,7 @@ than persisting a fabricated `props.status`.
|
|
|
1703
2791
|
|
|
1704
2792
|
**Need a real document outline instead of findings?** `pages_audit` tells you
|
|
1705
2793
|
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({
|
|
2794
|
+
heading tree in one look. `yootheme_builder_heading_outline({ layout_id })`
|
|
1707
2795
|
(read-only, reached via `yootheme_builder_advanced_read`) is a companion
|
|
1708
2796
|
STRUCTURE view for ONE page: every H1-H6 heading in true document order, each
|
|
1709
2797
|
carrying its TEXT and the element that carries it (`element_type`/`path` —
|
|
@@ -1712,7 +2800,7 @@ the alt-status of every image on the page (`alt: null` for missing, plus
|
|
|
1712
2800
|
`is_generic_alt` for a lazy "Logo"/"Bild"-style alt). Use it BEFORE editing a
|
|
1713
2801
|
page's heading hierarchy by hand, or to double-check a `heading-skip` finding
|
|
1714
2802
|
in context rather than as an isolated hit:
|
|
1715
|
-
`yootheme_builder_advanced_read({ tool: "yootheme_builder_heading_outline", arguments: {
|
|
2803
|
+
`yootheme_builder_advanced_read({ tool: "yootheme_builder_heading_outline", arguments: { layout_id: "page:4816" } })`
|
|
1716
2804
|
returns `{outline, images, h1_count, has_level_skip, images_missing_alt}` —
|
|
1717
2805
|
`has_level_skip` mirrors the `heading-skip` check's EXACT rule (a level more
|
|
1718
2806
|
than one above the highest seen so far, including a document that opens below
|
|
@@ -1726,12 +2814,21 @@ builder node to point at).
|
|
|
1726
2814
|
- **Promising a full SEO audit.** `pages_audit` covers Builder structure
|
|
1727
2815
|
and CMS basics only: heading hierarchy, empty/thin pages, missing
|
|
1728
2816
|
image alt text, duplicate layouts, title collisions, non-pretty URL
|
|
1729
|
-
fallbacks, stale content, and dead in-page anchors. Every response
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
It does not read or
|
|
1733
|
-
title, meta description, or
|
|
1734
|
-
|
|
2817
|
+
fallbacks, stale content, and dead in-page anchors. Every response carries
|
|
2818
|
+
a `disclaimer` field naming what is out of scope, and that field says
|
|
2819
|
+
itself that its list is **not exhaustive** — pass that on rather than
|
|
2820
|
+
presenting it as the complete set of blind spots. It does not read or
|
|
2821
|
+
evaluate Yoast/RankMath/any SEO-plugin meta title, meta description, or
|
|
2822
|
+
Open Graph tags, and it does not see whether the page may be INDEXED at
|
|
2823
|
+
all. Say this scope boundary out loud and point the user at their SEO
|
|
2824
|
+
plugin for that layer.
|
|
2825
|
+
- **Answering "we don't show up on Google" from this tool alone.** The first
|
|
2826
|
+
suspect is not a heading level — it is a `noindex`, which `pages_audit` has
|
|
2827
|
+
no check for in any category. Staging sites often carry a site-wide
|
|
2828
|
+
`<meta name="robots" content="noindex, nofollow">`, and until that is lifted
|
|
2829
|
+
every other SEO finding is beside the point. Settle it first:
|
|
2830
|
+
`yootheme_builder_fetch_public_url({ url, expect: "noindex" })` —
|
|
2831
|
+
`expect_found: true` means the page asks search engines to stay away.
|
|
1735
2832
|
- **Assuming builder props are the only content scanned.** Headings and
|
|
1736
2833
|
image alt text are also detected inside raw HTML: an `html`/`text`
|
|
1737
2834
|
element's markup content, not just builder props like
|
|
@@ -1748,7 +2845,16 @@ builder node to point at).
|
|
|
1748
2845
|
"Module"/"Module Position") are NOT scanned - verified live that neither
|
|
1749
2846
|
carries a markup-shaped `content` field on either platform; their actual
|
|
1750
2847
|
content lives in an external WP widget instance / Joomla `#__modules` row,
|
|
1751
|
-
outside the layout JSON entirely.
|
|
2848
|
+
outside the layout JSON entirely. Since W1/T1.8 that content IS readable,
|
|
2849
|
+
just not through THIS scanner: `include_modules:true` on
|
|
2850
|
+
`yootheme_builder_pages_audit`/`yootheme_builder_page_get_layout` lists
|
|
2851
|
+
every widget/module placed on this page (`modules_by_page`/
|
|
2852
|
+
`modules_on_page`), and `yootheme_builder_advanced_read({ tool:
|
|
2853
|
+
"yootheme_builder_local_content_read", arguments: { kind: "modules" } })`
|
|
2854
|
+
returns each one's actual `title`/`settings`/`theme_overlay` plus a
|
|
2855
|
+
`resolved` companion field on `element_get` for one `module`/
|
|
2856
|
+
`module_position` element (see Workflow 3) — this scanner's blind spot is
|
|
2857
|
+
unaffected either way.
|
|
1752
2858
|
- **`mode:"rendered"` sees what the builder tree structurally cannot** (T-A2,
|
|
1753
2859
|
2026-07-24) — pass `mode:"rendered"` (default is `"builder"`, the tree read
|
|
1754
2860
|
above) to instead render each page first (the SAME engine
|
|
@@ -1774,9 +2880,39 @@ builder node to point at).
|
|
|
1774
2880
|
pipeline, not a JSON walk): the response always carries
|
|
1775
2881
|
`render_scan: {attempted, rendered, capped}`, even when the cap never
|
|
1776
2882
|
engaged, so you can always see whether every page was actually rendered.
|
|
2883
|
+
When the cap DID engage, `render_scan.skipped_by_cap` names the templates
|
|
2884
|
+
it skipped (`{id, label}`), so a code found on the scanned subset is never
|
|
2885
|
+
silently generalised to the rest — re-run narrowed with `types:[…]` to
|
|
2886
|
+
reach them. That list is itself bounded at 30 entries and says so with
|
|
2887
|
+
`skipped_by_cap_not_listed` when it had to stop.
|
|
1777
2888
|
A page whose render failed is excluded from findings (never reported as a
|
|
1778
2889
|
false "no headings") and listed in `render_errors` instead — check both
|
|
1779
|
-
fields before trusting a clean rendered-mode result.
|
|
2890
|
+
fields before trusting a clean rendered-mode result. Each `render_errors`
|
|
2891
|
+
row carries `error_class` (the throwable's own class), and for an
|
|
2892
|
+
engine-level failure (`TypeError` and friends — a component executed during
|
|
2893
|
+
the render did not get something its own code requires) also a `reason`
|
|
2894
|
+
saying the layout is not the place to look and that the template is
|
|
2895
|
+
UNCHECKED rather than clean.
|
|
2896
|
+
- **A Joomla page carrying `module`/`module_position` is NOT a render
|
|
2897
|
+
failure** (render-fallback follow-up, 2026-07-30) — it renders via a
|
|
2898
|
+
tolerant fallback (the module/module-position subtree excluded, everything
|
|
2899
|
+
else rendered normally) rather than falling into `render_errors`, because
|
|
2900
|
+
Joomla's admin/REST context structurally cannot probe those two types (a
|
|
2901
|
+
CMS-runtime gap, not a broken layout). When this happens, the response
|
|
2902
|
+
carries `render_fallback: {pages: [{id, label, dropped_count}], suppressed_checks}`
|
|
2903
|
+
— `dropped_count` is how many module(-position) subtrees were excluded from
|
|
2904
|
+
THAT page's render, and `suppressed_checks` names which of `no-headings`,
|
|
2905
|
+
`missing-h1`, `heading-skip` were skipped on those pages specifically (the
|
|
2906
|
+
only three whose absence-based logic a hidden widget heading could
|
|
2907
|
+
fabricate — every other check, e.g. `multiple-h1`, only ever reports
|
|
2908
|
+
content that DID survive the fallback, so it keeps running). Same idea on
|
|
2909
|
+
`yootheme_builder_heading_outline` in `mode:"rendered"`: the response
|
|
2910
|
+
carries `render_fallback_used` (bool) + `render_dropped_count` (int),
|
|
2911
|
+
always present in that mode — treat `h1_count`/`has_level_skip`/the outline
|
|
2912
|
+
as possibly incomplete when `render_fallback_used` is true (content inside
|
|
2913
|
+
the excluded widget is invisible to the outline too). `WordPress renders
|
|
2914
|
+
these types successfully today, so this never engages there — WordPress
|
|
2915
|
+
pages never carry `render_fallback` / never set `render_fallback_used`.
|
|
1780
2916
|
- **Confusing the CONTENT count with `pages_list.elements_count`.**
|
|
1781
2917
|
`pages_list.elements_count` counts every node, including
|
|
1782
2918
|
`section`/`row`/`column`/`fragment` layout wrappers. `pages_audit`
|
|
@@ -1897,7 +3033,7 @@ What changes in the response:
|
|
|
1897
3033
|
tool returns `target_not_scannable` (HTTP 422). Tell the user the page could
|
|
1898
3034
|
not be audited and why; then audit it via `template_summary`, or pick a page
|
|
1899
3035
|
`pages_list` returns. Never present it as a clean result.
|
|
1900
|
-
- **`resolved_target`** — `{
|
|
3036
|
+
- **`resolved_target`** — `{ layout_id, post_id, type, label, frontend_url,
|
|
1901
3037
|
context, resolved_via, matched_by }`. This is the echo of WHICH page a fuzzy
|
|
1902
3038
|
address landed on. Always report it: `special: "home"` and
|
|
1903
3039
|
`title: "SunWest"` are guesses until the server says what they resolved to.
|
|
@@ -1907,36 +3043,29 @@ What changes in the response:
|
|
|
1907
3043
|
- **Richer per-hit evidence.** Single mode raises the per-page hit cap from 5 to
|
|
1908
3044
|
**100 hits per check** and adds a `content_snippet` to every locator (the site
|
|
1909
3045
|
sweep omits snippets to keep a whole-site payload small). The text leg also
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
the
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
- **On the plugin-served transport there is no summary table at all.** The
|
|
1934
|
-
`HITS`/`COUNT` column is rendered by the npm server. When the MCP server is the
|
|
1935
|
-
CMS plugin itself, the response is the JSON payload — so nothing labels the
|
|
1936
|
-
columns for you and the guidance above is the only thing standing between you
|
|
1937
|
-
and the misread. Read `findings[].pages[].hits` (falling back to
|
|
1938
|
-
`pages[].count`) and **never report `findings[].count` as the size of a
|
|
1939
|
-
single-page problem**: in single-target mode it is structurally `1`.
|
|
3046
|
+
keeps the per-hit locators under `findings[].pages[].locators` in both scopes.
|
|
3047
|
+
Both legs of the answer are the same JSON payload — there is no rendered
|
|
3048
|
+
summary table and no `Where to fix:` block on either, in either scope, so
|
|
3049
|
+
nothing labels a column for you and the two counters below are the whole
|
|
3050
|
+
story.
|
|
3051
|
+
- **Two counters, two levels, and neither is called `count`.**
|
|
3052
|
+
`findings[].affected_pages` is how many PAGES that finding matched. In
|
|
3053
|
+
single-target mode it is structurally `1` for every finding, so it is never
|
|
3054
|
+
the size of the problem there. `findings[].pages[].occurrences_here` is how
|
|
3055
|
+
many occurrences sit on THAT page — 19 alt-less images ⇒ `19`. It is the
|
|
3056
|
+
uncapped total: `hits_truncated` shortens the listed locators, never this
|
|
3057
|
+
figure. Report `occurrences_here` as the size of a single-page problem, and
|
|
3058
|
+
never `affected_pages` — an agent once reported "one image is affected"
|
|
3059
|
+
where nineteen were.
|
|
3060
|
+
- **A page entry with no `occurrences_here` is not a gap.** Some checks make the
|
|
3061
|
+
PAGE ITSELF the hit — `thin-content`, `empty-page`, `duplicate-layout` — and
|
|
3062
|
+
those entries carry no occurrence total at all, because there is nothing on
|
|
3063
|
+
the page to count more than once.
|
|
3064
|
+
- **What `occurrences_here` counts, the response says once.** The top-level
|
|
3065
|
+
`occurrences_unit` states it in words: matching ELEMENTS in the saved layout
|
|
3066
|
+
tree, a FLOOR when a bound row repeats or content is conditional, since the
|
|
3067
|
+
saved tree enumerates neither. Pass that caveat on rather than presenting the
|
|
3068
|
+
number as what a visitor's browser renders.
|
|
1940
3069
|
|
|
1941
3070
|
Two request shapes are refused up front, with a typed error rather than a
|
|
1942
3071
|
round-trip: more than one address key → `too_many_targets` (pick one; use
|
|
@@ -2048,6 +3177,87 @@ on the CONTENT count (`elements_content`), so wrapper-only pages — three neste
|
|
|
2048
3177
|
columns around nothing — are correctly flagged rather than passing as "not
|
|
2049
3178
|
empty".
|
|
2050
3179
|
|
|
3180
|
+
#### `dangling-source` — structure · critical
|
|
3181
|
+
|
|
3182
|
+
**Why it matters.** The page is not empty and nothing errored: an element IS
|
|
3183
|
+
there, bound to a source name that no longer exists. YOOtheme resolves the
|
|
3184
|
+
binding to nothing and renders nothing, so the gap looks identical to a layout
|
|
3185
|
+
problem while the cause sits in the source list. Without this finding the only
|
|
3186
|
+
way to spot it is to compare every binding on the site against
|
|
3187
|
+
`sources_list` by hand.
|
|
3188
|
+
**How to fix.** Compare each reported `source_name` against
|
|
3189
|
+
`yootheme_builder_sources_list`. Renamed elsewhere → repair the reference with
|
|
3190
|
+
`yootheme_builder_repair_source_references({from, to})` (that is the shipped
|
|
3191
|
+
`fix_hint`; `to` must be a currently registered name). Genuinely deleted →
|
|
3192
|
+
unbind the element instead, or rebuild the binding against a source that
|
|
3193
|
+
exists.
|
|
3194
|
+
**Severity.** Critical: the element renders nothing at all, and the page ships
|
|
3195
|
+
looking finished.
|
|
3196
|
+
|
|
3197
|
+
#### `unknown-source-fields` — structure · critical
|
|
3198
|
+
|
|
3199
|
+
**Why it matters.** The value-side sibling of `dangling-source`: the bound
|
|
3200
|
+
source EXISTS, but one or more `field_mappings` values name a source field the
|
|
3201
|
+
source does not emit, so that mapped slot binds to nothing and renders blank.
|
|
3202
|
+
Neither `empty-page` (an element IS there) nor `dangling-source` (the source
|
|
3203
|
+
name IS known) catches this — it falls through both rasters, and the same
|
|
3204
|
+
diagnosis is what `element_get_binding`/`inspect_multi_items_binding` already
|
|
3205
|
+
surface for a single element inspected by hand.
|
|
3206
|
+
**How to fix.** Compare each reported `unknown_source_field_values` entry
|
|
3207
|
+
against the `unknown_source_field_values_available` list that rides alongside
|
|
3208
|
+
it, then `element_bind_source` with a corrected `field_mappings` value (that is
|
|
3209
|
+
the shipped `fix_hint`).
|
|
3210
|
+
**Severity.** Critical: the mapped value renders nothing at all, and — unlike a
|
|
3211
|
+
typo'd element prop, which is refused before it writes — this one persists
|
|
3212
|
+
silently.
|
|
3213
|
+
|
|
3214
|
+
#### `container-missing-item-child` — structure · warning
|
|
3215
|
+
|
|
3216
|
+
**Why it matters.** A multi-item container (`grid`, `button`, `slideshow`,
|
|
3217
|
+
`accordion`, …) repeats its `*_item` child once per source row. With no such
|
|
3218
|
+
child there is nothing to repeat, so the container renders nothing — again with
|
|
3219
|
+
no error anywhere. `element_add` already says this at write time; this finding
|
|
3220
|
+
catches the states that write never saw, such as the child being deleted later
|
|
3221
|
+
or a library insert that dropped it.
|
|
3222
|
+
**How to fix.** `element_add` the type named in each locator's
|
|
3223
|
+
`expected_child_type` under the reported container path (that is the shipped
|
|
3224
|
+
`fix_hint`). Read its required props from `element_type_get_schema`, whose
|
|
3225
|
+
`minimal_layout` shows exactly this container-plus-child shape. If the container
|
|
3226
|
+
is meant to be bound to a source, bind the CHILD, never the container.
|
|
3227
|
+
**Severity.** Warning, not critical: the element genuinely renders nothing, but
|
|
3228
|
+
building the shell first and its child second is the order `minimal_layout`
|
|
3229
|
+
itself teaches — a page caught mid-build is half-finished, not broken.
|
|
3230
|
+
|
|
3231
|
+
#### `binding-renders-nothing` — structure · warning (publish/save only)
|
|
3232
|
+
|
|
3233
|
+
**Why it matters.** A bind that persisted is not a bind that delivers. The
|
|
3234
|
+
element is there, the source name is valid, the field mappings are in place, the
|
|
3235
|
+
save succeeded — and the page renders zero rows. Nothing on that path says so,
|
|
3236
|
+
which is why a customer once bound, published, looked, re-bound with `limit`,
|
|
3237
|
+
published, looked again, and only then ran `render_preview` and read
|
|
3238
|
+
`rendered_item_wrapper_count: 0`. This finding is that sentence, arriving two
|
|
3239
|
+
cycles earlier.
|
|
3240
|
+
**How to fix.** Each locator names the element AND its `source_name`. Usual
|
|
3241
|
+
causes in order: the source needs a context this template does not have (an
|
|
3242
|
+
archive source on a single page has no term to scope to — check with
|
|
3243
|
+
`source_get_schema` whether it takes a term/id argument), a filter or
|
|
3244
|
+
`query_arguments` narrowed it to nothing, or the source genuinely holds no
|
|
3245
|
+
entries yet. To check ONE binding as you make it rather than at publish, pass
|
|
3246
|
+
`probe: true` to `element_bind_source`.
|
|
3247
|
+
**Severity.** Warning: a source with no rows yet is a legitimate state (an empty
|
|
3248
|
+
category), and the page is not broken — it is empty where content was expected.
|
|
3249
|
+
|
|
3250
|
+
> **Scope, and it is narrower than every other `per-page` code.** This check
|
|
3251
|
+
> needs a ROW COUNT, and nothing in this product can produce one without
|
|
3252
|
+
> rendering. It therefore reads the render report that `page_save` / `page_publish`
|
|
3253
|
+
> has ALREADY computed for its own render-precheck — so it rides `quick_check`
|
|
3254
|
+
> and **never appears in `pages_audit`**, not even with `checks: ["structure"]`.
|
|
3255
|
+
> Rendering every page of a site to run one check would turn a sweep into
|
|
3256
|
+
> minutes. It also stays silent when the render threw (the precheck owns that
|
|
3257
|
+
> verdict), when the render used its module fallback (a partial render cannot
|
|
3258
|
+
> settle a zero), and when nothing on the page was supposed to iterate at all —
|
|
3259
|
+
> a `headline` bound to one field renders content and zero rows by construction.
|
|
3260
|
+
|
|
2051
3261
|
#### `no-headings` — structure · critical
|
|
2052
3262
|
|
|
2053
3263
|
**Why it matters.** With no heading of any level the page has no machine-readable
|
|
@@ -2427,7 +3637,7 @@ The raw response is a machine payload. What you hand the user follows this
|
|
|
2427
3637
|
shape every time, so two audits of two sites read the same way:
|
|
2428
3638
|
|
|
2429
3639
|
1. **Lead with the scope.** When `scope: "single"`, name the page the audit
|
|
2430
|
-
actually ran on from `resolved_target` — label, `
|
|
3640
|
+
actually ran on from `resolved_target` — label, `layout_id`, and
|
|
2431
3641
|
`matched_by` when the address was fuzzy ("`special: "home"` resolved to
|
|
2432
3642
|
*Startseite* (`page:4816`)"). When the response has no `scope`, it was a site
|
|
2433
3643
|
sweep: lead with `scanned.templates` and the `summary` totals.
|
|
@@ -2475,7 +3685,7 @@ shape every time, so two audits of two sites read the same way:
|
|
|
2475
3685
|
first (every field of `alt_paths` / `anchors` / … also rides on the locator
|
|
2476
3686
|
for the same hit, so nothing is lost), then the locator detail — halved
|
|
2477
3687
|
before it is dropped — then the remaining raw evidence, then whole evidence
|
|
2478
|
-
pages (`
|
|
3688
|
+
pages (`finding_pages_truncated`), so page ids and labels outlive the prose. Both
|
|
2479
3689
|
transports shed in exactly that order; it is pinned by a contract test that
|
|
2480
3690
|
derives the order from each implementation. Say so, and name the two ways
|
|
2481
3691
|
back: raise `max_chars` (it bounds both legs on this tool), or
|
|
@@ -2487,9 +3697,10 @@ shape every time, so two audits of two sites read the same way:
|
|
|
2487
3697
|
even for that floor, the plugin-served transport refuses with
|
|
2488
3698
|
`max_chars_too_small` and tells you the minimum to ask for — it never returns
|
|
2489
3699
|
a half-written payload.
|
|
2490
|
-
8. **Repeat the scope boundary.** Close with the response's `disclaimer`
|
|
2491
|
-
|
|
2492
|
-
nobody mistakes a clean audit for a clean SEO
|
|
3700
|
+
8. **Repeat the scope boundary.** Close with the response's `disclaimer`
|
|
3701
|
+
verbatim, INCLUDING its own statement that the list is not exhaustive, so
|
|
3702
|
+
nobody mistakes a clean audit for a clean SEO setup. On a search-visibility
|
|
3703
|
+
question, check indexability first — see the pitfall above.
|
|
2493
3704
|
|
|
2494
3705
|
Beyond that the prose is yours: group, summarise and prioritise however serves
|
|
2495
3706
|
the user. Rules 2, 4, 5 and 6 are the parts that must not vary.
|
|
@@ -2506,7 +3717,7 @@ WARNING · structure · multiple-h1 — page renders 2 h1s [open in bui
|
|
|
2506
3717
|
• Willkommen bei SunWest (Hero ▸ Headline) · headline
|
|
2507
3718
|
→ keep this one as the page's h1
|
|
2508
3719
|
• Unsere Standorte (Karten ▸ Panel "Standorte") · panel-title · "Unsere Standorte"
|
|
2509
|
-
→ Fix: element_update_settings({
|
|
3720
|
+
→ Fix: element_update_settings({ layout_id: "page:4816",
|
|
2510
3721
|
element_path: "/children/1/children/0", props: { title_element: "h2" } })
|
|
2511
3722
|
Why: exactly one h1 anchors the page's topic; several dilute it.
|
|
2512
3723
|
|
|
@@ -2515,7 +3726,9 @@ WARNING · content · images-missing-alt — 1 image without alt text
|
|
|
2515
3726
|
→ Fix: element_update_settings({ …, props: { image_alt: "<what it shows>" } })
|
|
2516
3727
|
Why: an empty alt makes the image invisible to screen readers and image search.
|
|
2517
3728
|
|
|
2518
|
-
|
|
3729
|
+
Not covered here, and not an exhaustive list: your CMS or SEO plugin owns meta
|
|
3730
|
+
title/description/OG, and whether search engines may index the page at all —
|
|
3731
|
+
check that with yootheme_builder_fetch_public_url({url, expect:"noindex"}).
|
|
2519
3732
|
```
|
|
2520
3733
|
|
|
2521
3734
|
**Worked example (tool-call snippet):**
|
|
@@ -2527,62 +3740,80 @@ yootheme_builder_advanced_read({
|
|
|
2527
3740
|
arguments: { checks: ["seo", "structure"] }
|
|
2528
3741
|
})
|
|
2529
3742
|
// A finding: { code: "missing-h1", category: "structure", severity: "warning",
|
|
2530
|
-
//
|
|
3743
|
+
// affected_pages: 3, pages: [{ id: "page:42", label: "About" }, ...],
|
|
2531
3744
|
// fix_hint: { tool: "element_update_settings",
|
|
2532
3745
|
// args_template: { props: { title_element: "h1" } }, note: "..." } }
|
|
2533
3746
|
|
|
2534
3747
|
// Step 3. Drill into the flagged page.
|
|
2535
|
-
yootheme_builder_template_summary({
|
|
3748
|
+
yootheme_builder_template_summary({ layout_id: "page:42" })
|
|
2536
3749
|
// heading_levels: { h1: 0, h2: 2, h3: 5, div: 1 }, images_missing_alt: 2
|
|
2537
3750
|
|
|
2538
3751
|
// Step 4. Fix that ONE page via the write tool the fix_hint named.
|
|
2539
3752
|
yootheme_builder_element_update_settings({
|
|
2540
|
-
|
|
3753
|
+
layout_id: "page:42",
|
|
2541
3754
|
element_path: "/children/0/children/0",
|
|
2542
3755
|
props: { title_element: "h1" },
|
|
2543
3756
|
etag: "abc123"
|
|
2544
3757
|
})
|
|
2545
3758
|
|
|
2546
3759
|
// Step 5. Republish; quick_check re-confirms this page is clean.
|
|
2547
|
-
yootheme_builder_page_publish({
|
|
3760
|
+
yootheme_builder_page_publish({ layout_id: "page:42", etag: "def456" })
|
|
2548
3761
|
// Response: { ..., quick_check: { clean: true } }
|
|
2549
3762
|
```
|
|
2550
3763
|
|
|
2551
3764
|
**Success criterion:** A re-run of `pages_audit` (or the `quick_check`
|
|
2552
3765
|
block on the next publish) no longer lists the fixed finding for that
|
|
2553
|
-
page, and the response's `summary.
|
|
2554
|
-
down.
|
|
3766
|
+
page, and the response's `summary.critical_finding_kinds` /
|
|
3767
|
+
`warning_finding_kinds` / `info_finding_kinds` totals go down. Those three
|
|
3768
|
+
count finding KINDS, not pages — `summary.affected_pages_summed_across_findings`
|
|
3769
|
+
is the page-level tally beside them.
|
|
2555
3770
|
|
|
2556
3771
|
---
|
|
2557
3772
|
|
|
2558
3773
|
## When something doesn't fit one of these 6 workflows
|
|
2559
3774
|
|
|
2560
3775
|
- **Move an element** (intra-template reorder/reparent): use
|
|
2561
|
-
`yootheme_builder_element_move({
|
|
3776
|
+
`yootheme_builder_element_move({ layout_id, element_path,
|
|
2562
3777
|
to_parent_path, to_index, etag })`. Reorders or reparents without
|
|
2563
3778
|
re-creating.
|
|
2564
3779
|
- **Delete an element**: use `yootheme_builder_element_delete({
|
|
2565
|
-
|
|
3780
|
+
layout_id, element_path, etag, confirm: true })`.
|
|
2566
3781
|
Elicitation-aware. Confirms via the AI client prompt before
|
|
2567
3782
|
destroying state when `confirm` is omitted. On hosts without
|
|
2568
3783
|
elicitation, it returns a preview-with-confirm-required response;
|
|
2569
3784
|
call again with `confirm: true`.
|
|
2570
3785
|
- **Unbind a source**: call through the gateway:
|
|
2571
|
-
`yootheme_builder_advanced({ tool: "yootheme_builder_element_unbind_source", arguments: {
|
|
3786
|
+
`yootheme_builder_advanced({ tool: "yootheme_builder_element_unbind_source", arguments: { layout_id, element_path, etag, confirm: true } })`.
|
|
2572
3787
|
Same elicitation flow as delete.
|
|
2573
3788
|
- **Orient in a large template before editing** (cheapest first read):
|
|
2574
|
-
`yootheme_builder_page_get_layout({
|
|
3789
|
+
`yootheme_builder_page_get_layout({ layout_id, mode: "outline" })`
|
|
2575
3790
|
returns a token-efficient INDENTED text tree — one line per node,
|
|
2576
|
-
`label ▸ type (bound: …) [sN]`.
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
3791
|
+
`label ▸ type (bound: …) [sN]`. **The handle `[sN]` is a PER-READ
|
|
3792
|
+
(state-scoped) reading alias only, never a write address — it is
|
|
3793
|
+
stable only at the etag you just read, and re-reads after ANY
|
|
3794
|
+
mutation (clone/move/delete/bind) get fresh handles.** The structured
|
|
3795
|
+
leg carries a SUMMARY (which sections are named, how many nodes each
|
|
3796
|
+
holds, the etag), not a path per node — for the actual write address
|
|
3797
|
+
(a RELATIVE `element_path`) call `yootheme_builder_element_list`,
|
|
3798
|
+
scoped to one named section with `root_path` from the outline's
|
|
3799
|
+
`named_sections` if you already know which chunk you want. For
|
|
3800
|
+
binding-focused work prefer `mode:"bindings_only"` instead — and read it as
|
|
3801
|
+
an inventory that checks exactly ONE thing. It compares every binding's
|
|
3802
|
+
SOURCE against the sources this site registers: a source that is gone gets
|
|
3803
|
+
`unresolved: true` on that binding and is named once in
|
|
3804
|
+
`unresolved_sources`, with an `unresolved_sources_note` beside it saying what
|
|
3805
|
+
to repair. That list is present but EMPTY when the comparison ran and found
|
|
3806
|
+
nothing, and is replaced
|
|
3807
|
+
by `unresolved_sources_not_checked` when the registered-source list could not
|
|
3808
|
+
be read at all — in that state an unmarked binding means nothing, so do not
|
|
3809
|
+
read it as a clean bill. What it does NOT check is the field VALUES inside a
|
|
3810
|
+
source that does resolve: a `props` value naming a field that does not exist
|
|
3811
|
+
looks exactly like one that does and will render blank. `element_get_binding`
|
|
3812
|
+
names those dead VALUES on one element; `pages_audit`'s `dangling-source`
|
|
3813
|
+
finding reports the same dead sources across the whole site, with paths.
|
|
2583
3814
|
- **Flat schema inspection** (e.g. enumerate every element path +
|
|
2584
|
-
type without fetching the whole nested tree): call
|
|
2585
|
-
|
|
3815
|
+
type without fetching the whole nested tree): call the L1 tool directly:
|
|
3816
|
+
`yootheme_builder_element_list({ layout_id })`.
|
|
2586
3817
|
- **Etag-only fetch** (e.g. polling for concurrent edits): use
|
|
2587
3818
|
`yootheme_builder_get_etag()` (takes no arguments) is cheaper than
|
|
2588
3819
|
fetching the full layout.
|
|
@@ -2593,13 +3824,26 @@ down.
|
|
|
2593
3824
|
`frontend_url_template` (with placeholders the user fills in).
|
|
2594
3825
|
- **Find out which site / install you are connected to**: call
|
|
2595
3826
|
`yootheme_builder_health` (Bearer-authenticated payload includes
|
|
2596
|
-
`site_url` + `home_url`) or `yootheme_builder_diagnose
|
|
3827
|
+
`site_url` + `home_url`), or `yootheme_builder_diagnose` for `site_url`
|
|
3828
|
+
alone. `active_site.site_id`, which rides on every answer, names the registry
|
|
3829
|
+
entry your own config picked — a different question from which install the
|
|
3830
|
+
plugin believes it is.
|
|
3831
|
+
- **Find WHERE `implode` directives sit on the site** (rows rendering as one
|
|
3832
|
+
comma-joined string, and you do not know which page): call through the
|
|
3833
|
+
read gateway
|
|
3834
|
+
`yootheme_builder_advanced_read({ tool: "yootheme_builder_layout_query", arguments: { has_implode_directives: true, max_paths: 50 } })`
|
|
3835
|
+
— no `layout_id`, so the sweep covers every page layout, and `paths[]`
|
|
3836
|
+
gives you the `layout_id` + `path` pairs to act on. The detection is the
|
|
3837
|
+
same one `inspect_multi_items_binding` reports as `has_implode_directives`,
|
|
3838
|
+
so opening a hit confirms it. Every count in that answer describes the
|
|
3839
|
+
matching subset, not the site.
|
|
2597
3840
|
- **Strip legacy `implode` directives** from an element binding (audit-clean
|
|
2598
3841
|
source props that pre-date the wrapper-source refactor): call through the
|
|
2599
3842
|
gateway:
|
|
2600
|
-
`yootheme_builder_advanced({ tool: "yootheme_builder_clean_implode_directives", arguments: {
|
|
3843
|
+
`yootheme_builder_advanced({ tool: "yootheme_builder_clean_implode_directives", arguments: { layout_id, element_path, etag } })`.
|
|
2601
3844
|
Returns the audit log + a fresh ETag; idempotent (`cleaned_count: 0` when
|
|
2602
|
-
there is nothing to remove).
|
|
3845
|
+
there is nothing to remove). It also removes EMPTY `implode` stubs, which
|
|
3846
|
+
the finder above does not report — an empty stub joins nothing.
|
|
2603
3847
|
- **Start from a pre-designed layout or element preset instead of
|
|
2604
3848
|
hand-building** (the user wants a "Home"/"Contact"/"Services" page, or a
|
|
2605
3849
|
ready-made Accordion/Alert preset, not a section built element-by-element):
|
|
@@ -2626,14 +3870,14 @@ layout root, then child 1 of that); a three-level path is
|
|
|
2626
3870
|
into the Nth child (0-based). This is the form `element_list` emits as
|
|
2627
3871
|
`rel_path`, and the form every worked example in this skill uses.
|
|
2628
3872
|
|
|
2629
|
-
- **Fully-qualified:** `/templates/<
|
|
3873
|
+
- **Fully-qualified:** `/templates/<layout_id>/layout/children/0/...` is the
|
|
2630
3874
|
same address with the template prefix. Read/write tools return the
|
|
2631
3875
|
fully-qualified pointer as `path`; you can pass it straight back.
|
|
2632
3876
|
|
|
2633
|
-
**What the tools EMIT:** `element_list`
|
|
3877
|
+
**What the tools EMIT:** `element_list` returns both `path`
|
|
2634
3878
|
(fully-qualified) and `rel_path` (the canonical `/children/0/...` form); every
|
|
2635
3879
|
write-tool response (`element_add`, `element_update_settings`, `element_clone`,
|
|
2636
|
-
`element_move`, `element_bind_source`, `
|
|
3880
|
+
`element_move`, `element_bind_source`, `page_generate_dynamic`, `scaffold_section`) and
|
|
2637
3881
|
`element_get` return the fully-qualified `path` plus the canonical `rel_path`.
|
|
2638
3882
|
Copy either straight back into the next call.
|
|
2639
3883
|
|
|
@@ -2653,6 +3897,58 @@ hand you.
|
|
|
2653
3897
|
layout node itself, which is not an addressable element. Use `""` for
|
|
2654
3898
|
"the top".
|
|
2655
3899
|
|
|
3900
|
+
**What is NOT an element address.** The grammar above is exhaustive: an
|
|
3901
|
+
element is the layout root plus zero or more `/children/<index>` steps. These
|
|
3902
|
+
tools refuse anything else rather than writing into it — `element_add`,
|
|
3903
|
+
`element_delete`, `element_move`, `element_clone`, `element_update_settings`,
|
|
3904
|
+
`element_set_status`, `element_bind_source`, `element_unbind_source`,
|
|
3905
|
+
`element_set_source`, `clean_implode_directives`, `page_generate_dynamic`,
|
|
3906
|
+
`library_insert` (both `element_path` and `sublayout.element_path`) and
|
|
3907
|
+
`library_save_as`. In particular these resolve to real JSON but are **not**
|
|
3908
|
+
elements:
|
|
3909
|
+
|
|
3910
|
+
- `…/children` — the LIST of a node's children, not a child. Drop the
|
|
3911
|
+
trailing `/children` to name the element that should contain the child.
|
|
3912
|
+
- `…/props` — an element's settings. Pass the element's own path; the tool
|
|
3913
|
+
writes into `props` for you. A props map that happens to carry a `type` key
|
|
3914
|
+
(`props: {"type":"primary"}` is an ordinary element prop) is still props.
|
|
3915
|
+
- `…/type` — a scalar member of an element, not the element. Any member other
|
|
3916
|
+
than `children` ends the element grammar; pass the element's own path.
|
|
3917
|
+
- `/templates/<id>` — the template record. Its element tree starts one
|
|
3918
|
+
segment further down, at `/templates/<id>/layout`.
|
|
3919
|
+
- `…/children/007` — a leading-zero or gapped reference-token. Array positions
|
|
3920
|
+
are canonical RFC-6901 §4 indices; anything else names no position.
|
|
3921
|
+
|
|
3922
|
+
The layout root itself (`parent_path: ""`, or `/templates/<id>/layout`) is a
|
|
3923
|
+
valid **parent** for `element_add` / `element_move`, and never a thing to
|
|
3924
|
+
`element_delete`, `element_move` or `element_clone` — deleting it takes the
|
|
3925
|
+
whole page with it. Each refusal names the offending argument and what to
|
|
3926
|
+
pass instead; nothing is changed. (`page_generate_dynamic` is the one exception
|
|
3927
|
+
on the root: it COPIES rather than mutates, so `element_path` may name the
|
|
3928
|
+
whole page.)
|
|
3929
|
+
|
|
3930
|
+
**`children` must be a JSON array.** `pages_create`, `page_save` and
|
|
3931
|
+
`element_add` refuse a `children` sent as an object with named keys — `{"0":…,"5":…}` — with
|
|
3932
|
+
`layout_children_not_a_list`. The Builder walks `children` by position, so a
|
|
3933
|
+
keyed object there renders in insertion order, makes every `/children/<index>`
|
|
3934
|
+
path address a different node than it reads, and can overwrite a sibling on the
|
|
3935
|
+
next append. A `{"0":…,"1":…}` object with contiguous keys is accepted: it
|
|
3936
|
+
re-encodes as a JSON array, which is what lands on disk either way.
|
|
3937
|
+
|
|
3938
|
+
Only the layout root must declare `children`. An empty section, row or
|
|
3939
|
+
column may omit the key — that is how the builder itself stores them
|
|
3940
|
+
(YOOtheme's save-transforms remove an empty `children`). A `children` value
|
|
3941
|
+
that is present must be a JSON array, never an object.
|
|
3942
|
+
|
|
3943
|
+
**Theme templates in `library_insert` / `library_save_as`.** Those two tools
|
|
3944
|
+
resolve their path against the template as stored, and a THEME template
|
|
3945
|
+
(a `pages_list` id with no `page:` / `article:` prefix) stores its tree one
|
|
3946
|
+
segment down. `element_path: ""` still means "the top of this template" and
|
|
3947
|
+
works on both kinds; a deeper path on a theme template starts at
|
|
3948
|
+
`/layout/children/<index>`. Pass a `page:<id>` / `article:<id>` path there and
|
|
3949
|
+
the refusal says so. (The `element_*` tools are unaffected — their `rel_path`
|
|
3950
|
+
is layout-relative for every template kind.)
|
|
3951
|
+
|
|
2656
3952
|
**Reading the index.** In `/children/0/children/2/children/1`: child 0 of the layout,
|
|
2657
3953
|
then child 2 of THAT, then child 1 of THAT. Indices are positional and shift
|
|
2658
3954
|
when siblings are added or removed; re-read after a structural mutation before
|
|
@@ -2660,10 +3956,12 @@ reusing a path (see the ETag handshake below).
|
|
|
2660
3956
|
|
|
2661
3957
|
**Don't hand-build deep paths.** Enumerate with
|
|
2662
3958
|
`yootheme_builder_element_list` (flat `{path, rel_path, element_type}`
|
|
2663
|
-
rows)
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
3959
|
+
rows) — copy `path` from a row there, never count `/children/`
|
|
3960
|
+
segments by eye on a big template. `yootheme_builder_page_get_layout({
|
|
3961
|
+
layout_id, mode: "outline" })` only ORIENTS (an indented
|
|
3962
|
+
`label ▸ type [sN]` tree plus a per-section node-count summary); scope
|
|
3963
|
+
`element_list` to one named chunk with `root_path` from the outline's
|
|
3964
|
+
`named_sections` when you already know which one you want.
|
|
2667
3965
|
|
|
2668
3966
|
## ETag handshake and self-correcting errors
|
|
2669
3967
|
|
|
@@ -2681,9 +3979,12 @@ carrying the real `path`). Copy the `path` from a row — never count
|
|
|
2681
3979
|
carrying `current_etag` — read it and retry.
|
|
2682
3980
|
|
|
2683
3981
|
So a write never leaves you guessing: on a lock error the response hands
|
|
2684
|
-
you the etag to use next. On
|
|
2685
|
-
|
|
2686
|
-
|
|
3982
|
+
you the etag to use next. On a `412` specifically, take that etag as
|
|
3983
|
+
permission to retry only AFTER re-reading — the site-wide hash does not
|
|
3984
|
+
cover per-page layouts, so the conflict may well be your own page under
|
|
3985
|
+
someone else's edit, and the error cannot tell the two apart. On success,
|
|
3986
|
+
`changed_paths` tells you exactly which addresses moved, so you can
|
|
3987
|
+
re-target without re-reading the whole tree.
|
|
2687
3988
|
|
|
2688
3989
|
**Self-correcting path errors.** When a path-addressed read cannot find
|
|
2689
3990
|
the element at the path you sent, the `element_not_found` error does NOT
|
|
@@ -2719,7 +4020,9 @@ to re-read the entire layout after a wrong-path error.
|
|
|
2719
4020
|
|
|
2720
4021
|
Every high-volume read tool takes `max_chars`. When a response does not fit,
|
|
2721
4022
|
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
|
|
4023
|
+
it tells you so. Read the verdict before you report a result as complete
|
|
4024
|
+
(one read caps a text FIELD instead — last paragraph, where these flags are
|
|
4025
|
+
absent):
|
|
2723
4026
|
|
|
2724
4027
|
- `truncated: true` — this response is not the whole answer.
|
|
2725
4028
|
- `omitted_count: N` — exactly how many entries were left out.
|
|
@@ -2730,28 +4033,30 @@ it tells you so. Read the verdict before you report a result as complete:
|
|
|
2730
4033
|
be rewound past the dropped entries is **removed, never left stale**, so a
|
|
2731
4034
|
`next_cursor` you receive is always safe to follow.
|
|
2732
4035
|
- `omitted_keys: [...]` — on a single-object response, which fields were shed
|
|
2733
|
-
(the small identifying ones like `
|
|
4036
|
+
(the small identifying ones like `layout_id` / `etag` are kept last).
|
|
2734
4037
|
- `retry_hint` — the concrete lever: a higher `max_chars`, a narrower
|
|
2735
4038
|
`fields[]` projection, or a smaller `limit`.
|
|
2736
4039
|
|
|
2737
|
-
The payload always parses —
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
`
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
4040
|
+
The payload always parses — in this shape the text leg is reduced entry by
|
|
4041
|
+
entry rather than cut mid-string. If you ever receive a response where
|
|
4042
|
+
**neither** leg parses, that is a bug — report it rather than working around it.
|
|
4043
|
+
|
|
4044
|
+
Here both legs carry the **same shortened payload**: reading `structuredContent`
|
|
4045
|
+
gives you exactly what the text leg already said, no more, so it is not a way
|
|
4046
|
+
around the cap. Decide whether an answer is whole from `truncated` /
|
|
4047
|
+
`omitted_count`, never from which leg you happened to read — and **absence of
|
|
4048
|
+
`truncated` means the answer is complete**, so do not hedge a full response with
|
|
4049
|
+
"there may be more".
|
|
4050
|
+
|
|
4051
|
+
**`page_get_layout(mode:"outline")` shortens differently, and none of the three
|
|
4052
|
+
rules above holds for it.** The cap bounds ONLY the outline text (both legs
|
|
4053
|
+
carry that same string); the summary beside it in `structuredContent` —
|
|
4054
|
+
`named_sections`, `total_elements`, `etag` — is never shortened. So here a
|
|
4055
|
+
bounded text leg DOES sit beside a complete structured one, the text is
|
|
4056
|
+
cut mid-string, not entry by entry, and the cut is announced by a trailing
|
|
4057
|
+
`--- TRUNCATED (N chars total, showing first M) ---` line, with **no
|
|
4058
|
+
`truncated` field and no `omitted_count`**. Read that marker; a missing flag
|
|
4059
|
+
means nothing here.
|
|
2755
4060
|
|
|
2756
4061
|
Two levers beat raising `max_chars`, because they reduce what is produced
|
|
2757
4062
|
rather than what survives: ask for fewer fields (`fields[]`) and fewer rows
|
|
@@ -3208,44 +4513,58 @@ skipped (no geocoding); an empty `map.height` becomes 300; a non-numeric
|
|
|
3208
4513
|
and make it data-driven — repeating over a source's items — WITHOUT
|
|
3209
4514
|
rebuilding it node by node.
|
|
3210
4515
|
|
|
3211
|
-
Use `
|
|
3212
|
-
byte-identically and swaps ONLY what you map, so the design
|
|
3213
|
-
styles, the `#parent` bindings) survives untouched.
|
|
4516
|
+
Use `yootheme_builder_page_generate_dynamic`. It copies the referenced
|
|
4517
|
+
layout byte-identically and swaps ONLY what you map, so the design
|
|
4518
|
+
(spacing, styles, the `#parent` bindings) survives untouched. It always
|
|
4519
|
+
creates a NEW, unpublished, unlinked page from the reference — the
|
|
4520
|
+
reference itself is never modified.
|
|
3214
4521
|
|
|
3215
4522
|
**Canonical tool-call sequence (snake_case):**
|
|
3216
4523
|
|
|
3217
4524
|
1. `yootheme_builder_get_etag()`: current lock.
|
|
3218
|
-
2. `yootheme_builder_template_summary({
|
|
3219
|
-
`yootheme_builder_page_get_layout({
|
|
4525
|
+
2. `yootheme_builder_template_summary({ layout_id })` (or
|
|
4526
|
+
`yootheme_builder_page_get_layout({ layout_id, mode: "outline" })`):
|
|
3220
4527
|
find the REFERENCE you will dynamize — a `section_name` (a named
|
|
3221
4528
|
section from the summary) or an `element_path`.
|
|
3222
4529
|
3. `yootheme_builder_sources_list()`: get the `list_source_name` to bind
|
|
3223
4530
|
(the published source that yields the items).
|
|
3224
|
-
4. `
|
|
4531
|
+
4. `yootheme_builder_page_generate_dynamic({ layout_id, output: "new_page", section_name: "<name>", list_source_name: "<source>", leaf_map: { "<original-leaf-field>": "<flat-flow-field>" }, etag })`.
|
|
3225
4532
|
|
|
3226
4533
|
**How the mapping works:**
|
|
3227
4534
|
|
|
3228
4535
|
- `leaf_map` keys are the ORIGINAL leaf field-names in the designed
|
|
3229
4536
|
section; values are the flat flow field they should now read. The
|
|
3230
|
-
`#parent` binding and any tuned props are kept.
|
|
4537
|
+
`#parent` binding and any tuned props are kept. A KEPT (unmapped) leaf
|
|
4538
|
+
whose original name is not a field the swapped source emits surfaces in
|
|
4539
|
+
the response's `summary.unmapped_kept_fields_missing_in_target` — map
|
|
4540
|
+
it or accept the empty render.
|
|
3231
4541
|
- A section with SEVERAL source blocks dynamizes in ONE call via
|
|
3232
4542
|
`source_map` — an array of `{ match, source_name, leaf_map }`, one
|
|
3233
4543
|
entry per block.
|
|
3234
4544
|
- For sub-query panels, key a `leaf_map` entry by
|
|
3235
4545
|
`"<subquery>.<leaf>"`.
|
|
3236
|
-
- `
|
|
3237
|
-
|
|
3238
|
-
|
|
4546
|
+
- `output` is REQUIRED — "new_page" is the only value available today; a
|
|
4547
|
+
future "in_place" value (editing the reference directly) is not yet
|
|
4548
|
+
available.
|
|
4549
|
+
- `page_generate_dynamic` requires `page.title`. It returns the page plus
|
|
4550
|
+
a `summary`, and `changed_paths` + `new_etag` like every other write.
|
|
4551
|
+
- With `dry_run:true`, a `would_change:false` answer carries a `reason` —
|
|
4552
|
+
most commonly "no bound exemplar element under the reference": the
|
|
4553
|
+
reference has nothing to swap because none of its leaves are bound yet.
|
|
4554
|
+
Bind one element first (`element_bind_source`), then retry.
|
|
3239
4555
|
|
|
3240
4556
|
**Common pitfalls:**
|
|
3241
4557
|
|
|
3242
4558
|
- **Binding Multi-Items on the container.** Bind the source + Multi-Items
|
|
3243
4559
|
on the `*_item` CHILD, never on the grid/list container — see the
|
|
3244
|
-
Multi-Items renderer truth table above. `
|
|
3245
|
-
rule for you when you hand it the right reference.
|
|
4560
|
+
Multi-Items renderer truth table above. `page_generate_dynamic` follows
|
|
4561
|
+
that rule for you when you hand it the right reference.
|
|
3246
4562
|
- **Hand-editing the JSON instead.** Don't reconstruct the section with
|
|
3247
4563
|
`element_add` calls to "make it dynamic" — that loses the design.
|
|
3248
|
-
`
|
|
4564
|
+
`page_generate_dynamic` is the supported one-call path.
|
|
4565
|
+
- **Expecting the reference page to change.** It never does — the result
|
|
4566
|
+
is always a NEW page. Publish the new page or copy its layout onto the
|
|
4567
|
+
reference yourself if you actually want an in-place swap.
|
|
3249
4568
|
- **Reusing a stale etag.** Feed `new_etag` from the response into your
|
|
3250
4569
|
next write.
|
|
3251
4570
|
|
|
@@ -3285,12 +4604,71 @@ apikey). See "WootsUp AI Layouts" below.
|
|
|
3285
4604
|
Browse, get, AND insert against the remote catalog (`source: "catalog"`,
|
|
3286
4605
|
the default) all depend on a YOOtheme Pro Library API key configured on the
|
|
3287
4606
|
connected site (the customer's own theme `apikey`, set once in the YOOtheme
|
|
3288
|
-
Customizer
|
|
3289
|
-
`library_browse` does not error
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
4607
|
+
Customizer, not something this MCP server manages). If it's missing,
|
|
4608
|
+
`library_browse` does not error: it returns `{ items: [], total: 0 }` with
|
|
4609
|
+
`api_key_configured: false` and a `detail_note`. Read that boolean rather
|
|
4610
|
+
than guessing from the count. **This apikey requirement applies ONLY to the
|
|
4611
|
+
remote catalog.** The local AI-Layouts store (next subsection) needs no apikey
|
|
4612
|
+
at all.
|
|
4613
|
+
|
|
4614
|
+
**Do NOT tell the customer to check the apikey field until you know that is the
|
|
4615
|
+
problem.** `api_key_configured: false` means no key could be READ, and the
|
|
4616
|
+
reader answers that for five different situations. One of them is OUR lookup
|
|
4617
|
+
missing the row rather than anything the customer did, and it needs the
|
|
4618
|
+
opposite action. Run `yootheme_builder_diagnose` and read `library_key.state`:
|
|
4619
|
+
|
|
4620
|
+
| `state` | who has to act |
|
|
4621
|
+
|---|---|
|
|
4622
|
+
| `key_present` | nobody. The key is readable, so an empty catalog is a different problem. |
|
|
4623
|
+
| `update_site_without_key` (Joomla) | the customer, in the YOOtheme Customizer theme settings. |
|
|
4624
|
+
| `theme_config_empty` (WordPress) | the customer, same place. |
|
|
4625
|
+
| `theme_config_unavailable` (WordPress) | nobody yet: YOOtheme is not loaded at all. Check the theme is active, then run `yootheme_builder_health`. |
|
|
4626
|
+
| `update_site_not_found` (Joomla) | **nobody, in the key field.** No `pkg_yootheme` update site matched, so the Customizer would show that field empty too. Report the state instead of sending the customer to a field that cannot fix it. |
|
|
4627
|
+
|
|
4628
|
+
`library_key.hint` carries the same verdict as a sentence you can pass on, and
|
|
4629
|
+
`library_key.configured` is the boolean. The key itself is never in the payload.
|
|
4630
|
+
|
|
4631
|
+
**A REFUSED catalog fetch is NOT an empty catalog.** When the site HAS a key
|
|
4632
|
+
and the YOOtheme Pro Library rejects it (measured: HTTP 401 "Invalid API Key"),
|
|
4633
|
+
`library_browse` and `library_get` answer `error_code:
|
|
4634
|
+
"library_upstream_unavailable"` (HTTP 503) carrying `upstream_status` and
|
|
4635
|
+
`api_key_configured`. Do NOT respond to it by dropping filters, trying another
|
|
4636
|
+
id, or calling `library_get` to "verify" the id: nothing you send repairs a key
|
|
4637
|
+
the provider refuses, and `library_get` is refused by the same upstream. Only
|
|
4638
|
+
the site owner can take it up with YOOtheme. Meanwhile the keyless `source: "ai"`
|
|
4639
|
+
store and `page_get_layout` still work. A successful `total: 0` is a different
|
|
4640
|
+
thing entirely: the catalog was read and matched nothing, and that is now the
|
|
4641
|
+
only thing an empty page can mean.
|
|
4642
|
+
|
|
4643
|
+
**Browse can succeed while get and insert fail, and that is normal, not a
|
|
4644
|
+
bug.** The LIST endpoint the browse call uses answers even with NO key at all;
|
|
4645
|
+
the DETAIL endpoint (`/v1/library/layout/<id>`) that `library_get` and
|
|
4646
|
+
`library_insert` need CHECKS the key. Measured on two independent installs:
|
|
4647
|
+
`api.yootheme.com` refused those installs' configured key on every
|
|
4648
|
+
key-checking endpoint, the detail endpoint included, while browsing kept
|
|
4649
|
+
working normally. So "I can browse hundreds of layouts but every `library_get`
|
|
4650
|
+
fails" is a REFUSED KEY, not a bad id and not a broken plugin.
|
|
4651
|
+
|
|
4652
|
+
**Say what was measured, and stop there.** The refusal answers
|
|
4653
|
+
`library_upstream_unavailable` (HTTP 503) with `upstream_status: 401` and
|
|
4654
|
+
`api_key_configured: true`, and the honest report is exactly that: *the
|
|
4655
|
+
YOOtheme Pro Library refused this site's API key, so the call could not be
|
|
4656
|
+
made.* Then stop re-trying ids, because no id repairs a refused key.
|
|
4657
|
+
|
|
4658
|
+
**Do NOT tell the customer WHY it was refused.** You cannot see why. A 401 is
|
|
4659
|
+
the same 401 for a plan that does not cover the endpoint, an expired key, a
|
|
4660
|
+
key that was replaced, a domain or host restriction, and a change on
|
|
4661
|
+
YOOtheme's side. If the customer asks what to check, offer those as
|
|
4662
|
+
POSSIBILITIES and point them at their YOOtheme account; never present one as
|
|
4663
|
+
the diagnosis. Telling a paying customer their plan is the reason is a claim
|
|
4664
|
+
about their account that we have not established and cannot withdraw once the
|
|
4665
|
+
assistant has said it. Never phrase it as a defect in their site either.
|
|
4666
|
+
|
|
4667
|
+
**`library_item_unavailable` (404) is a DIFFERENT state.** It means the item
|
|
4668
|
+
could not be fetched at all: no Library API key is configured on the site, or
|
|
4669
|
+
the id is not in the catalog. A site whose key is configured and refused gets
|
|
4670
|
+
the 503 above, never this 404. Reading them as the same thing sends a customer
|
|
4671
|
+
who HAS a key off to configure one.
|
|
3294
4672
|
|
|
3295
4673
|
### WootsUp AI Layouts — your OWN local library (no apikey, save → reuse)
|
|
3296
4674
|
|
|
@@ -3564,8 +4942,16 @@ at fetch time, never persisted raw):
|
|
|
3564
4942
|
the rest of the insert.
|
|
3565
4943
|
|
|
3566
4944
|
The insert response's `images_resolved: { downloaded, placeholders }`
|
|
3567
|
-
tells you
|
|
3568
|
-
|
|
4945
|
+
tells you HOW MANY of each — check it rather than assuming every image
|
|
4946
|
+
round-tripped. It does not tell you WHICH, and a count on its own is not
|
|
4947
|
+
actionable: `image_import_failures` is. That field appears only when a
|
|
4948
|
+
curated insert actually failed to fetch something, and carries one entry
|
|
4949
|
+
per lost image with the `prop_path` inside the fetched item (e.g.
|
|
4950
|
+
`children[0].props.image`), the `exception_class`, and a `message`. Read it
|
|
4951
|
+
before reporting the insert as finished: a failure is often transient, so a
|
|
4952
|
+
second `library_insert` — or a searched-and-imported photo bound into that
|
|
4953
|
+
exact prop path — usually fixes it. Absent means nothing failed; on
|
|
4954
|
+
`images_mode: "as_saved"` or `"placeholder"` the curate step never ran.
|
|
3569
4955
|
|
|
3570
4956
|
### Fill image slots with real on-topic photos (search → import → bind)
|
|
3571
4957
|
|
|
@@ -3573,9 +4959,13 @@ The professionalism signal is on-brand IMAGERY (see "Make it the CUSTOMER's bran
|
|
|
3573
4959
|
|
|
3574
4960
|
**The flow (three tools):**
|
|
3575
4961
|
1. `yootheme_builder_image_search({ source, query, orientation })` returns candidate rows, each with a `full_url` (the image to import) plus dimensions and author. `source`: `unsplash` or `pexels` for real photography (the primary rich sources); `proimages` for YOOtheme's own curated decorative and background assets. Use `orientation: "landscape"` for heroes and banners, `"portrait"` for tall cards.
|
|
4962
|
+
A search that could NOT be carried out is an ERROR, not an empty shelf: it comes back with `error_code: "image_search_unavailable"` (HTTP 503). Do NOT respond to it by broadening the query. It means the provider refused the request or never answered, most often an HTTP 401 on the site's YOOtheme Pro Library API key (`upstream_status: 401`), which only the site owner can resolve. When that happens, stop searching, keep honest placeholders, and tell the user what `error` said. A SUCCESSFUL `total: 0` is a different thing entirely and DOES mean the query matched nothing; `api_key_configured` tells you whether the key gate was even open.
|
|
4963
|
+
**`api_key_configured: true` plus a 401 is a real, ordinary state: the site HAS a key and the upstream turned it down for this call.** That is the whole of what the two fields establish, and it is not a defect in the plugin or in the site. A YOOtheme Pro Library API key can be perfectly well configured and still be REFUSED. Measured on two independent installs: with such a key, EVERY key-checking endpoint at `api.yootheme.com` refused it, including `/v1/library/layout/<id>`, the DETAIL endpoint `library_get` and `library_insert` depend on. The LIST endpoint behind `library_browse` answers even WITHOUT a key, which is why browsing keeps working and only the detail reads and searches fail. So the honest reading of "browse fine, get and search refused" is: **the upstream refuses this site's key** for those calls. Report exactly that, and stop. **What you must NOT do is name the reason.** Whether it is the subscription behind the key, an expired or replaced key, a domain restriction, or something changed at YOOtheme, is not visible from here, and all of them look like the same 401. If the customer wants to act on it, list those as POSSIBILITIES to check in their YOOtheme account, never as a finding. Telling a paying customer their subscription is short when it is not is not a mistake you get to take back. Do NOT report it as a defect, do NOT retry with broader queries, and do NOT tell them the plugin is broken. Image search works on WordPress and Joomla alike.
|
|
3576
4964
|
2. JUDGE the candidates by FIT and TONE, exactly like you choose a section, and never grab the first result. Search deliberately by the brief's INDUSTRY plus TONE: a coffee roaster searches `"coffee roasting"` / `"espresso pour"` / `"cafe interior"` (warm, editorial); a law firm searches `"modern office architecture"` (clean, restrained). A generic `query: "business"` returns generic stock that reads as filler.
|
|
3577
|
-
3. `yootheme_builder_image_import({ url: "<full_url>" })` sideloads the chosen photo into the site's OWN Media Library and returns `{ local_url }`. Only Unsplash, Pexels, and YOOtheme image hosts can be imported (a security allowlist); other URLs are rejected.
|
|
3578
|
-
|
|
4965
|
+
3. `yootheme_builder_image_import({ url: "<full_url>" })` sideloads the chosen photo into the site's OWN Media Library and returns `{ local_url }`. The argument is also accepted under the name `full_url`, so a row from step 1 can be passed on unchanged — give ONE of the two, never both with different values. Only Unsplash, Pexels, and YOOtheme image hosts can be imported (a security allowlist); other URLs are rejected.
|
|
4966
|
+
**`local_url` has a PLATFORM-SPECIFIC shape, and both are correct.** WordPress returns a root-relative path WITH a leading slash (`/wp-content/uploads/2026/07/hero.jpg`); Joomla returns a JPATH_ROOT-relative path WITHOUT one (`images/yootheme/hero.jpg`, the same shape YOOtheme's own image dialog stores). Bind whatever came back VERBATIM. Adding a slash on Joomla or stripping one on WordPress produces a path the layout cannot resolve, and the element then renders empty with no error anywhere. Importing the same photo twice returns the FIRST attachment instead of creating a duplicate, as long as the earlier copy is still in the site's current upload folder (on WordPress that is `wp-content/uploads/YYYY/MM/`, so a copy imported in an earlier month is not matched and a second attachment is created).
|
|
4967
|
+
A failure never throws: it comes back as `{ imported: false, reason, failure }`. Read `failure` before deciding what to do next. `failure: "rejected"` means THIS url did not import (blocked host, upstream error, the CMS refused the file type), so picking a different candidate is the right move. `failure: "unavailable"` means the SITE cannot import any image right now (no media runtime, no HTTP transport), so stop importing, keep the placeholder, and tell the user what the `reason` said. Trying twenty more photos against an `unavailable` site only wastes the user's time.
|
|
4968
|
+
4. Bind it: `yootheme_builder_element_update_settings({ layout_id, element_path, props: { image: "<local_url>" }, merge: true })`.
|
|
3579
4969
|
|
|
3580
4970
|
**Real photo vs honest placeholder:**
|
|
3581
4971
|
- A real, ON-TOPIC photo beats a placeholder every time; it is the single biggest lift from "template" to "finished". Fill the hero and feature-section image slots with searched photos that match the brief.
|
|
@@ -3597,7 +4987,9 @@ The most common way an AI-built page falls short of a real Pro layout: it defaul
|
|
|
3597
4987
|
1. Compose from the real Library sections FIRST. The section corpus (`library_browse({kind:"section"})`) is about 2000 individually-designed, already-rich sections across every demo. Selecting well among them for fit, tone, and variety, and preserving their design, is the primary path to a varied Pro-quality page. Do NOT strip their rich features (below).
|
|
3598
4988
|
2. When you hand-build a section (no corpus section fits, or you build from native elements), reach into the palette below and pick the rich pattern that fits. Never the bare-minimum version. Vary your choices across the page.
|
|
3599
4989
|
|
|
3600
|
-
**The polish features Pro sections use constantly (these ARE what "rich" means): preserve them when composing, apply them when hand-building. The `panel` props named below are VERIFIED against the LIVE YOOtheme Pro 5.x `panel` schema (`element_type_get_schema("panel")` → 81 fields). `element_type_get_schema` is the source of truth — always confirm the exact key AND its enum values there before writing; a guessed key is
|
|
4990
|
+
**The polish features Pro sections use constantly (these ARE what "rich" means): preserve them when composing, apply them when hand-building. The `panel` props named below are VERIFIED against the LIVE YOOtheme Pro 5.x `panel` schema (`element_type_get_schema("panel")` → 81 fields). `element_type_get_schema` is the source of truth — always confirm the exact key AND its enum values there before writing; a guessed key is stored and reported back under `warnings` (`code:
|
|
4991
|
+
"unknown_prop_keys"`) rather than refused, and a guessed VALUE either
|
|
4992
|
+
no-ops silently or, on an enum field, is refused with 422.**
|
|
3601
4993
|
- **Card styling — THE single biggest "flat vs rich" lever: `panel_style` on a `panel`.** A bare `panel` (or a plain column of text) is what makes a section look flat; a `panel_style` turns it into a real styled CARD. Verified enum: `card-default` / `card-primary` / `card-secondary` / `card-hover` (hover-lift card) / `card-overlay` / `tile-default` / `tile-muted` / `tile-primary` / `tile-secondary`. A feature / team / pricing "card" is a `panel` WITH a `panel_style`, not three bare text columns.
|
|
3602
4994
|
- **Hover** (on a `panel`; the image-hover props require a `link` + `panel_link`/`image_link`, per the schema `enable` rules): `hover_image` (swap the image on hover), `image_transition: scale-up`|`scale-down` (image zoom on hover), `title_hover_style: reset`|`heading`. `panel_style: card-hover` also gives a hover lift on its own.
|
|
3603
4995
|
- **Depth**: `image_box_shadow` (`small`…`xlarge`) and `image_box_decoration` (`default`/`primary`/`secondary`/`shadow`/`mask`) — these apply to a panel image only when NO `panel_style` is set (they are mutually gated: use a `panel_style` OR the box-shadow/decoration, not both).
|
|
@@ -3631,7 +5023,7 @@ ones are legal depends on `kind`:
|
|
|
3631
5023
|
| `target` shape | Accepts `kind: "layout"` | Accepts `kind: "element"` (preset) |
|
|
3632
5024
|
|---|---|---|
|
|
3633
5025
|
| `{ new_page: { title, status? } }` | ✓ creates a page, layout's sections become its content | ✗ rejected — a preset is not a page |
|
|
3634
|
-
| `{
|
|
5026
|
+
| `{ layout_id, element_path? }` | ✓ ONLY the layout ROOT is section-accepting (`element_path: ""` or omitted) | ✓ ONLY a **Column** — every other node type is rejected |
|
|
3635
5027
|
| `{ sublayout: { template_id, element_path } }` | ✓ the fetched layout's sections are stripped to bare ROWS first (a sublayout/fragment accepts rows, not sections) | ✗ rejected — a sublayout is not a Column |
|
|
3636
5028
|
|
|
3637
5029
|
**A preset (`kind: "element"`) MUST target a Column. There is no
|
|
@@ -3646,12 +5038,13 @@ first, then point `element_path` at ONE of the returned `column_paths`:
|
|
|
3646
5038
|
yootheme_builder_library_insert({
|
|
3647
5039
|
id: "8cf55920",
|
|
3648
5040
|
kind: "element",
|
|
3649
|
-
target: { template_id: "home", element_path: "" }
|
|
5041
|
+
target: { template_id: "home", element_path: "" },
|
|
5042
|
+
etag: "<etag>"
|
|
3650
5043
|
})
|
|
3651
5044
|
// → 422 target_not_a_column: 'target at "/" is a "layout"'.
|
|
3652
5045
|
|
|
3653
5046
|
// Fix: scaffold a section->row->column hull, then target the new Column.
|
|
3654
|
-
yootheme_builder_scaffold_section({
|
|
5047
|
+
yootheme_builder_scaffold_section({ layout_id: "home", columns: 1, etag: "<etag>" })
|
|
3655
5048
|
// Response includes column_paths: ["/children/4/children/0/children/0"]
|
|
3656
5049
|
|
|
3657
5050
|
yootheme_builder_library_insert({
|
|
@@ -3670,6 +5063,63 @@ separate strip step. Point `element_path` at the actual sublayout/
|
|
|
3670
5063
|
fragment node itself, not its parent — pointing at the wrong level is a
|
|
3671
5064
|
`422 target_not_a_sublayout`.
|
|
3672
5065
|
|
|
5066
|
+
**The table above is about the TARGET. The ITEM is checked too.** The
|
|
5067
|
+
composed result — the preset, or the layout's sections, or the stripped
|
|
5068
|
+
rows — goes through the SAME layout grammar `page_save` runs, before any
|
|
5069
|
+
image is imported. Two refusals come out of that, and neither is about
|
|
5070
|
+
where you pointed:
|
|
5071
|
+
|
|
5072
|
+
- `422 layout_invalid_hierarchy` — what would be stored is a shape the
|
|
5073
|
+
Page Builder never builds. The common case is a spine node saved as a
|
|
5074
|
+
preset: a Row or a Column put into your own library with
|
|
5075
|
+
`library_save_as` comes back classified as an element, and a Row inside
|
|
5076
|
+
a Column is refused. So is a whole layout spliced into a Column, and so
|
|
5077
|
+
is a **sublayout fragment at a page or layout root** — 67 of the shipped
|
|
5078
|
+
catalog layouts are fragments (the dropdown, search and dialog groups),
|
|
5079
|
+
served under `kind: "layout"` like any other layout. **The one
|
|
5080
|
+
destination that accepts them is a `sublayout` target — never a
|
|
5081
|
+
Column.** The Column branch of the target table only ever opens for
|
|
5082
|
+
`kind: "element"` presets; pointed at a Column, one of these fails
|
|
5083
|
+
before the hierarchy check even runs, with
|
|
5084
|
+
`422 target_not_section_accepting`, and that answer's own hint suggests
|
|
5085
|
+
the layout root or a `new_page` target — both refuse it too, for the
|
|
5086
|
+
same `layout_invalid_hierarchy` reason as the root case above. For this
|
|
5087
|
+
one item type, skip that suggestion and go straight to
|
|
5088
|
+
`target: { sublayout: { template_id, element_path } }`.
|
|
5089
|
+
- `422 library_item_kind_mismatch` — the item carries a different `kind`
|
|
5090
|
+
than the call declared. The declared kind alone decides which branch
|
|
5091
|
+
composes the item, so a layout handed over as `kind: "element"` would go
|
|
5092
|
+
in through the preset branch. The answer names both kinds; retry with
|
|
5093
|
+
the one the item carries. Items from your own library carry their own
|
|
5094
|
+
classification; a catalog id is looked up per kind, so there a wrong
|
|
5095
|
+
`kind` is already a `404 library_item_unavailable`.
|
|
5096
|
+
|
|
5097
|
+
Both are raised BEFORE the write and before any image import, so a
|
|
5098
|
+
refused insert stores nothing and imports nothing.
|
|
5099
|
+
|
|
5100
|
+
### `insert_mode` — and the confirm gate that guards its default
|
|
5101
|
+
|
|
5102
|
+
An `existing`/`sublayout` target already has children whenever you insert
|
|
5103
|
+
into a page you (or a customer) already built. `insert_mode` decides what
|
|
5104
|
+
happens to them, mirroring the three choices the Library dialog itself
|
|
5105
|
+
offers:
|
|
5106
|
+
|
|
5107
|
+
| `insert_mode` | effect |
|
|
5108
|
+
|---|---|
|
|
5109
|
+
| `"replace"` (default) | DROPS the target's existing children; the composed content becomes the whole list |
|
|
5110
|
+
| `"top"` | prepends the composed content before the existing children |
|
|
5111
|
+
| `"bottom"` | appends the composed content after the existing children |
|
|
5112
|
+
|
|
5113
|
+
**`"replace"` on a non-empty target answers with a PREVIEW instead of
|
|
5114
|
+
writing, unless you pass `confirm: true`.** The preview reports
|
|
5115
|
+
`preview: true`, `applied: false`, `requires_confirmation: true`, and —
|
|
5116
|
+
so you know exactly what would be lost — `would_remove_count` (how many
|
|
5117
|
+
children) and `would_remove_types` (their `type`s, in order). Nothing is
|
|
5118
|
+
written on that call; re-send the SAME request with `confirm: true` to
|
|
5119
|
+
replace anyway, or switch to `insert_mode: "top"` / `"bottom"` to keep
|
|
5120
|
+
the existing content instead. An ALREADY-EMPTY target never needs
|
|
5121
|
+
`confirm`, whatever mode you pick — there is nothing there to lose.
|
|
5122
|
+
|
|
3673
5123
|
### Adapting a Library insert is STATIC content editing, not dynamizing
|
|
3674
5124
|
|
|
3675
5125
|
**Every Library layout and preset is authored STATIC** — no source
|
|
@@ -3683,7 +5133,7 @@ section.
|
|
|
3683
5133
|
an archive-style listing) is a deliberate, separate follow-up call, not
|
|
3684
5134
|
something insert does for you.** Use the existing dynamize workflow
|
|
3685
5135
|
documented in "Dynamize an existing section" just above this one
|
|
3686
|
-
(`
|
|
5136
|
+
(`yootheme_builder_page_generate_dynamic`) to re-target a Library-inserted
|
|
3687
5137
|
section's leaf fields onto a Source, or bind a Multi-Items container
|
|
3688
5138
|
directly with `yootheme_builder_element_bind_source` (Workflow 2) if the
|
|
3689
5139
|
inserted item already has a grid/list container you want to re-point at
|
|
@@ -3705,7 +5155,7 @@ live data instead of its demo items.
|
|
|
3705
5155
|
- **Stuffing placeholder copy into a field you have nothing for**, to
|
|
3706
5156
|
defeat collapsing. Leave it empty; collapsing is the intended behavior.
|
|
3707
5157
|
- **Expecting `library_insert` to make anything dynamic.** It composes
|
|
3708
|
-
STATIC content only — dynamizing is the separate `
|
|
5158
|
+
STATIC content only — dynamizing is the separate `page_generate_dynamic` /
|
|
3709
5159
|
`element_bind_source` call.
|
|
3710
5160
|
|
|
3711
5161
|
**Worked example (browse → get → insert → adapt):**
|
|
@@ -3735,7 +5185,7 @@ yootheme_builder_library_insert({
|
|
|
3735
5185
|
images: "placeholder",
|
|
3736
5186
|
etag: "<etag>"
|
|
3737
5187
|
})
|
|
3738
|
-
// Response: {
|
|
5188
|
+
// Response: { layout_id: "home", inserted_path: "/children/3", element_count: 12,
|
|
3739
5189
|
// images_mode: "placeholder", images_resolved: { downloaded: 0, placeholders: 6 },
|
|
3740
5190
|
// new_etag: "<fresh>", frontend_url: null }
|
|
3741
5191
|
|
|
@@ -3743,7 +5193,7 @@ yootheme_builder_library_insert({
|
|
|
3743
5193
|
// inserted section back first (page_get_layout / element_get) to find the
|
|
3744
5194
|
// real headline's element_path, then edit CONTENT only — style untouched.
|
|
3745
5195
|
yootheme_builder_element_update_settings({
|
|
3746
|
-
|
|
5196
|
+
layout_id: "home",
|
|
3747
5197
|
element_path: "/children/3/children/0/children/0",
|
|
3748
5198
|
props: { content: "<customer's real headline>" },
|
|
3749
5199
|
merge: true,
|
|
@@ -3820,62 +5270,69 @@ perform.
|
|
|
3820
5270
|
|
|
3821
5271
|
<!-- TOOL-CATALOG:BEGIN -->
|
|
3822
5272
|
|
|
3823
|
-
**
|
|
5273
|
+
**60 catalogued tools** plus the `yootheme_builder_advanced` (write) + `yootheme_builder_advanced_read` (read-only) gateways = **62 callable** end to end, of which **49 are advertised in `tools/list`** (45 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.
|
|
3824
5274
|
|
|
3825
5275
|
| Tool | Kind | Input keys | Description |
|
|
3826
5276
|
| --- | --- | --- | --- |
|
|
3827
|
-
| `yootheme_builder_clean_implode_directives` | destructive | `confirm`, `element_path`, `etag`, `
|
|
3828
|
-
| `yootheme_builder_diagnose` | read+idempotent | `site_id` |
|
|
3829
|
-
| `yootheme_builder_element_add` |
|
|
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
|
|
3831
|
-
| `yootheme_builder_element_clone` |
|
|
3832
|
-
| `yootheme_builder_element_delete` | destructive | `confirm`, `element_path`, `etag`, `
|
|
3833
|
-
| `yootheme_builder_element_get` | read+idempotent | `element_path`, `fields`, `
|
|
3834
|
-
| `yootheme_builder_element_get_binding` | read+idempotent | `element_path`, `
|
|
3835
|
-
| `yootheme_builder_element_list` | read+idempotent | `cursor`, `depth`, `fields`, `include_meta`, `limit`, `max_chars`, `root_path`, `site_id
|
|
3836
|
-
| `yootheme_builder_element_move` | idempotent | `element_path`, `etag`, `
|
|
3837
|
-
| `yootheme_builder_element_set_source` | idempotent | `element_path`, `etag`, `site_id`, `source`, `source_extended
|
|
3838
|
-
| `yootheme_builder_element_set_status` | idempotent | `element_path`, `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:
|
|
3840
|
-
| `yootheme_builder_element_types_list` | read+idempotent | `fields`, `include_meta`, `site_id` | List element types registered on this site (built-ins + YOOessentials
|
|
3841
|
-
| `yootheme_builder_element_unbind_source` | destructive | `confirm`, `element_path`, `etag`, `
|
|
3842
|
-
| `yootheme_builder_element_update_settings` | idempotent | `element_path`, `etag`, `merge`, `name`, `props`, `site_id`, `
|
|
3843
|
-
| `
|
|
3844
|
-
| `
|
|
3845
|
-
| `
|
|
3846
|
-
| `
|
|
3847
|
-
| `
|
|
3848
|
-
| `
|
|
3849
|
-
| `
|
|
3850
|
-
| `
|
|
3851
|
-
| `
|
|
3852
|
-
| `
|
|
3853
|
-
| `
|
|
3854
|
-
| `
|
|
3855
|
-
| `
|
|
3856
|
-
| `
|
|
3857
|
-
| `
|
|
3858
|
-
| `
|
|
3859
|
-
| `
|
|
3860
|
-
| `
|
|
3861
|
-
| `
|
|
3862
|
-
| `
|
|
3863
|
-
| `
|
|
3864
|
-
| `
|
|
3865
|
-
| `
|
|
3866
|
-
| `
|
|
3867
|
-
| `
|
|
3868
|
-
| `
|
|
3869
|
-
| `
|
|
3870
|
-
| `
|
|
3871
|
-
| `
|
|
3872
|
-
| `
|
|
3873
|
-
| `
|
|
5277
|
+
| `yootheme_builder_clean_implode_directives` | write+destructive+idempotent | `confirm`, `element_path`, `etag`, `layout_id`, `site_id` | Use when a Multi-Items element renders rows as ONE comma-joined string instead of N repeated items — stale `implode` directives left by a plugin upgrade (detect via inspect_multi_items_binding). Destructive: the binding can't be restored without re-mapping. The clean requires confirm:true + ETag; the dry run neither. Without `confirm` the call runs a server-side DRY RUN and reports `would_clean_count` while writing nothing. Live at once. |
|
|
5278
|
+
| `yootheme_builder_diagnose` | read+idempotent | `site_id` | Why a call was refused: `auth_bound` (key accepted), `etag_ok` (layout readable), `site_url` (WHICH install; a key valid elsewhere fails here), `capability_ok` + `capability_warning` (the CMS user has no `can_edit`, so no blind 403). Install facts: health. |
|
|
5279
|
+
| `yootheme_builder_element_add` | write+non-idempotent | `children`, `element_type`, `etag`, `index`, `layout_id`, `name`, `parent_path`, `position`, `props`, `site_id`, `type_name` | Add a new element to a template. Provide `parent_path` (or "" for root), `element_type`, and optional `props`/`children`. Content/leaf elements may only be added inside a `column`; at the root only `section` — else call scaffold_section for a valid column path. Inserting renumbers later siblings: `renumbered_siblings` names each moved sibling address (absent if none). Requires ETag. Live at once in the stored tree — already save-transformed (defaults filled, empty children removed). Stage hidden with element_set_status(status:false). Prop names are snake_case. Common traps: `content` (not `title`) on button_item/headline/list_item/quotation/text; check `element_type_get_schema` first. The answer carries no `props`, only the new address. The stored node also carries the element's defaults — read it back with element_get. |
|
|
5280
|
+
| `yootheme_builder_element_bind_source` | write+destructive+idempotent | `arguments`, `bindingLevel`, `confirm`, `directives`, `element_path`, `etag`, `field`, `field_mappings`, `layout_id`, `probe`, `query_arguments`, `relation`, `site_id`, `source_id`, `source_name` | Bind 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); leaf under a #parent fragment → INHERIT (`__node_item__`); a standalone query renders empty. Map: bind `map_item`, map `location` to a "lat,lng" field or no markers. Requires ETag. Live at once. CONVENIENCE half of a pair — plain field names, but it REPLACES `source.props` whole, never merges; element_set_source is the CAPABILITY half. A re-bind that would drop anything already bound answers with a preview until you repeat it with `confirm: true`. To remove a binding, call element_unbind_source. |
|
|
5281
|
+
| `yootheme_builder_element_clone` | write+non-idempotent | `element_path`, `etag`, `layout_id`, `site_id`, `to_index`, `to_parent_path` | Clone an element. Default: as a sibling right after the source; pass `to_parent_path` to drop the copy elsewhere in the SAME template instead. Renumbers later siblings: `renumbered_siblings` names each moved sibling address (absent if none). Requires ETag. Live at once — stage hidden with element_set_status(status:false). |
|
|
5282
|
+
| `yootheme_builder_element_delete` | write+destructive+non-idempotent | `confirm`, `element_path`, `etag`, `layout_id`, `site_id` | PERMANENTLY delete an element + children. Cannot be undone. Pass `confirm: true` AND a fresh ETag. Preview: neither. A real delete returns `deleted:true` and NO `requires_confirmation` key; a returned `requires_confirmation:true` means NOTHING was deleted. `renumbered_siblings` names each moved sibling address (absent if none). Live at once. Hide instead: element_set_status(status:false). |
|
|
5283
|
+
| `yootheme_builder_element_get` | read+idempotent | `element_path`, `fields`, `layout_id`, `max_chars`, `site_id` | Get the full element object at a JSON-Pointer path, including props and a `children_count` (not the children — use element_list with root_path). On a `module`/`module_position` element it also returns `resolved` (the referenced widget/module row) plus `resolved_reason` when it could not be resolved. `max_chars` bounds the text output. |
|
|
5284
|
+
| `yootheme_builder_element_get_binding` | read+idempotent | `element_path`, `layout_id`, `max_chars`, `site_id` | Read the source binding on an element. Returns {layout_id, element_path, binding, has_binding, bound_via_item, item_binding?}: on a multi-item container (grid/slideshow/accordion/...) the binding sits on the `*_item` CHILD, so `has_binding:false` there does NOT mean static — read `bound_via_item`/`item_binding` first. Site-wide REVERSE question (which elements hang on a source): yootheme_builder_layout_query (gateway-only), OMIT layout_id. To remove what this returns, call element_unbind_source. |
|
|
5285
|
+
| `yootheme_builder_element_list` | read+idempotent | `cursor`, `depth`, `fields`, `include_meta`, `layout_id`, `limit`, `max_chars`, `root_path`, `site_id` | List elements in a template as a flat array with JSON-Pointer paths + types + `label`. Scope with `root_path`/`depth`, paginate with `limit`/`cursor`. Rows carry BOTH addresses: `path` (absolute) and `rel_path` — `rel_path` is the one write calls take as `element_path`. To find WHERE a module/widget is used, call local_content_read (gateway-only), never loop this per page. |
|
|
5286
|
+
| `yootheme_builder_element_move` | write+idempotent | `element_path`, `etag`, `layout_id`, `site_id`, `to_index`, `to_parent_path` | Move an element to a new parent + index in the tree — reordering or reparenting. Renumbers siblings at BOTH ends: `renumbered_siblings` names each moved sibling address (absent if none). Requires ETag. Live at once. |
|
|
5287
|
+
| `yootheme_builder_element_set_source` | write+destructive+idempotent | `confirm`, `element_path`, `etag`, `layout_id`, `site_id`, `source`, `source_extended` | Surgically SET a precomposed `source` (+ optional `source_extended.props`) on ONE element at a pointer, ETag-locked. Use this — not element_bind_source — when you need `source_extended.props`, which bind_source cannot write. CAPABILITY half of a pair — it also REPLACES `source.props` whole, never merges; element_bind_source is the CONVENIENCE half. Element-prop keys the type does not render echo back as `unknown_prop_keys`. WHOLE-NODE write: whatever you do not send is REMOVED. Omitting `source_extended` clears it exactly like passing `null`, and a `source` without `props` drops every prop mapping. Re-send what you want to keep. A call that would drop existing mappings answers with a preview until you repeat it with `confirm: true`. Live at once. To remove a binding entirely, call element_unbind_source. |
|
|
5288
|
+
| `yootheme_builder_element_set_status` | write+idempotent | `element_path`, `etag`, `layout_id`, `site_id`, `status` | 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 (gateway-only) and template_summary content_nodes both honour it. Requires ETag. Live at once. |
|
|
5289
|
+
| `yootheme_builder_element_type_get_schema` | read+idempotent | `element_type`, `field_names`, `fields`, `include_fields`, `max_chars`, `name_contains`, `site_id`, `type_name` | Prop schema; read before element_add/element_update_settings: undeclared keys are STORED, not refused: warns unknown_prop_keys. NO element_type: lists the types. With one: head only — binding_contract (containers/items) + groups[] + field_count. include_fields:true adds every descriptor {name,type,label?,text?,description?,enum?,value_hint?,group?,no_effect?}; Select descriptors: field_names/name_contains, answered with field_count/returned_count. 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. Bound size with fields[]/max_chars. |
|
|
5290
|
+
| `yootheme_builder_element_types_list` | read+idempotent | `fields`, `include_meta`, `site_id` | List element types registered on this site (built-ins + YOOessentials extras). Names feed `element_type` of element_add. "slider"/"carousel" → `slideshow`/`panel-slider`/`overlay-slider`. Prop keys differ per element — use element_type_get_schema before element_add/bind. |
|
|
5291
|
+
| `yootheme_builder_element_unbind_source` | write+destructive+idempotent | `confirm`, `element_path`, `etag`, `layout_id`, `site_id` | Remove the source binding from an element — the counterpart to element_bind_source. Clears the canonical top-level `source` key AND the legacy `props.source` — may break dynamic-content rendering, always ask the user to confirm. ETag on the unbind only. Live at once. |
|
|
5292
|
+
| `yootheme_builder_element_update_settings` | write+idempotent | `element_path`, `etag`, `layout_id`, `merge`, `name`, `props`, `site_id`, `unset` | Update `props` on an element. Default replaces all props; pass `merge:true` for server-side deep-merge (only request keys overwritten). Requires ETag. `unset:["key",…]` resets props to their YOOtheme default and is valid ON ITS OWN: sent without `props`, it removes only those keys and touches nothing else. Send `props`, `unset`, or both — a call with neither is refused. Live at once. |
|
|
5293
|
+
| `yootheme_builder_fetch_public_url` | read+idempotent+openWorld | `expect`, `max_excerpt_chars`, `site_id`, `url` | The independent proof a publish reached visitors: fetches a page of the CONFIGURED site over the public network as an ANONYMOUS visitor — no login/cookies/Bearer — and reports http_status, page_title, an EXCERPT (never the whole document), html_bytes, and whether a cache served it (cache_verdict; "unknown" means no header said, NOT that nothing cached, and no re-fetch settles it — purge the cache instead). With `expect` it answers expect_found true/false. Refuses any address outside the configured site and never follows a redirect off it — an unrestricted fetcher is an open proxy. Get the url from `frontend_url` (pages_list / page_publish). Searches the WHOLE page (header, menu, footer too); a hit is no proof of THIS layout. render_preview renders ONLY that layout tree, never the theme wrapper. |
|
|
5294
|
+
| `yootheme_builder_get_etag` | read+idempotent | `site_id` | Get the current site-wide ETag for optimistic locking. ANY write to ANY template bumps it — a 412 means the site state advanced, not necessarily your template. Same token page_get_layout/element_list return. Pass back as `etag` on writes; on 412, re-read and retry. The ETag is SITE-wide, not per template — that is why this tool takes no `layout_id`. |
|
|
5295
|
+
| `yootheme_builder_get_skill` | read+idempotent | `site_id`, `topic` | Read a chapter of the full workflow guide. Without `topic`: the list of available topics, each with the SIZE of what it returns. With `topic`: that chapter. The connect-time `instructions` carry only a short preamble — this is how you reach the rest. A long chapter is served in PARTS: its row lists their slugs, and asking for the parent returns the chapter intro plus that list. A row carrying `oversized_note` is served whole and is large — read the note before asking for it. |
|
|
5296
|
+
| `yootheme_builder_header_transparency_set` | write+idempotent | `acknowledge_contrast_risk`, `dry_run`, `etag`, `layout_id`, `mode`, `pull_content_behind`, `site_id`, `text_color`, `transparent` | Inspect + repair the transparent header. Always targets the FIRST section — later ones are silently dead. dry_run defaults TRUE: returns current state + the theme-declared --uk-inverse, risk_class, recommendation. mode:"auto" writes the theme-declared colour; over a hero image/video it writes NONE and says LOOK. mode:"explicit" refuses a colour contradicting the theme (409) unless acknowledge_contrast_risk. transparent:false is always allowed. |
|
|
5297
|
+
| `yootheme_builder_heading_outline` | read+idempotent | `layout_id`, `max_chars`, `mode`, `site_id` | A real, doc-order H1-H6 outline of ONE template, each heading with its source element, plus alt-status of every image. Structure inspection, not findings — use pages_audit for site-wide SEO findings. One row per CARRIER: a container whose bound item repeats appears once, while template_summary.heading_levels counts it once per rendered row. |
|
|
5298
|
+
| `yootheme_builder_health` | read+idempotent | `site_id` | Check plugin installed/reachable. Returns plugin version, YT Pro version, REST endpoints, site_url, home_url on both platforms; wp_version + yooessentials_version are WordPress-only. available_endpoints is derived from the live route registration on both platforms; every path is request-ready as printed. served_areas lists the tool areas this server serves (was `capabilities` — kept one release as an alias). Multi-site: pass `site_id`, else this checks the active/default site. `write_rate_limit` = the write cap this site enforces, per BEARER KEY: everything sharing the key shares it. See diagnose for Bearer-validity + connectivity. |
|
|
5299
|
+
| `yootheme_builder_image_import` | write+idempotent | `full_url`, `site_id`, `url` | Sideload a photo into the site's Media Library. Only Unsplash, Pexels and the YOOtheme library CDN are accepted; other hosts are refused. Returns `{imported:true, local_url}`; shape is platform-specific: WordPress leads with a slash (/wp-content/uploads/...), Joomla does NOT (images/yootheme/...). Bind it VERBATIM via element_update_settings({props:{image: local_url}, merge:true}). Give the address as `url` OR as `full_url`, the field name an image_search row carries; one of the two is required. Identical re-imports reuse the existing file in the current upload folder. Failures return `{imported:false, reason, failure}`; `unavailable` = no url works now. |
|
|
5300
|
+
| `yootheme_builder_image_search` | read+idempotent | `color`, `include_meta`, `offset`, `orientation`, `per_page`, `query`, `site_id`, `source` | Search on-topic photos (Unsplash/Pexels), YOOtheme Pro Images, or (source:"local") this site's own media library. Rows carry `full_url` (feeds image_import) plus dimensions/author. `total:0` is the ONLY shape meaning the provider matched nothing; a search that never happened answers an `error_code` instead. No apikey gives `image_search_api_key_not_configured`; a provider that REFUSES gives `error_code: image_search_unavailable`, which no broader query can fix. A key can be CONFIGURED and still be REFUSED (401): report that the upstream refused it, never why (source:"local" is exempt — see its field description). Works on WordPress and Joomla. |
|
|
5301
|
+
| `yootheme_builder_inspect_multi_items_binding` | read+idempotent | `element_path`, `layout_id`, `site_id` | Reports Multi-Items binding state: container/item pair (grid↔grid_item, map↔map_item, …), binding level (none\|container\|item), recommended_fix. For map/map_item, a bound item without a `location` field is flagged as a blocking map_location_warning (0 markers). If rows render as ONE comma-joined string, see clean_implode_directives (gateway-only). |
|
|
5302
|
+
| `yootheme_builder_inspect_sublayout` | read+idempotent | `element_path`, `layout_id`, `max_chars`, `site_id` | Inspects a panel/grid_item `< SUBLAYOUT`: lists the `fragment` subtree bindable leaves and the render-time inner grid. Flags `ungated_grid_prop`: grid props set but the gate unsatisfied → silent no-op. For container↔item binding see inspect_multi_items_binding. |
|
|
5303
|
+
| `yootheme_builder_layout_query` | read+idempotent | `content_contains`, `cursor`, `element_type`, `fields`, `has_binding`, `has_implode_directives`, `include_meta`, `layout_id`, `limit`, `max_chars`, `max_depth`, `max_paths`, `name_matches`, `site_id`, `source_name`, `under_section` | TWO scopes. OMIT layout_id → SITE-WIDE: binding counts per `source_name` (COUNTED, never resolved — a source that no longer exists counts like a live one; pages_audit flags those as `dangling-source`, with paths), `max_paths` for individual paths, `has_implode_directives:true` to locate the comma-joining leftovers. WITH layout_id → ONE layout, AND-combined `element_type`, `name_matches`, `content_contains` (case-insensitive substring of `content`/`title`/`meta` props), `has_binding`, `under_section`; paginate `limit`/`cursor`. Mixing scope arguments is REFUSED, never dropped. To find WHERE a module/widget is used call local_content_read (kind:"modules"). On a module/module_position row `label` is the builder-assigned element name, NOT the module title: read element_get(.resolved). |
|
|
5304
|
+
| `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. Filter: `kind`, `topic`, `type`/`role`, `element_type`, `purpose`, `query`. A section id is the composite "<layoutId>#<sectionIndex>" — feeds library_get/insert. `source`: "catalog" (default) or "ai" — YOUR saved entries from library_save_as, which library_delete removes again — both gateway-only, call via yootheme_builder_advanced({tool, arguments}). `total:0` = the catalog matched nothing; a read with no data answers `library_upstream_unavailable` or `library_api_key_not_configured`, never an empty page. |
|
|
5305
|
+
| `yootheme_builder_library_delete` | write+destructive+non-idempotent | `confirm`, `id`, `site_id`, `source` | Delete ONE saved entry from your "WootsUp AI Layouts" store (library_save_as writes into it) — pass the `id` from a library_browse(source:"ai") row. `source` defaults to "ai", the ONLY deletable store; the curated library is read-only, source:"catalog" returns 403. IRREVERSIBLE — requires confirm:true. |
|
|
5306
|
+
| `yootheme_builder_library_get` | read+idempotent | `id`, `include_thumbnail`, `kind`, `max_chars`, `site_id`, `source` | Fetch ONE Library layout or element preset by `id` — call after library_browse (items[].id feeds this). `include_thumbnail:true` adds an image (costs tokens). This DETAIL endpoint checks the key, browse does not: no key gives 503 library_api_key_not_configured, a refused key 503 library_upstream_unavailable. Report the refusal, not a cause. `source`: "catalog" (default) or "ai" for your own library_save_as (gateway-only) entries. |
|
|
5307
|
+
| `yootheme_builder_library_insert` | write+destructive+non-idempotent | `confirm`, `etag`, `id`, `images`, `index`, `insert_mode`, `kind`, `section_index`, `site_id`, `source`, `target` | Insert a Library item onto a page — call after library_get/browse. `target`: new_page, {template_id,element_path?} (splices onto the layout root, or a preset onto a Column), or sublayout. A preset (kind:"element") MUST target a Column (422 otherwise) — scaffold one with scaffold_section. `images`: "placeholder" (default) or "curated" (downloads to Media Library). Placement: `insert_mode` for layouts, `index` for presets. `etag` locks (412 if stale). `source`: "catalog" (default) or "ai" to insert your own saved entry. |
|
|
5308
|
+
| `yootheme_builder_library_save_as` | write+non-idempotent | `destination`, `name`, `site_id`, `source` | Save a page or subtree into a reusable library — the REVERSE of 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 source:"ai") or "native" (YOOtheme's built-in My Layouts). |
|
|
5309
|
+
| `yootheme_builder_local_content_read` | read+idempotent+openWorld | `fields`, `include_children`, `include_meta`, `include_usage`, `kind`, `limit`, `max_chars`, `offset`, `parent_id`, `search`, `site_id`, `status`, `taxonomy`, `type`, `verify_on_urls` | Read native records for one `kind`: entities, terms (ids that archive-scope a Grid), users, fields, menus, media, or modules (Builder widgets). Paginated {items,total,next_offset}, clamped to what the key may see. A deleted page/article is not in the default list; `status` (kind:"entities" only) gives the exclusive trashed view; the response carries `trashed_total` unasked when one exists. Archive-scoping: read {kind:"terms",taxonomy:"category"}, pick a term id, bind a source with query_arguments:{id:<term_id>}. Call local_content_types first. |
|
|
5310
|
+
| `yootheme_builder_local_content_types` | read+idempotent | `fields`, `kind`, `max_chars`, `site_id` | Discover the site's NATIVE content landscape, native to WP/Joomla (no API Mapper) — {content_types, taxonomies, field_groups, capabilities, positions}; taxonomies include ACF/com_fields groups. Content-type and taxonomy rows carry `bindable` + `source_name` (null for native types — bind via element_bind_source by content-type name); field-group rows carry neither. `positions` (B23) lists every valid widget-area / theme-position value for modules_create/modules_update's `position` argument — read it here BEFORE a write, not from a 400 refusal after one; null when not determinable, never `[]` as a claim that none exist. Call first, then local_content_read for records. |
|
|
5311
|
+
| `yootheme_builder_modules_create` | write+non-idempotent | `base`, `cache`, `cache_time`, `cachemode`, `class_sfx`, `content`, `dry_run`, `endLevel`, `layout`, `menutype`, `moduleclass_sfx`, `placement`, `position`, `showAllChildren`, `showtitle`, `site_id`, `startLevel`, `tag_id`, `text`, `title`, `type`, `window_open` | Create a widget (WordPress: custom_html/text/block) or module (Joomla: mod_custom/mod_menu), exactly five known types. REQUIRES placement (position, module_element or position_element): a call without it creates nothing and returns ways (every way this platform offers) plus notes. `position` is validated server-side against what this site declares and must agree with placement. content/text stored byte-for-byte, never sanitized. dry_run defaults TRUE, creates nothing; a real create returns the full row PLUS next_step, the exact follow-up call for the placement you chose. Gateway-only, not in tools/list — call via yootheme_builder_advanced. |
|
|
5312
|
+
| `yootheme_builder_modules_delete` | write+destructive+non-idempotent | `acknowledge_element_refs`, `confirm`, `id`, `intent`, `site_id` | Delete a widget (WordPress) or module (Joomla) — the clean-up verb create/update lacked. Needs `intent`: call once without it and the server lists which are possible for this module's CURRENT state, each with its own effect. confirm defaults FALSE: returns the row plus usage.element_refs and changes NOTHING. A DIRECT layout reference blocks the irreversible step (409) until acknowledge_element_refs:true. |
|
|
5313
|
+
| `yootheme_builder_modules_update` | write+idempotent | `access`, `active`, `assignment`, `content`, `dry_run`, `id`, `ordering`, `position`, `showtitle`, `site_id`, `theme_overlay`, `title` | Update a widget/module's writable subset: title, content (byte-identical), position, active, theme_overlay; plus showtitle, ordering, access, assignment on Joomla. dry_run defaults TRUE, returns {before, after, changed, notes}, writes nothing. Refused: title on a WordPress block widget (it has none), a position this site does not declare (either platform, same error_code as modules_create), and active:true while a Joomla publish window leaves it scheduled/expired — there active means published AND that window. Joomla: active:true also lifts a module out of the trash, PUBLISHED not as it was. position:"" clears the position on Joomla, refused on WordPress (deactivate with active:false instead). Gateway-only, not in tools/list — call via yootheme_builder_advanced. |
|
|
5314
|
+
| `yootheme_builder_navigation` | read+idempotent | `menu`, `site_id` | The site's navigation menus as a parent→children TREE, not a flat list — "which page is under which menu item". Each item carries a `page_role` facet and, when derivable, a `target_page` ready for page_get_layout. |
|
|
5315
|
+
| `yootheme_builder_page_generate_dynamic` | write+non-idempotent | `conditions`, `dry_run`, `element_path`, `etag`, `layout_id`, `leaf_map`, `list_source_name`, `output`, `page`, `remove_leaves`, `section_name`, `site_id`, `source_map` | Copies a designed section byte-identically into a NEW, unpublished, unlinked page and swaps only what you map — it never edits the reference. Requires output:"new_page" (today's only value; "in_place" is not yet available). Give a REFERENCE (section_name or element_path) + list_source_name + a leaf_map. A section with several source blocks dynamizes in ONE call via source_map. Preview with dry_run:true (a would_change:false answer carries `reason`). Live at once — stage hidden with element_set_status(status:false). The response carries `cms_visibility.findable_by_visitor`: can a visitor FIND this one page via a navigation path? It reads the menu route AND the access level, which the per-row `visibility` of pages_list does not. Cannot make it the site front page (a CMS menu setting). |
|
|
5316
|
+
| `yootheme_builder_page_get_layout` | read+idempotent | `fields`, `flat`, `force`, `include_modules`, `layout_id`, `max_chars`, `mode`, `site_id`, `url` | Get the layout tree for one layout — address by `layout_id` or `url`. `mode:"bindings_only"` for dynamize/bind. Over `max_chars` whole entries DROP (`truncated`+`omitted_count`; outline cuts its text) and NOTHING pages the rest — no cursor in any mode. Narrow instead: `mode:"outline"` to map, then element_list(root_path). For a site-wide SEO/structure audit do NOT loop this tool page-by-page — call yootheme_builder_pages_audit (gateway-only). For ONE narrow question (e.g. "where is the binding" or "which elements have type X") this whole-template read is heavier than it needs to be — prefer yootheme_builder_layout_query (gateway-only; has_binding/element_type/name_matches filters) or element_list with `root_path` to scope the walk. |
|
|
5317
|
+
| `yootheme_builder_page_publish` | write+idempotent | `cms_status`, `etag`, `layout_id`, `menu_item`, `quick_check`, `site_id` | Publish a template's LAYOUT — persist state, flush the plugin's own caches (a page-cache/CDN plugin is NOT purged: purge it yourself, then verify with fetch_public_url), snapshot the ETag; "publish" ALSO means the CMS post status, and this tool does not change that, so a draft page stays invisible. Step (2) of the three page_save describes; pages_set_status is step (3). ETag required — 412 on conflict. Answers `cms_visibility.findable_by_visitor`: can a visitor FIND this one page via a navigation path? It reads the menu route AND the access level, which the per-row `visibility` of pages_list does not. Optional `cms_status` folds step (3) into this call for the common build-it-and-make-it-live case; without it nothing about the CMS status changes. |
|
|
5318
|
+
| `yootheme_builder_page_save` | write+idempotent | `dry_run`, `etag`, `layout`, `layout_id`, `quick_check`, `site_id` | Re-run save-transforms + flush the plugin's own caches. Optional whole-tree replace via `layout` — the INNER {type:"layout",children:[...]} tree, NOT page_get_layout's whole response (rejected 422). THREE separate steps, and this is the first: (1) page_save replaces or re-normalises the stored tree — every write, element or whole-tree, runs the same save-transforms; (2) page_publish snapshots the ETag and flushes them; (3) pages_set_status decides whether a visitor may see the page at all. This call does NOT do (2) or (3). Response `previous_layout` is the tree right before THIS call, not before your edits (element writes already landed) — identical to what you just wrote is normal when the transforms change nothing. `dry_run:true` preflights, no ETag. ETag on the save (428/412). |
|
|
5319
|
+
| `yootheme_builder_pages_audit` | read+idempotent | `checks`, `include_meta`, `include_modules`, `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). Covers heading structure and accessibility (h1/heading-skip), image alt text, thin content, duplicate layouts, title collisions, broken in-page anchor links (same-page `#id` jumps only — no external link is ever fetched or resolved), and design consistency (markup outside the UIkit design system, header/navigation contrast). Only active (non-disabled) nodes produce findings. `target` scopes to ONE page for the full set. SEO-plugin meta (title/description/OG) NOT covered. |
|
|
5320
|
+
| `yootheme_builder_pages_create` | write+non-idempotent | `dry_run`, `layout`, `menu_item`, `published`, `site_id`, `slug`, `status`, `title` | Create a page (WordPress) or article (Joomla 5/6) with a per-page layout. Required: title. Optional: status (default draft), layout, menu_item, dry_run (preflight, no persist). Joomla: a published article has NO SEF route until a menu item points at it — pass menu_item for a navigable url. Cannot make it the site front page: which page answers / is a CMS menu setting this server never writes. pages_list marks the current one with is_public_homepage. |
|
|
5321
|
+
| `yootheme_builder_pages_delete` | write+destructive+non-idempotent | `confirm`, `intent`, `layout_id`, `site_id` | Delete a page (WordPress) or article (Joomla 5/6). OMIT confirm to preview: returns 200 with label, elements_count, what happens to media, and the intents this page's state allows, and deletes nothing. confirm:true + an intent carries it out. Already-trashed returns 200 {deleted:false, already_trashed:true}, not an error. |
|
|
5322
|
+
| `yootheme_builder_pages_list` | read+idempotent | `cursor`, `fields`, `include_meta`, `label_contains`, `limit`, `max_chars`, `site_id`, `sort`, `visibility` | List pages, templates and layouts. Each row returns `kind` (content \| theme_template) plus serves_context_kind, serves_context_ids and page_role — the template ROLE (WHICH objects it renders for), NOT a data binding; for who-binds-what call layout_query with no layout_id. The envelope returns `totals`. Call first for layout_id (page_get_layout/element_list). `type` = PAGE type, not element type (those filter on `element_type`). Rows with a CMS status carry `visibility`: public \| hidden \| undetermined, never empty. Reads STORED FIELDS only — status, plus WordPress password protection (`hidden`, with the reason). It does NOT read the menu route, the access level, or whether the permalink resolves, so Joomla rows stay `undetermined`; for THAT verdict read `cms_visibility.findable_by_visitor` from pages_set_status / page_publish — a different, per-page question. Page contents (SEO/structure/links/modules): yootheme_builder_pages_audit. `is_public_homepage` marks the one row served at / (the site front page); no tool here moves it. |
|
|
5323
|
+
| `yootheme_builder_pages_resolve` | read+idempotent | `site_id`, `target`, `url` | Resolve a page/article/archive URL to the layout_id that renders it. SEF/pretty permalinks resolve on both WordPress and Joomla. A category/term resolves to the SPECIFICALLY-ASSIGNED template (honoring include-child-categories), not a generic one. Returns {layout_id, post_id, type, frontend_url, context, resolved_via}. |
|
|
5324
|
+
| `yootheme_builder_pages_restore` | write+non-idempotent | `layout_id`, `site_id` | Restore a TRASHED page (WordPress) or article (Joomla) — the one correct way back (pages_set_status refuses a trashed page with 409 naming this tool). WordPress: reinstates the EXACT pre-trash status (core defaults restores to draft since 5.6) and attempts to reclaim the pre-trash slug — a hand-rolled write leaves the "__trashed" suffix instead; a slug another page has since taken comes back different — check the response's frontend_url. Joomla stores no pre-trash status, so this can only set state to unpublished (visible to editors, not visitors); call yootheme_builder_pages_set_status with status:"published" afterwards to make it visible. Not currently trashed → refused (409). Find a trashed id with yootheme_builder_local_content_read({kind:"entities", status:"trash"}). |
|
|
5325
|
+
| `yootheme_builder_pages_set_status` | write+idempotent | `layout_id`, `site_id`, `status` | Change the CMS status of an EXISTING page (WordPress) or article (Joomla) — keeps its id and URL. Step (3) of the three page_save describes. Use YOUR platform's own words: WordPress publish, draft, pending, private, future; Joomla published, unpublished, archived — the other platform's word is refused (422). Status is RE-READ after the write. To trash or delete use yootheme_builder_pages_delete; a page already IN the trash is refused (409) — use yootheme_builder_pages_restore first. The response carries `cms_visibility.findable_by_visitor`: can a visitor FIND this one page via a navigation path? It reads the menu route AND the access level, which the per-row `visibility` of pages_list does not. |
|
|
5326
|
+
| `yootheme_builder_render_preview` | read+idempotent | `expect`, `layout_id`, `site_id`, `url` | Render-check a SAVED layout. Address by `layout_id` or `url`. rendered_item_wrapper_count counts the repeat-item wrappers rendered, BOUND OR NOT, so a 0 is not evidence a binding failed (per-source rows: `source_item_counts`). Distinct from pages_list `elements_count` (all tree nodes). A source the registry cannot resolve is named in `unresolved_sources`, not just a smaller count. Renders ONLY the page's own layout tree, never the theme wrapper: a MISS with `expect` proves nothing beyond the tree (adds {matched, snippet, match_conclusive, match_note, mismatch_hints, stored_but_not_rendered — the last names elements that DO store the text but rendered no rows}). A Joomla module/module_position element sharpens this — EXCLUDED (fallback_used:true) or silently empty (dropped_count:0) — but match_conclusive:false marks EVERY inconclusive miss. Reports CMS status in `cms_status`/`cms_status_note` (absent for a theme template); pages_set_status remains the tool that changes it. |
|
|
5327
|
+
| `yootheme_builder_repair_source_references` | write+idempotent | `allow_unregistered`, `dry_run`, `etag`, `field_map`, `from`, `layout_id`, `site_id`, `source_map`, `to` | Repairs binding REFERENCES to a source/type/field name in ONE template, in one ETag-locked write, after that name changed OUTSIDE the builder (a taxonomy/CPT rename, a re-published flow). Does NOT rename the source itself — sources_list stays unchanged. Pass `from`/`to` for one token, or `source_map`/`field_map` for several. Preview with dry_run:true. |
|
|
5328
|
+
| `yootheme_builder_scaffold_section` | write+non-idempotent | `column_widths`, `columns`, `etag`, `layout_id`, `site_id` | Create a VALID section → row → column hull in ONE call; returns the column path(s) for content. Only `section` may sit at the layout root; leaf/content elements are rejected anywhere except inside a `column`. Pass `columns` (1-12) or `column_widths` for an asymmetric row. Requires ETag. |
|
|
5329
|
+
| `yootheme_builder_sites_list` | read+idempotent | `include_meta`, `site_id` | List all sites configured in this multi-site MCP installation — site_id, URL, platform, DEFAULT + ACTIVE flag per row. Call first to discover site_ids. A call omitting site_id goes to `active_site_id` (set by use_site, process-wide) else to `default_site_id`. Pass `site_id` to FILTER to one site; omit it for all. IDs from any tool (layout_id, element ids, …) are PER-SITE, never global: the same id can exist on a different site naming something else entirely. Always keep an id paired with the site_id it came from. |
|
|
5330
|
+
| `yootheme_builder_sites_overview` | read+idempotent | `include_meta`, `include_pages`, `max_chars`, `site_id` | Every configured site in ONE call: reachability, Bearer validity and page counts per site, probed in parallel. Replaces sites_list plus one sites_test and one pages_list per site. A site that is down becomes a row with an `error`, never a failed call. Pass `site_id` to narrow to one site; `include_pages:false` skips the page read (one HTTP request per site less). The content/theme_template split is absent on hosts that do not report it — never reported as zero. |
|
|
3874
5331
|
| `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. |
|
|
3875
|
-
| `yootheme_builder_source_get_schema` | read+idempotent | `max_chars`, `name`, `site_id` | Introspect ONE data source:
|
|
3876
|
-
| `yootheme_builder_sources_list` | read+idempotent | `cursor`, `fields`, `group`, `include_meta`, `kind`, `limit`, `max_chars`, `name_contains`, `site_id` | List
|
|
3877
|
-
| `yootheme_builder_template_set_type` | idempotent | `dry_run`, `etag`, `
|
|
3878
|
-
| `yootheme_builder_template_summary` | read+idempotent | `cursor`, `include_nodes`, `
|
|
3879
|
-
| `yootheme_builder_use_site` | idempotent | `probe`, `site_id` | Switch the ACTIVE site for this
|
|
5332
|
+
| `yootheme_builder_source_get_schema` | read+idempotent | `max_chars`, `name`, `site_id`, `source_name` | Introspect ONE data source: query methods + arguments + RELATIONS (e.g. Term→Posts: categoryFaqs.taxonomyCategoryFaq → field "faq"). `is_archive_context_provider` says whether the source CAN follow the current archive term; it does NOT say whether the source returns rows here — only a render answers that (element_bind_source with probe:true, or render_preview). Some arguments carry `enum_values` — pick from those when present, never invent values. Call before binding on an archive/category template. Get `name` from sources_list. |
|
|
5333
|
+
| `yootheme_builder_sources_list` | read+idempotent | `cursor`, `fields`, `group`, `include_meta`, `kind`, `limit`, `max_chars`, `name_contains`, `site_id` | List data sources/feeds available in the builder — name + label + origin + `group` per source. Call before binding an element with element_bind_source. `group` is what a source answers FROM: a "Page"-group LIST source serves the CURRENT page/archive context, so a standalone page gets zero rows from it. Each row says so in `context_note` when it applies. A long list narrows with `group` (the ORIGIN bucket: apimapper \| wordpress \| joomla \| essentials — not the per-row `group` column above), `name_contains`, or `kind`. Reach for one before paging. WHERE a source is used, site-wide: yootheme_builder_layout_query (gateway-only), OMIT layout_id. |
|
|
5334
|
+
| `yootheme_builder_template_set_type` | write+idempotent | `dry_run`, `etag`, `layout_id`, `site_id`, `type` | Reassign which archive/single a YOOtheme THEME template applies to — set its `type` condition in ONE ETag-locked write. Use after a taxonomy/CPT rename orphans a template. Site-wide + immediate — preview with dry_run:true, reversible via previous_type. THEME templates only: page:/article: refused (422). |
|
|
5335
|
+
| `yootheme_builder_template_summary` | read+idempotent | `cursor`, `include_nodes`, `layout_id`, `layout_ids`, `limit`, `site_id` | Token-efficient template overview: element counts by type, bindings, max depth, named sections — computed server-side in one call. Use before element_list/page_get_layout on a large template. `include_nodes:true` adds a `content_nodes` per-node breakdown (status/effective_visible/headings/alts), pageable via `limit`/`cursor`. `layout_ids:[...]` batches several: response carries `summaries`, `not_found` and `forbidden`. Example: yootheme_builder_template_summary({ layout_id: "default" }). |
|
|
5336
|
+
| `yootheme_builder_use_site` | write+idempotent | `probe`, `site_id` | Switch the ACTIVE site for this session — subsequent calls that omit `site_id` target it. NOTE: this default is PROCESS-WIDE — concurrent clients sharing this MCP server inherit it; pass `site_id` per call for isolation. Runs a /health + /etag probe by default; `probe:false` switches offline. `site_id` is REQUIRED. |
|
|
3880
5337
|
|
|
3881
5338
|
<!-- TOOL-CATALOG:END -->
|