From 61a4f1688ea86049d9071bbc4ab49bf288f3d7b9 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 16 Jun 2026 12:39:43 -0600 Subject: [PATCH 1/9] Include the 10up/block-renderer-core package --- package-lock.json | 602 ++++++++++++++++++++++++++++------------------ package.json | 1 + 2 files changed, 374 insertions(+), 229 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2972f2dce..0e4d449eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "3.8.0", "license": "GPL-2.0-or-later", "dependencies": { + "@10up/block-renderer-core": "^0.2.0", "@wordpress/icons": "^13.0.0", "choices.js": "^11.2.3", "clsx": "^2.1.1", @@ -28,6 +29,24 @@ "wp-hooks-documentor": "github:10up/wp-hooks-documentor#build" } }, + "node_modules/@10up/block-renderer-core": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@10up/block-renderer-core/-/block-renderer-core-0.2.0.tgz", + "integrity": "sha512-R1Ds+4pqHI8Nj3PbiBDpQtghEaEi/7gRbGYArnGM7wj6xgGA9zJLLcX6l/hqJ+XNUT4zoUHnK/7xULX30K0YqQ==", + "license": "MIT", + "dependencies": { + "zod": "^3.24.0" + } + }, + "node_modules/@10up/block-renderer-core/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -1949,6 +1968,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", @@ -2284,6 +2318,25 @@ "tslib": "^2.4.0" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.50.2", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.50.2.tgz", @@ -7121,64 +7174,6 @@ "react": "^19.2.4" } }, - "node_modules/@types/wordpress__blocks/node_modules/@wordpress/compose": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-8.0.0.tgz", - "integrity": "sha512-M+zUGHjXzFPJzL9/Kufxe8qTNOE1RHWSynGaF3vdAXJFaQRw30asvyO85DfnEELQ0R5blaWCbBexyp6oIYnyBQ==", - "dev": true, - "license": "GPL-2.0-or-later", - "dependencies": { - "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "^4.47.0", - "@wordpress/dom": "^4.47.0", - "@wordpress/element": "^7.0.0", - "@wordpress/is-shallow-equal": "^5.47.0", - "@wordpress/keycodes": "^4.47.0", - "@wordpress/priority-queue": "^3.47.0", - "@wordpress/private-apis": "^1.47.0", - "@wordpress/undo-manager": "^1.47.0", - "change-case": "^4.1.2", - "mousetrap": "^1.6.5", - "use-memo-one": "^1.1.1" - }, - "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" - }, - "peerDependencies": { - "react": "^19.2.4" - } - }, - "node_modules/@types/wordpress__blocks/node_modules/@wordpress/data": { - "version": "10.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.47.0.tgz", - "integrity": "sha512-OAwxgplnv42U2oIKb7QkzR/v8OfStgSfl8/eHtA+aE4Z4yvRas71O1w6mmHABIKo3+zSwyxiTakag+LMD9/9Mw==", - "dev": true, - "license": "GPL-2.0-or-later", - "dependencies": { - "@wordpress/compose": "^8.0.0", - "@wordpress/deprecated": "^4.47.0", - "@wordpress/element": "^7.0.0", - "@wordpress/is-shallow-equal": "^5.47.0", - "@wordpress/priority-queue": "^3.47.0", - "@wordpress/private-apis": "^1.47.0", - "@wordpress/redux-routine": "^5.47.0", - "deepmerge": "^4.3.0", - "equivalent-key-map": "^0.2.2", - "is-plain-object": "^5.0.0", - "is-promise": "^4.0.0", - "redux": "^5.0.1", - "rememo": "^4.0.2", - "use-memo-one": "^1.1.1" - }, - "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" - }, - "peerDependencies": { - "react": "^19.2.4" - } - }, "node_modules/@types/wordpress__blocks/node_modules/@wordpress/element": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-7.0.0.tgz", @@ -7200,34 +7195,6 @@ "npm": ">=8.19.2" } }, - "node_modules/@types/wordpress__blocks/node_modules/@wordpress/rich-text": { - "version": "7.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.47.0.tgz", - "integrity": "sha512-CNQGDtfp9ObpwtIYsAO8UD0mb4hj9F5K8maLn1t83EkxaozwzTGjpGSaV6Y/J9eQd2ZkvG9Cu37YfKO4gRhbHw==", - "dev": true, - "license": "GPL-2.0-or-later", - "dependencies": { - "@wordpress/a11y": "^4.47.0", - "@wordpress/compose": "^8.0.0", - "@wordpress/data": "^10.47.0", - "@wordpress/deprecated": "^4.47.0", - "@wordpress/dom": "^4.47.0", - "@wordpress/element": "^7.0.0", - "@wordpress/escape-html": "^3.47.0", - "@wordpress/i18n": "^6.20.0", - "@wordpress/keycodes": "^4.47.0", - "@wordpress/private-apis": "^1.47.0", - "colord": "2.9.3", - "memize": "^2.1.0" - }, - "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" - }, - "peerDependencies": { - "react": "^19.2.4" - } - }, "node_modules/@types/wordpress__blocks/node_modules/react": { "version": "19.2.7", "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", @@ -8119,14 +8086,14 @@ } }, "node_modules/@wordpress/a11y": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-4.47.0.tgz", - "integrity": "sha512-Gq0PRRg7/jXFD4V8UfQNQEMR5DX8KxYUK1QUbHgRedsaW/ZhlgsXJHmBMZfeLubBn/tf0xpdPYdp0MNA5oc3Rg==", + "version": "4.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-4.48.1.tgz", + "integrity": "sha512-BPU7wRoz2XRmP3ZgVtENPKS4iO5/+bKNid/xLrvD6cP1qMhIGowQsBNqmkP1V5+q71hQM/ID6tpFjeLhmogfPg==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/dom-ready": "^4.47.0", - "@wordpress/i18n": "^6.20.0" + "@wordpress/dom-ready": "^4.48.1", + "@wordpress/i18n": "^6.21.1" }, "engines": { "node": ">=18.12.0", @@ -8134,9 +8101,9 @@ } }, "node_modules/@wordpress/autop": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-4.47.0.tgz", - "integrity": "sha512-2r/Fq6TeRV7ytsRfppJozKVaqY2jPGue2dwvthgo8/LruHOGuSBSFz67wmgI+0WUXPqq2+DjQ5mL0+416sxNMA==", + "version": "4.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-4.48.1.tgz", + "integrity": "sha512-vMOdHhXIv559fYsg72AnWACblxZdojIerVeWxlX7a/ptoZK8MjqA0ZVhFsHezTBqdfiFyoRtiRECSFYLXWlSZg==", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -8374,9 +8341,9 @@ } }, "node_modules/@wordpress/blob": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-4.47.0.tgz", - "integrity": "sha512-uLFDeZlccLFuz42jRGgw+yH+B/5ISXDl6ht3kZugHo5auXD3oUikDLyz1+kiR/hhbWF2dzzoWOxAi52d+nHhSQ==", + "version": "4.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-4.48.1.tgz", + "integrity": "sha512-iK3dtZu/UtnYpKfQ2aGZM2xrLXK5ff88QNU77XlraipaGV/C7zK2M0+sWY6cVL50TfMR7VX9prZ2XCpE5aRzSg==", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -8385,9 +8352,9 @@ } }, "node_modules/@wordpress/block-serialization-default-parser": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-5.47.0.tgz", - "integrity": "sha512-xRZ026shHpau7VWDXLGBvfaU6s/yC1efzOGKiDGZ0fbTbTQcr09ma7ToaDyCiHIfMoSu9lMYnriLLOEKVht8Sg==", + "version": "5.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-5.48.1.tgz", + "integrity": "sha512-REsjN6tT2lXekrjuiu2O0+FYW13QHhy23j7C458zzSjpYcxROtl/T8AozOJYRvG9SkdC9Og3PkEP/9/nGC4IVw==", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -8406,6 +8373,87 @@ "npm": ">=8.19.2" } }, + "node_modules/@wordpress/data": { + "version": "10.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.48.1.tgz", + "integrity": "sha512-74p4PiDLxS0SAd4tdkPEUF5rtHVtdRzoXExfhkZaumviE56tEceG07YbLjQWpZ+Vl1tZCvkyqLbMHK+Wj6ZerQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@types/react": "^18.3.27", + "@wordpress/compose": "^8.1.1", + "@wordpress/deprecated": "^4.48.1", + "@wordpress/element": "^8.0.1", + "@wordpress/is-shallow-equal": "^5.48.1", + "@wordpress/priority-queue": "^3.48.1", + "@wordpress/private-apis": "^1.48.1", + "@wordpress/redux-routine": "^5.48.1", + "deepmerge": "^4.3.1", + "equivalent-key-map": "^0.2.2", + "is-plain-object": "^5.0.0", + "is-promise": "^4.0.0", + "redux": "^5.0.1", + "rememo": "^4.0.2", + "use-memo-one": "^1.1.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/data/node_modules/@wordpress/compose": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-8.1.1.tgz", + "integrity": "sha512-AEC9yOS5CuTk34F+oiWebhQrxgICnb/v/KScQBUVm6zbs1AMFtu5ku+Zk0A3YTD0tO/hNzXLbvsXhJdh1bGkRA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@types/mousetrap": "^1.6.8", + "@types/react": "^18.3.27", + "@wordpress/deprecated": "^4.48.1", + "@wordpress/dom": "^4.48.1", + "@wordpress/element": "^8.0.1", + "@wordpress/is-shallow-equal": "^5.48.1", + "@wordpress/keycodes": "^4.48.1", + "@wordpress/priority-queue": "^3.48.1", + "@wordpress/private-apis": "^1.48.1", + "@wordpress/undo-manager": "^1.48.1", + "change-case": "^4.1.2", + "mousetrap": "^1.6.5", + "use-memo-one": "^1.1.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/data/node_modules/@wordpress/element": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-8.0.1.tgz", + "integrity": "sha512-otYhxfm6ZKkcLCl/tI1rB70z6MVDvTL+RiPOWXi4qm0niJf4isSXCcB91ffj1gzJXKbEpszHfysIoU9L2gCSGQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.1", + "@wordpress/deprecated": "^4.48.1", + "@wordpress/escape-html": "^3.48.1", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/dependency-extraction-webpack-plugin": { "version": "6.48.0", "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.48.0.tgz", @@ -8431,12 +8479,12 @@ "license": "BSD" }, "node_modules/@wordpress/deprecated": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-4.48.0.tgz", - "integrity": "sha512-aTa7oww6hvTjfIvxLsxlcwYj7skAGPnr1V2S0iBVQfiIn5wJPiGjM9hz4QEf6kyR44Vh0IYjW9wSxVuDMGZUdw==", + "version": "4.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-4.48.1.tgz", + "integrity": "sha512-ZfJSCxWr4Ss5qGja9W7L6+8vcrbX0n+tKDe2TrYvTq6GiqEc+0MrajIl4vi/58jhceHbSze1ITX/ocC3Ed3NLg==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/hooks": "^4.48.0" + "@wordpress/hooks": "^4.48.1" }, "engines": { "node": ">=18.12.0", @@ -8444,13 +8492,13 @@ } }, "node_modules/@wordpress/dom": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-4.47.0.tgz", - "integrity": "sha512-HD9pdNpS8dJnA70ZSBjx6FBbC4dChNaWWE+OxlvhgKisgETSIZaxvvc+W23WPIfC1WsKfD4amKtfLHXo+38TrQ==", + "version": "4.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-4.48.1.tgz", + "integrity": "sha512-UmouS3KAAUf6q90adAM37FZ3Tq+w+5UfNVUEKRtZjzbH3gMZJTKmYXpG9dkYyLLmicXgOrcG1GQ0qTX91MjAuA==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/deprecated": "^4.47.0" + "@wordpress/deprecated": "^4.48.1" }, "engines": { "node": ">=18.12.0", @@ -8458,9 +8506,9 @@ } }, "node_modules/@wordpress/dom-ready": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-4.47.0.tgz", - "integrity": "sha512-j2s4GdhxxQi2pbgyqdSz+Xh59K626/ErASH/ZKF7GlnBk4fjf3X0Odg7wopZ5wQfJgZSfEjGr1sonRst5uMAVw==", + "version": "4.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-4.48.1.tgz", + "integrity": "sha512-EYd2H8cYSk8H3wSnTK1wTtuC+hOCmMmZrCEBWzgHevs1n3B9g0nwBafSiRWpzc0vA2vculkNNtlSfy3ByJ2hag==", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -8539,9 +8587,9 @@ } }, "node_modules/@wordpress/escape-html": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.48.0.tgz", - "integrity": "sha512-phw399RofSqTqIM4DikmkDfgJ7exDYgPfDuxjv3D2YnUTTUsR+U9fA+pA+/rNUiZD1YOmVILQmkJt6oLaVM+nQ==", + "version": "3.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.48.1.tgz", + "integrity": "sha512-TQJK6sBcmMA5+xMjiAFuivcZ27wUgAfVCgB/fGf7YoxVC+BnCCIanAsHgpY0d4juGUK6LXzDEhU6ZgOpGkGqIQ==", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -8623,9 +8671,9 @@ } }, "node_modules/@wordpress/hooks": { - "version": "4.48.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.48.0.tgz", - "integrity": "sha512-rU1yGEy0Mb+2oRG5QX/bKIIwKQmYAvATfUQeXIF20/mbR0qutYeVTCIvWEyb4pf71tvnQFiN18RWRXWsvKrDbQ==", + "version": "4.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.48.1.tgz", + "integrity": "sha512-QZh3Cv9TMJkrCQikuZSsDKTgX+6BBzYJe0MFKp7yP8drj/dtRpkqo5+eYmovBq3pk+HoyP7UJ1vTOb3GPJeU6Q==", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -8633,9 +8681,9 @@ } }, "node_modules/@wordpress/html-entities": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-4.47.0.tgz", - "integrity": "sha512-D3sVJF1uTkjTUJvaAVJsoV8dCkP8q8L29NpavP4qClVQAhlYEynhmMUpesoL163f65Fi7XHwfIVejGjGDrMoXA==", + "version": "4.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-4.48.1.tgz", + "integrity": "sha512-Gq6j3yl+m0pc0989jFjAgYbtdwHyUS/5PR39zg+hQfq1IWiqCwfhFlJAqc8ymwx/gSklrxf9mmyhBwmUPWtMcw==", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -8644,14 +8692,14 @@ } }, "node_modules/@wordpress/i18n": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.20.0.tgz", - "integrity": "sha512-CRwh4/kRtetolDHL8qXUuvV4XV4lSbvqQ7TiqBK3t6OTtj+exQME9Jb/vQACaQFKJBGCHTHoEtwMfJ5tfrkggg==", + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.21.1.tgz", + "integrity": "sha512-qBbDJTRCtMfEzTVtzOa/fZf8XlU/JY3nI4MSdxyRQKduFanbXvzTRqJSSEIpZS4ACJTyUqafZX8zEZIH5CrNHQ==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.47.0", + "@wordpress/hooks": "^4.48.1", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "tannin": "^1.2.0" @@ -8683,9 +8731,9 @@ } }, "node_modules/@wordpress/is-shallow-equal": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.47.0.tgz", - "integrity": "sha512-NskF7HZlGvK8e5GlU8U340g9LGe/rcH3+4UxDAj9oECVL09B1s1IQaHWNUs65lq+ACNca6p7eOOfcBQ7PNhqXA==", + "version": "5.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.48.1.tgz", + "integrity": "sha512-qOn4doqp8Xr5vOdF7kni5BThkMLxFA9fZIUVZo/lzs+/rwV7rwdQQXtq/PXnHcOWDOqHSXBVlUciV7clDE6aeA==", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -8731,13 +8779,14 @@ } }, "node_modules/@wordpress/keycodes": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.47.0.tgz", - "integrity": "sha512-hAN65nUfVyhbq9EfIG7ue4FtZ7PoWCSmN7BnWdPAP6GLe9/RdfPuHtc91Of7xBULdMeXLeH04KsfwuE99tjo+w==", + "version": "4.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.48.1.tgz", + "integrity": "sha512-mVNex4sY0APJj69kxFfCmaoJVOaNd9Bu7oNecuXKEAI4sp/jvvn0x+IGwLt1lMrLqC3+bPEo+q+JV3KNXMOJIQ==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/i18n": "^6.20.0" + "@types/react": "^18.3.27", + "@wordpress/i18n": "^6.21.1" }, "engines": { "node": ">=18.12.0", @@ -8792,12 +8841,13 @@ } }, "node_modules/@wordpress/primitives": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-4.47.0.tgz", - "integrity": "sha512-Y1uhC6BiLFzORtXnMa07o3DVXG0z+q2KV6g48ClpZJ9aDiehBfdThBHwa6oa7HbYX/yAoMuCkuA7fWbm3zYxNw==", + "version": "4.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-4.48.1.tgz", + "integrity": "sha512-nzAcsXhBxw9x2q/ImVa45Ft80TO+e/WgCDmWaU3Zb2trogwThxZTezkE0oeQ6PSxSeGYM6nBgk+qqFCG8wyMhQ==", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/element": "^7.0.0", + "@types/react": "^18.3.27", + "@wordpress/element": "^8.0.1", "clsx": "^2.1.1" }, "engines": { @@ -8805,78 +8855,33 @@ "npm": ">=8.19.2" }, "peerDependencies": { - "react": "^19.2.4" - } - }, - "node_modules/@wordpress/primitives/node_modules/@types/react": { - "version": "19.2.16", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz", - "integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==", - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@wordpress/primitives/node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" + "react": "^18.0.0" } }, "node_modules/@wordpress/primitives/node_modules/@wordpress/element": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-7.0.0.tgz", - "integrity": "sha512-iJoc3pzeof0mhwN0gVqDqc64xCRoAij2JynU6YxGSmw5a1uJwi1w3GESX3nPR1eaDdv2V6vwkinMBI+aGG7wxQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-8.0.1.tgz", + "integrity": "sha512-otYhxfm6ZKkcLCl/tI1rB70z6MVDvTL+RiPOWXi4qm0niJf4isSXCcB91ffj1gzJXKbEpszHfysIoU9L2gCSGQ==", "license": "GPL-2.0-or-later", "dependencies": { - "@types/react": "^19.2.13", - "@types/react-dom": "^19.2.3", - "@wordpress/deprecated": "^4.47.0", - "@wordpress/escape-html": "^3.47.0", + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.1", + "@wordpress/deprecated": "^4.48.1", + "@wordpress/escape-html": "^3.48.1", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", - "react": "^19.2.4", - "react-dom": "^19.2.4" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "engines": { "node": ">=18.12.0", "npm": ">=8.19.2" } }, - "node_modules/@wordpress/primitives/node_modules/@wordpress/element/node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@wordpress/primitives/node_modules/@wordpress/element/node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.7" - } - }, - "node_modules/@wordpress/primitives/node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, "node_modules/@wordpress/priority-queue": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-3.47.0.tgz", - "integrity": "sha512-KWvo9JmFkNrv16DJEiqna97Oe7HLlVZaaB3t05QH8uJYG1YiH58Ld15B2V7nZWxZZ+HBm6CLrPUuGnt1+Ohtiw==", + "version": "3.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-3.48.1.tgz", + "integrity": "sha512-6nPO/FU1f9r9Zilaz7TOFSTIH5ojPqk/mmDFofo0h2kIljqik/mLwBOIls6WdDrQ2kti+BRNohbjGElAcws7kg==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -8888,9 +8893,9 @@ } }, "node_modules/@wordpress/private-apis": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.48.0.tgz", - "integrity": "sha512-HHOSXLCAlBggfMozwWtX36wgsSt22g2tZwpka47Rjzr3hNY1BZ6SrrFJumiNxooy5PDKbRgcF092PAF82hdJXg==", + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.48.1.tgz", + "integrity": "sha512-KddQQq+qboNnc4fROsy9bsX4JENRfvBMtuBg5CjOq11hScFyh169ozoHozMEtNGXou4XFykEHCRwzM5MfSHjiA==", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -8899,9 +8904,9 @@ } }, "node_modules/@wordpress/redux-routine": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-5.47.0.tgz", - "integrity": "sha512-r2HV47NUYxk/XiaG47nLgmb5GeC+KtisHVS4WYlKsUe3JlCffKc1dphL58EST1xvCt9UBCiwXfd8+Y0GHaXUpw==", + "version": "5.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-5.48.1.tgz", + "integrity": "sha512-+mUHB2DxfqGODfc9Lwdhz8D7jjojjWqhoa8w0ckUCzh84ZERiR3BcoiGhCkiWVSl9XedKu9itLFna5Q4gilEZw==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -8917,6 +8922,85 @@ "redux": ">=4" } }, + "node_modules/@wordpress/rich-text": { + "version": "7.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.48.1.tgz", + "integrity": "sha512-pj+S2d2p4EUJ03V/tOhlvb9qGPixft7v1zj9KEyM70VY6nHD5mmVp95Q5ALtlioyDFWYhzSo609PEd9fJ8FTsQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@types/react": "^18.3.27", + "@wordpress/a11y": "^4.48.1", + "@wordpress/compose": "^8.1.1", + "@wordpress/data": "^10.48.1", + "@wordpress/deprecated": "^4.48.1", + "@wordpress/dom": "^4.48.1", + "@wordpress/element": "^8.0.1", + "@wordpress/escape-html": "^3.48.1", + "@wordpress/i18n": "^6.21.1", + "@wordpress/keycodes": "^4.48.1", + "@wordpress/private-apis": "^1.48.1", + "colord": "^2.9.3", + "memize": "^2.1.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/rich-text/node_modules/@wordpress/compose": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-8.1.1.tgz", + "integrity": "sha512-AEC9yOS5CuTk34F+oiWebhQrxgICnb/v/KScQBUVm6zbs1AMFtu5ku+Zk0A3YTD0tO/hNzXLbvsXhJdh1bGkRA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@types/mousetrap": "^1.6.8", + "@types/react": "^18.3.27", + "@wordpress/deprecated": "^4.48.1", + "@wordpress/dom": "^4.48.1", + "@wordpress/element": "^8.0.1", + "@wordpress/is-shallow-equal": "^5.48.1", + "@wordpress/keycodes": "^4.48.1", + "@wordpress/priority-queue": "^3.48.1", + "@wordpress/private-apis": "^1.48.1", + "@wordpress/undo-manager": "^1.48.1", + "change-case": "^4.1.2", + "mousetrap": "^1.6.5", + "use-memo-one": "^1.1.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/rich-text/node_modules/@wordpress/element": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-8.0.1.tgz", + "integrity": "sha512-otYhxfm6ZKkcLCl/tI1rB70z6MVDvTL+RiPOWXi4qm0niJf4isSXCcB91ffj1gzJXKbEpszHfysIoU9L2gCSGQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.1", + "@wordpress/deprecated": "^4.48.1", + "@wordpress/escape-html": "^3.48.1", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/scripts": { "version": "32.4.0", "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-32.4.0.tgz", @@ -9272,13 +9356,13 @@ } }, "node_modules/@wordpress/shortcode": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-4.47.0.tgz", - "integrity": "sha512-WGCP3dWN6BwD9jOPLrICtMml4PcA+z01jliepQFrdblol9+McRsss9DZ8pvQQ9jiZEyRqw4SXw8RzDBirjFYYg==", + "version": "4.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-4.48.1.tgz", + "integrity": "sha512-zfOz45verEOIf3YIE4zOlMcQoZ2za+OFuJ4SKE+nmglUFLmF2I0iLGlNQO0BNLPI26+krSVZ8kclzJRaB5Z6Kw==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { - "memize": "^2.0.1" + "memize": "^2.1.0" }, "engines": { "node": ">=18.12.0", @@ -9286,9 +9370,9 @@ } }, "node_modules/@wordpress/style-runtime": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@wordpress/style-runtime/-/style-runtime-0.4.0.tgz", - "integrity": "sha512-frzAg1rsn8X0KNgrxxLxszLvWCKY0Nk2e8j8Mjm2pI2URmS8Et7NefuXP3JnHBD4U1L1Ug9yKO/FA65ojQ7CEA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@wordpress/style-runtime/-/style-runtime-0.4.1.tgz", + "integrity": "sha512-guZ0p9a5ZQyyCFPwVqDkhDNVXdXAhIqNkPGSNIGguEtt3OtSOskEMwYJHyXZYX8nlbH0FyKflGJhE4G6QlIWlw==", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -9318,15 +9402,16 @@ } }, "node_modules/@wordpress/theme": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@wordpress/theme/-/theme-0.15.0.tgz", - "integrity": "sha512-qoozJ4YEPb0LvTBnTMj8a7kPlQtT2LeGL7b/vKJkvnB9dIEUOED5c0rpeRZJoK9b77fpUH5GwYzPE3IWiQ6l2w==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@wordpress/theme/-/theme-0.15.1.tgz", + "integrity": "sha512-0SqH40Sd4pKH8YkDjQ4JM2NJzdhliO19QTPHAOAGA+tXuh+YwHOwFxX8Mg0v/vvI4XJD11zuiKGr+grBI7icTQ==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/element": "^8.0.0", - "@wordpress/private-apis": "^1.48.0", - "@wordpress/style-runtime": "^0.4.0", + "@types/react": "^18.3.27", + "@wordpress/element": "^8.0.1", + "@wordpress/private-apis": "^1.48.1", + "@wordpress/style-runtime": "^0.4.1", "colorjs.io": "^0.6.0", "memize": "^2.1.0" }, @@ -9346,20 +9431,20 @@ } }, "node_modules/@wordpress/theme/node_modules/@wordpress/element": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-8.0.0.tgz", - "integrity": "sha512-lQ8TB2vBr6lzcwQ2zh0xy+FC77Demb3FqL81fzpSsLbGUF9hZiTyyUuwc6SG21gCkGGjVm2TnU9BuHax/8nDfQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-8.0.1.tgz", + "integrity": "sha512-otYhxfm6ZKkcLCl/tI1rB70z6MVDvTL+RiPOWXi4qm0niJf4isSXCcB91ffj1gzJXKbEpszHfysIoU9L2gCSGQ==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { "@types/react": "^18.3.27", "@types/react-dom": "^18.3.1", - "@wordpress/deprecated": "^4.48.0", - "@wordpress/escape-html": "^3.48.0", + "@wordpress/deprecated": "^4.48.1", + "@wordpress/escape-html": "^3.48.1", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", - "react": "^18.3.0", - "react-dom": "^18.3.0" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "engines": { "node": ">=18.12.0", @@ -9367,13 +9452,13 @@ } }, "node_modules/@wordpress/undo-manager": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-1.47.0.tgz", - "integrity": "sha512-tmAIGJ28X0+/yt9KHacPwJTXX/qjmLgZgrkF86b/qXC5uz5OuvjrtcUwG3YcH/DoQjDRwe8eOkJ/6Ql3LwqrhA==", + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-1.48.1.tgz", + "integrity": "sha512-i/yHiUQ5S47yong7FXxIRpJgO1MbItEKfcyp1a3rRe66rw1RVPmAlJxyeKaQg9eZbCXOmmDz5cLzZNPyUDN6uA==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/is-shallow-equal": "^5.47.0" + "@wordpress/is-shallow-equal": "^5.48.1" }, "engines": { "node": ">=18.12.0", @@ -9381,9 +9466,9 @@ } }, "node_modules/@wordpress/warning": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.48.0.tgz", - "integrity": "sha512-En+A99j8aySNzUH0iXok0H2Xi+Uw2useKqYsvPm33VEMa0a0XIwa2I9srK5STp8RydCm1dK+/41K9e5xeFu23Q==", + "version": "3.48.1", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.48.1.tgz", + "integrity": "sha512-5YyMCOHycuHG+AjsVEUafpTqV4b4qjVv/tel5m1L8k8g8dUW5T/XKguFo1nhCqQc4HqoGBrJtKjaf6dMmg8uWg==", "dev": true, "license": "GPL-2.0-or-later", "engines": { @@ -10370,6 +10455,55 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/babel-plugin-macros/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", @@ -15782,10 +15916,11 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", - "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -22498,6 +22633,15 @@ "node": ">=4" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", diff --git a/package.json b/package.json index 2bd621385..e926a1178 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "wp-hooks-documentor": "github:10up/wp-hooks-documentor#build" }, "dependencies": { + "@10up/block-renderer-core": "^0.2.0", "@wordpress/icons": "^13.0.0", "choices.js": "^11.2.3", "clsx": "^2.1.1", From e0934f6a4e53be38dc32221dc9a8c6f78d229d56 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 16 Jun 2026 12:44:33 -0600 Subject: [PATCH 2/9] Modify our Content Generation prompt to request content be returned in JSON. Modify our Providers to request JSON returned responses --- .../ContentGeneration/return-format.php | 75 ++++++++++--------- includes/Classifai/Providers/Azure/OpenAI.php | 23 ++++-- .../Classifai/Providers/Localhost/Ollama.php | 19 ++++- .../Classifai/Providers/OpenAI/ChatGPT.php | 25 +++++-- 4 files changed, 88 insertions(+), 54 deletions(-) diff --git a/includes/Classifai/Features/Prompts/ContentGeneration/return-format.php b/includes/Classifai/Features/Prompts/ContentGeneration/return-format.php index 893387b08..df8fa7e8e 100644 --- a/includes/Classifai/Features/Prompts/ContentGeneration/return-format.php +++ b/includes/Classifai/Features/Prompts/ContentGeneration/return-format.php @@ -2,48 +2,49 @@ /** * Return-format instruction appended to the system message for the Content Generation feature. * - * Describes the WordPress block markup that the model should emit. + * Instructs the model to emit a constrained JSON "BlockTree" structure, which + * ClassifAI converts to valid WordPress block markup client-side (rather than + * asking the model to hand-author fragile `` markup). * * @package Classifai */ // phpcs:disable Squiz.PHP.Heredoc.NotAllowed, PluginCheck.CodeAnalysis.Heredoc.NotAllowed return <<<'INSTRUCTION' -The content returned should be valid WordPress block markup as described below, using elements like paragraphs and headings where appropriate. Be selective on the elements you use, defaulting to paragraphs. Please check the content before returning to ensure each element has proper opening and closing block markup and HTML tags and any required block attributes. Ensure elements don't nest inside each other, i.e. don't put a paragraph inside another paragraph or a list within a paragraph. Don't start the content with a heading, start with a paragraph. - -Markup available to use; don't use any other blocks, even if requested: - -

CONTENT

- - - -

CONTENT

- - - -
CONTENT
CONTENT
- - - -
-

CONTENT

-
- - - -

QUOTE

AUTHOR
- - - - - - - -
    -
  1. CONTENT
  2. -
- +Return the content as a single JSON object describing a flat "block tree". Do not return HTML, Markdown, block comment markup, code fences, or any prose. Output only the JSON object. + +The JSON object has this shape: +{ + "root": "", + "elements": { + "": { "key": "", "type": "", "props": { ... }, "children": ["", ...] } + } +} + +Rules: +- Every element has a unique string "key" matching its key in "elements". +- "children" is an ordered array of keys; omit it for blocks that hold no inner blocks. +- The root must be a single element. To return multiple top-level blocks, make the root an element of type "fragment" and list the top-level block keys in its "children". A "fragment" is a virtual wrapper only; give it no props. +- Default to paragraphs. Be selective with other blocks. Do not start the content with a heading; start with a paragraph. +- Use only the block types listed below; do not use any other blocks, even if requested. + +Block types and their props: +- "core/paragraph": props { "content": string }. No children. +- "core/heading": props { "content": string, "level": 2 or 3 }. No children. +- "core/list": props { "ordered": boolean (optional, default false) }. children: one or more "core/list-item". +- "core/list-item": props { "content": string }. No children. +- "core/quote": props { "citation": string (optional) }. children: one or more "core/paragraph". +- "core/pullquote": props { "value": string, "citation": string (optional) }. No children. +- "core/table": props { "body": [ { "cells": [ { "content": string, "tag": "td" } ] } ] }. Each row is an object with a "cells" array; each cell has "content" and "tag" ("td" for body cells). No children. +- "core/separator": props {}. No children. +- "core/image": props { "url": string, "alt": string (optional), "caption": string (optional) }. No children. +- "core/group": props { "layout": { "type": "constrained" } }. children: any blocks. Use to group related blocks. +- "core/columns": props {}. children: two or more "core/column". +- "core/column": props { "width": string (optional, e.g. "50%") }. children: any blocks. +- "core/buttons": props {}. children: one or more "core/button". +- "core/button": props { "text": string, "url": string (optional) }. No children. + +Example: +{"root":"r","elements":{"r":{"key":"r","type":"fragment","props":{},"children":["p1","h1","l1"]},"p1":{"key":"p1","type":"core/paragraph","props":{"content":"An opening paragraph."}},"h1":{"key":"h1","type":"core/heading","props":{"content":"A section heading","level":2}},"l1":{"key":"l1","type":"core/list","props":{"ordered":false},"children":["li1","li2"]},"li1":{"key":"li1","type":"core/list-item","props":{"content":"First point"}},"li2":{"key":"li2","type":"core/list-item","props":{"content":"Second point"}}}} INSTRUCTION; // phpcs:enable diff --git a/includes/Classifai/Providers/Azure/OpenAI.php b/includes/Classifai/Providers/Azure/OpenAI.php index a4fcb5854..2e8a28802 100644 --- a/includes/Classifai/Providers/Azure/OpenAI.php +++ b/includes/Classifai/Providers/Azure/OpenAI.php @@ -18,6 +18,7 @@ use function Classifai\sanitize_number_of_responses_field; use function Classifai\safe_wp_remote_post; use function Classifai\get_temperature; +use function Classifai\sanitize_generated_block_tree; class OpenAI extends Provider { @@ -954,8 +955,9 @@ public function generate_content( int $post_id = 0, array $args = array() ) { $body = apply_filters( 'classifai_azure_openai_content_request_body', array( - 'messages' => $messages, - 'temperature' => 0.9, + 'messages' => $messages, + 'temperature' => 0.9, + 'response_format' => array( 'type' => 'json_object' ), ), $post_id ); @@ -977,17 +979,26 @@ public function generate_content( int $post_id = 0, array $args = array() ) { return $response; } - // If we have a message, return it. - $return = ''; + // Pull the message content out of the response. + $content = ''; if ( ! empty( $response['choices'] ) ) { foreach ( $response['choices'] as $choice ) { if ( isset( $choice['message'], $choice['message']['content'] ) ) { - $return = wp_kses_post( trim( $choice['message']['content'], ' "\'' ) ); + $content = trim( $choice['message']['content'] ); } } } - return $return; + // The response should be a JSON BlockTree; validate before returning. + $decoded = json_decode( $content, true ); + if ( null === $decoded || JSON_ERROR_NONE !== json_last_error() ) { + return new WP_Error( 'invalid_content_response', esc_html__( 'The generated content was not in the expected format. Please try again.', 'classifai' ) ); + } + + // Sanitize the block tree's string values before they are rendered/saved. + $decoded = sanitize_generated_block_tree( $decoded ); + + return wp_json_encode( $decoded ); } /** diff --git a/includes/Classifai/Providers/Localhost/Ollama.php b/includes/Classifai/Providers/Localhost/Ollama.php index 75eda18a0..3446d9e1a 100644 --- a/includes/Classifai/Providers/Localhost/Ollama.php +++ b/includes/Classifai/Providers/Localhost/Ollama.php @@ -17,6 +17,7 @@ use function Classifai\get_default_prompt; use function Classifai\sanitize_number_of_responses_field; +use function Classifai\sanitize_generated_block_tree; /** * Ollama class @@ -787,6 +788,7 @@ public function generate_content( int $post_id = 0, array $args = array() ) { 'model' => $settings[ static::ID ]['model'] ?? '', 'messages' => $messages, 'stream' => false, + 'format' => 'json', ), $post_id ); @@ -804,13 +806,22 @@ public function generate_content( int $post_id = 0, array $args = array() ) { return $response; } - // If we have a message, return it. - $return = ''; + // Pull the message content out of the response. + $content = ''; if ( isset( $response['message'], $response['message']['content'] ) ) { - $return = wp_kses_post( trim( $response['message']['content'], ' "\'' ) ); + $content = trim( $response['message']['content'] ); } - return $return; + // The response should be a JSON BlockTree; validate before returning. + $decoded = json_decode( $content, true ); + if ( null === $decoded || JSON_ERROR_NONE !== json_last_error() ) { + return new WP_Error( 'invalid_content_response', esc_html__( 'The generated content was not in the expected format. Please try again.', 'classifai' ) ); + } + + // Sanitize the block tree's string values before they are rendered/saved. + $decoded = sanitize_generated_block_tree( $decoded ); + + return wp_json_encode( $decoded ); } /** diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index fc8ac255c..53c398eb7 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -22,6 +22,7 @@ use function Classifai\get_modified_image_source_url; use function Classifai\get_largest_size_and_dimensions_image_url; use function Classifai\get_temperature; +use function Classifai\sanitize_generated_block_tree; class ChatGPT extends Provider { @@ -1216,9 +1217,10 @@ public function generate_content( int $post_id = 0, array $args = array() ) { $body = apply_filters( 'classifai_chatgpt_content_request_body', array( - 'model' => $this->chatgpt_model, - 'messages' => $messages, - 'temperature' => 0.9, + 'model' => $this->chatgpt_model, + 'messages' => $messages, + 'temperature' => 0.9, + 'response_format' => array( 'type' => 'json_object' ), ), $post_id ); @@ -1235,17 +1237,26 @@ public function generate_content( int $post_id = 0, array $args = array() ) { return $response; } - // If we have a message, return it. - $return = ''; + // Pull the message content out of the response. + $content = ''; if ( ! empty( $response['choices'] ) ) { foreach ( $response['choices'] as $choice ) { if ( isset( $choice['message'], $choice['message']['content'] ) ) { - $return = wp_kses_post( trim( $choice['message']['content'], ' "\'' ) ); + $content = trim( $choice['message']['content'] ); } } } - return $return; + // The response should be a JSON BlockTree; validate before returning. + $decoded = json_decode( $content, true ); + if ( null === $decoded || JSON_ERROR_NONE !== json_last_error() ) { + return new WP_Error( 'invalid_content_response', esc_html__( 'The generated content was not in the expected format. Please try again.', 'classifai' ) ); + } + + // Sanitize the block tree's string values before they are rendered/saved. + $decoded = sanitize_generated_block_tree( $decoded ); + + return wp_json_encode( $decoded ); } /** From 92e2707973e4675119f692026be1f1c8004ac137 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 16 Jun 2026 12:45:09 -0600 Subject: [PATCH 3/9] Add a helper function to recursively sanitize JSON --- includes/Classifai/Helpers.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index 63908b64a..a4e6f0a2c 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -988,3 +988,36 @@ function get_temperature( float $temperature, int $results = 1 ): float { return (float) min( 2.0, $temperature + ( $results / 10 ) ); } + +/** + * Recursively sanitize the values of an AI-generated block tree. + * + * The Content Generation feature receives a JSON "block tree" from the model + * and renders its string props (paragraph and heading content, captions, list + * items, table cells, etc.) as HTML in the editor and saves them to post + * content. Sanitize every string value with wp_kses_post() and treat `url` + * props as URLs so untrusted markup (e.g. script tags or javascript: URLs) + * can't reach the browser. + * + * @param mixed $value Decoded block tree, or a nested value within it. + * @return mixed Sanitized value. + */ +function sanitize_generated_block_tree( $value ) { + if ( is_array( $value ) ) { + $sanitized = array(); + foreach ( $value as $key => $item ) { + if ( 'url' === $key && is_string( $item ) ) { + $sanitized[ $key ] = esc_url_raw( $item ); + } else { + $sanitized[ $key ] = sanitize_generated_block_tree( $item ); + } + } + return $sanitized; + } + + if ( is_string( $value ) ) { + return wp_kses_post( $value ); + } + + return $value; +} From 5fc4d5acfd6feb1ee0cc937f7f2f61f10beefaab Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 16 Jun 2026 12:46:30 -0600 Subject: [PATCH 4/9] Modify our client-side code to use the block renderer --- .../components/ai-response.tsx | 12 +- .../content-generation/components/chat-ui.tsx | 27 ++-- .../utils/render-block-tree.ts | 115 ++++++++++++++++++ src/js/types/wordpress.d.ts | 2 + 4 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 src/js/features/content-generation/utils/render-block-tree.ts diff --git a/src/js/features/content-generation/components/ai-response.tsx b/src/js/features/content-generation/components/ai-response.tsx index d90016e99..8d37fd22e 100644 --- a/src/js/features/content-generation/components/ai-response.tsx +++ b/src/js/features/content-generation/components/ai-response.tsx @@ -9,6 +9,11 @@ import React from 'react'; */ import { decodeEntities } from '@wordpress/html-entities'; +/** + * Internal dependencies + */ +import { renderBlockTreeToMarkup } from '../utils/render-block-tree'; + /** * Props for the AIResponse component */ @@ -32,10 +37,15 @@ const contentStyles: CSSProperties = { * @return {React.ReactElement} AI response container */ export const AIResponse: React.FC< AIResponseProps > = ( { content } ) => { + // The response is a JSON BlockTree; render it to readable block markup for + // the preview, falling back to the raw response if it isn't valid JSON. + const markup = renderBlockTreeToMarkup( content ); + const html = markup !== null ? markup : decodeEntities( content ); + return (
); }; diff --git a/src/js/features/content-generation/components/chat-ui.tsx b/src/js/features/content-generation/components/chat-ui.tsx index 36e50ed1e..6385671b0 100644 --- a/src/js/features/content-generation/components/chat-ui.tsx +++ b/src/js/features/content-generation/components/chat-ui.tsx @@ -24,6 +24,7 @@ import { ChatHistory } from './chat-history'; import { ErrorMessage } from './error-message'; import { ChatInput } from './chat-input'; import type { ConversationEntry } from './types'; +import { renderBlockTreeToMarkup } from '../utils/render-block-tree'; // Define style objects outside of JSX const chatContainerStyles: CSSProperties = { @@ -241,20 +242,24 @@ export const ChatUI: React.FC = () => { content: '', } ) .then( () => { - const contentWithEntities = decodeEntities( content ); - - const containsBlockMarkup = - contentWithEntities.includes( '` markup. We render it to valid markup here using the + * editor's native `@wordpress/blocks` (`createBlock` + `serialize`), so no + * Node-only renderer needs to be bundled. + * + * Callers must ensure the relevant block types are registered before + * rendering (the block editor registers core blocks automatically; other + * contexts such as the dashboard must call `registerCoreBlocks()` first). + */ + +/** + * WordPress dependencies + */ +import { createBlock, serialize } from '@wordpress/blocks'; + +/** + * External dependencies + */ +import { + blockTreeSchema, + FRAGMENT_BLOCK_TYPE, +} from '@10up/block-renderer-core'; + +type BlockTree = ReturnType< typeof blockTreeSchema.parse >; + +/** A WordPress block instance, as produced by `createBlock`. */ +type BlockInstance = ReturnType< typeof createBlock >; + +/** + * Recursively build block instances for a single element key. + * + * A `fragment` element is virtual: its children are promoted to the current + * level rather than wrapped in a real block. Visited keys are tracked to + * guard against malformed trees that reference each other in a cycle. + * + * @param {string} key Element key to resolve. + * @param {BlockTree} tree The full block tree. + * @param {Set} seen Keys already visited on this branch. + * @return {BlockInstance[]} Resolved block instances. + */ +function elementToBlocks( + key: string, + tree: BlockTree, + seen: Set< string > +): BlockInstance[] { + if ( seen.has( key ) ) { + return []; + } + seen.add( key ); + + const element = tree.elements[ key ]; + if ( ! element ) { + return []; + } + + const childKeys = element.children ?? []; + const innerBlocks = childKeys.flatMap( ( childKey ) => + elementToBlocks( childKey, tree, seen ) + ); + + // A fragment is a virtual wrapper: surface its children directly. + if ( element.type === FRAGMENT_BLOCK_TYPE ) { + return innerBlocks; + } + + return [ + createBlock( + element.type, + ( element.props ?? {} ) as Record< string, unknown >, + innerBlocks + ), + ]; +} + +/** + * Render a JSON BlockTree string to WordPress block markup. + * + * Returns `null` when the input is not a valid BlockTree (invalid JSON, fails + * schema validation, or produces no blocks) so callers can fall back to + * treating the response as raw HTML. + * + * @param {string} json The raw JSON string returned by the provider. + * @return {string|null} Serialized block markup, or `null` on failure. + */ +export function renderBlockTreeToMarkup( json: string ): string | null { + let data: unknown; + try { + data = JSON.parse( json ); + } catch { + return null; + } + + const result = blockTreeSchema.safeParse( data ); + if ( ! result.success ) { + return null; + } + + try { + const blocks = elementToBlocks( + result.data.root, + result.data, + new Set() + ); + if ( ! blocks.length ) { + return null; + } + return serialize( blocks ); + } catch { + return null; + } +} diff --git a/src/js/types/wordpress.d.ts b/src/js/types/wordpress.d.ts index 871df2551..29cfe7f09 100644 --- a/src/js/types/wordpress.d.ts +++ b/src/js/types/wordpress.d.ts @@ -28,6 +28,8 @@ declare module '@wordpress/blocks' { export const pasteHandler: any; export const parse: any; export const registerBlockType: any; + export const createBlock: any; + export const serialize: any; } declare module '@wordpress/commands' { From 7e7d7001fc34f9fb3d6a0f91f5e62b1a9de651f8 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 16 Jun 2026 12:46:44 -0600 Subject: [PATCH 5/9] Update tests --- tests/test-plugin/chatgpt-content.json | 18 ++++++++++++++++++ tests/test-plugin/e2e-test-plugin.php | 2 ++ 2 files changed, 20 insertions(+) create mode 100644 tests/test-plugin/chatgpt-content.json diff --git a/tests/test-plugin/chatgpt-content.json b/tests/test-plugin/chatgpt-content.json new file mode 100644 index 000000000..99d001a73 --- /dev/null +++ b/tests/test-plugin/chatgpt-content.json @@ -0,0 +1,18 @@ +{ + "id": "chatcmpl-123", + "object": "chat.completion", + "created": 1677652288, + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": "{\"root\":\"r\",\"elements\":{\"r\":{\"key\":\"r\",\"type\":\"core/paragraph\",\"props\":{\"content\":\"Hello there, how may I assist you today?\"}}}}" + }, + "finish_reason": "stop" + }], + "usage": { + "prompt_tokens": 9, + "completion_tokens": 12, + "total_tokens": 21 + } +} diff --git a/tests/test-plugin/e2e-test-plugin.php b/tests/test-plugin/e2e-test-plugin.php index 0ca0bb8d2..2bbd7b16b 100644 --- a/tests/test-plugin/e2e-test-plugin.php +++ b/tests/test-plugin/e2e-test-plugin.php @@ -61,6 +61,8 @@ function classifai_test_mock_http_requests( $preempt, $parsed_args, $url ) { $response = file_get_contents( __DIR__ . '/chatgpt-key-takeaways.json' ); } else if ( str_contains( $prompt, 'generating tags for an image uploaded' ) ) { $response = file_get_contents( __DIR__ . '/chatgpt-image-tags.json' ); + } else if ( str_contains( $prompt, 'copy editor drafting an article' ) ) { + $response = file_get_contents( __DIR__ . '/chatgpt-content.json' ); } } } elseif ( strpos( $url, 'https://api.openai.com/v1/moderations' ) !== false ) { From 9aa185dbcc7290059e719413a4b5e1dd24aa9bd9 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 16 Jun 2026 12:47:17 -0600 Subject: [PATCH 6/9] Update our quickdraft integration to build and save content client-side --- .../Features/QuickDraftIntegration.php | 16 ++---- .../content-generation/quick-draft/index.js | 51 ++++++++++++++++--- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/includes/Classifai/Features/QuickDraftIntegration.php b/includes/Classifai/Features/QuickDraftIntegration.php index 2b140cdca..56dbe13a0 100644 --- a/includes/Classifai/Features/QuickDraftIntegration.php +++ b/includes/Classifai/Features/QuickDraftIntegration.php @@ -206,19 +206,9 @@ public function endpoint_callback( WP_REST_Request $request ) { return $result; } - // Update the post with generated content. - $updated_post = array( - 'ID' => $post_id, - 'post_content' => $result, - 'post_status' => 'draft', - ); - - $update_result = wp_update_post( $updated_post ); - - if ( is_wp_error( $update_result ) ) { - return new WP_Error( 'post_update_failed', esc_html__( 'Failed to update post with generated content.', 'classifai' ) ); - } - + // $result is a JSON BlockTree. The draft is left empty here; the client + // renders it to block markup (using the editor's block registry) and + // saves it back via the core REST API before redirecting the user. return rest_ensure_response( array( 'post_id' => $post_id, diff --git a/src/js/features/content-generation/quick-draft/index.js b/src/js/features/content-generation/quick-draft/index.js index a1f12bc90..d2c81884a 100644 --- a/src/js/features/content-generation/quick-draft/index.js +++ b/src/js/features/content-generation/quick-draft/index.js @@ -7,12 +7,28 @@ */ import apiFetch from '@wordpress/api-fetch'; import domReady from '@wordpress/dom-ready'; +import { registerCoreBlocks } from '@wordpress/block-library'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import './index.scss'; +import { renderBlockTreeToMarkup } from '../utils/render-block-tree'; + +// Core blocks aren't registered on the dashboard, so register them once before +// we render an AI-generated BlockTree to markup. +let coreBlocksRegistered = false; + +/** + * Ensure core block types are registered before rendering. + */ +function ensureCoreBlocksRegistered() { + if ( ! coreBlocksRegistered ) { + registerCoreBlocks(); + coreBlocksRegistered = true; + } +} /** * Initialize Quick Draft content generation. @@ -160,18 +176,37 @@ domReady( () => { }, } ) .then( function ( response ) { - if ( response.success ) { - // Clear the form. - titleField.value = ''; - contentField.value = ''; - - // Show success message. - showSuccessMessage( response ); - } else { + if ( ! response.success ) { throw new Error( response.message || window.classifaiQuickDraft.error ); } + + // The response is a JSON BlockTree; render it to block markup + // and save it to the draft via the core REST API. + ensureCoreBlocksRegistered(); + const markup = + renderBlockTreeToMarkup( response.content ) ?? + response.content; + + return apiFetch( { + path: `/wp/v2/posts/${ response.post_id }`, + method: 'POST', + data: { + content: markup, + status: 'draft', + }, + } ).then( function () { + return response; + } ); + } ) + .then( function ( response ) { + // Clear the form. + titleField.value = ''; + contentField.value = ''; + + // Show success message. + showSuccessMessage( response ); } ) .catch( function ( error ) { showErrorMessage( From ba814efc28ab2376d9dd908a76063bd53a40d370 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 16 Jun 2026 12:50:03 -0600 Subject: [PATCH 7/9] Add unit tests for new helper function --- tests/Integration/HelpersTest.php | 106 ++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tests/Integration/HelpersTest.php b/tests/Integration/HelpersTest.php index 562f9ce00..4dafaa09b 100644 --- a/tests/Integration/HelpersTest.php +++ b/tests/Integration/HelpersTest.php @@ -564,4 +564,110 @@ public function test_safe_file_get_contents_remote_and_local() { $this->assertSame( 'local-body', safe_file_get_contents( $tmp ) ); unlink( $tmp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink } + + /** + * sanitize_generated_block_tree() strips disallowed markup (e.g. scripts and + * event-handler attributes) from string props while preserving allowed + * inline HTML. + */ + function test_sanitize_generated_block_tree_strips_unsafe_html() { + $tree = array( + 'root' => 'p1', + 'elements' => array( + 'p1' => array( + 'key' => 'p1', + 'type' => 'core/paragraph', + 'props' => array( + 'content' => 'Hello world', + ), + ), + ), + ); + + $sanitized = sanitize_generated_block_tree( $tree ); + $content = $sanitized['elements']['p1']['props']['content']; + + $this->assertStringContainsString( 'world', $content, 'Allowed inline HTML should be preserved.' ); + $this->assertStringNotContainsString( '', + 'tag' => 'td', + ), + ), + ), + ), + ), + ), + 'l1' => array( + 'key' => 'l1', + 'type' => 'core/list', + 'props' => array( + 'ordered' => true, + ), + ), + ), + ); + + $sanitized = sanitize_generated_block_tree( $tree ); + + // Non-string scalars are preserved as-is. + $this->assertSame( 2, $sanitized['elements']['h1']['props']['level'] ); + $this->assertTrue( $sanitized['elements']['l1']['props']['ordered'] ); + + // Nested string values are sanitized. + $cell = $sanitized['elements']['t1']['props']['body'][0]['cells'][0]; + $this->assertStringNotContainsString( '"}}}}'; + + $encoded = wp_json_encode( sanitize_generated_block_tree( json_decode( $json ) ) ); + + $this->assertStringContainsString( '"props":{}', $encoded, 'Empty objects must remain objects.' ); + $this->assertStringNotContainsString( '"props":[]', $encoded, 'Empty objects must not become arrays.' ); + $this->assertStringNotContainsString( '