From 406a0c1bc8bd973519d76e8cae0d75039160475c Mon Sep 17 00:00:00 2001 From: romain-pm Date: Tue, 21 Jul 2026 21:21:39 +0200 Subject: [PATCH 1/2] feat: server-side JS field validators for extended inputs (#158) Re-enforces field constraints on form submission, in JavaScript, so a client bypassing browser validation cannot store out-of-range values. formidable-engine: - depends on the javascript-modules SDK (JSServerExtensionInvoker), imported from the javascript-modules-engine bundle (feature/js-server-extensions-sdk) - new validateFieldsWithJs pipeline step (after validateRequired): loads each submitted field's node (system session) and runs JS validators registered for its type; a violation rejects the submission with FMDB-010. Fails closed on validator error; skipped entirely when the SDK service is absent - FormFieldMetadataCollector now carries field node identifiers - JsFieldValidator bridges the pipeline to the SDK invoker formidable-extended-inputs: - registerFormFieldValidator(props, fn) JS API (server.registry.add) - validators for rating (1..max int), scale (min..max by step), switch (true/false), consent (true, required-aware) Verified end-to-end on Jahia 8.2.x: valid values (incl. edges) accepted; forged rating/scale/switch/consent values rejected with FMDB-010; 149 engine tests green. --- .../jahia-content-create-content/SKILL.md | 67 +- .../jahia-content-explore-structure/SKILL.md | 3 + .../jahia-content-move-content/SKILL.md | 15 +- .../jahia-content-query-content/SKILL.md | 11 +- .../jahia-content-translate-content/SKILL.md | 15 +- .agents/skills/jahia-content/SKILL.md | 26 +- .../skills/jahia-dev-accessibility/SKILL.md | 36 +- .agents/skills/jahia-dev-apis/SKILL.md | 30 +- .../references/authentication.md | 34 +- .../jahia-dev-apis/references/graphql.md | 145 +- .../jahia-dev-apis/references/jcr-api.md | 32 +- .../jahia-dev-apis/references/security.md | 144 +- .../skills/jahia-dev-build-component/SKILL.md | 3 + .../jahia-dev-create-page-template/SKILL.md | 57 +- .../jahia-dev-create-template-set/SKILL.md | 45 +- .agents/skills/jahia-dev-create-view/SKILL.md | 238 +- .agents/skills/jahia-dev-cypress/SKILL.md | 504 +- .agents/skills/jahia-dev-debug/SKILL.md | 26 +- .../jahia-dev-define-content-type/SKILL.md | 232 +- .agents/skills/jahia-dev-import-from/SKILL.md | 31 +- .agents/skills/jahia-dev-java/SKILL.md | 25 +- .../jahia-dev-java/references/backend.md | 66 +- .../references/content-types.md | 111 +- .../jahia-dev-java/references/modules.md | 20 +- .../skills/jahia-dev-java/references/osgi.md | 39 +- .../jahia-dev-java/references/rendering.md | 38 +- .../references/ui-extensions.md | 202 +- .agents/skills/jahia-dev-jexperience/SKILL.md | 35 +- .agents/skills/jahia-dev-ops/SKILL.md | 11 +- .../skills/jahia-dev-ops/references/docker.md | 74 +- .../jahia-dev-ops/references/monitoring.md | 52 +- .../jahia-dev-ops/references/provisioning.md | 3 +- .agents/skills/jahia-dev-osgi-module/SKILL.md | 35 +- .agents/skills/jahia-dev-properties/SKILL.md | 35 +- .../references/all-properties.md | 302 +- .../skills/jahia-dev-query-content/SKILL.md | 125 +- .agents/skills/jahia-dev-review/SKILL.md | 5 + .agents/skills/jahia-dev-screenshot/SKILL.md | 10 + .agents/skills/jahia-dev-start-local/SKILL.md | 4 + .../skills/jahia-dev-ui-extension/SKILL.md | 273 +- .agents/skills/jahia-dev/SKILL.md | 14 +- .../skills/jahia-java-concurrency/SKILL.md | 28 +- .agents/skills/jahia-java-jcr/SKILL.md | 2 + .agents/skills/jahia-java-osgi/SKILL.md | 1 + .../skills/jahia-java-persistence/SKILL.md | 7 + .agents/skills/jahia-java-security/SKILL.md | 16 +- .agents/skills/jahia-review-java/SKILL.md | 26 +- .../references/code-review-output.md | 18 +- .agents/skills/jahia/SKILL.md | 32 +- .chachalog/config.mjs | 30 +- .../instructions/changelog.instructions.md | 30 +- .github/instructions/copilot.instructions.md | 1 + .github/release.yml | 2 +- .github/workflows/delivery-pr-chores.yml | 2 +- .github/workflows/docs-academy-publish.yml | 2 +- .github/workflows/docs-academy-update.yml | 2 +- .github/workflows/nightly-RL.yml | 2 +- .github/workflows/nightly-SN.yml | 3 +- .github/workflows/on-code-change.yml | 2 +- .github/workflows/on-merge.yml | 6 +- .harness/spec-formidable-results-screen.md | 76 +- .yarnrc.yml | 1 - AGENTS.md | 120 +- CHANGELOG.md | 62 +- CONTEXT.md | 75 +- README.md | 6 + docs/captcha-server-side-validation.md | 46 +- docs/custom-validation.md | 96 +- docs/error-codes.md | 30 +- docs/export.md | 74 +- docs/form-submission-flow.md | 142 +- ...ws-and-elements-from-third-party-module.md | 13 +- docs/results-permissions.md | 33 +- docs/why-queryselector-in-react.md | 13 +- formidable-elements/.m2/sync-version.js | 19 +- formidable-elements/.vscode/extensions.json | 2 +- formidable-elements/.vscode/settings.json | 12 +- .../fieldsets/fmdb_checkbox.json | 1 - .../fieldsets/fmdb_radio.json | 1 - .../forms/fmdbmix_buttons.json | 31 +- .../forms/fmdbmix_multistep.json | 6 +- .../fmdbmix_rangeValidationMessages.json | 3 +- .../forms/fmdbmix_responses.json | 7 +- .../forms/fmdbmix_style.json | 6 +- .../forms/fmdbmix_textValidationMessages.json | 3 +- .../forms/fmdbmix_validationMessages.json | 6 +- .../components/Fieldset/default.server.tsx | 17 +- .../src/components/Form/CSS-VARIABLES.md | 71 +- .../src/components/Form/Captcha.client.tsx | 52 +- .../components/Form/Form.client.module.css | 7 +- .../src/components/Form/Form.client.tsx | 158 +- .../src/components/Form/cm.server.tsx | 6 +- .../src/components/Form/default.server.tsx | 144 +- .../src/components/Form/types.ts | 9 +- .../FormContainer/LogicAwareRender.tsx | 31 +- .../FormContainer/hidden.logic.server.tsx | 41 +- .../components/FormReference/cm.server.tsx | 6 +- .../FormReference/default.server.tsx | 9 +- .../Input/Button/default.server.tsx | 20 +- .../Input/Checkbox/Checkbox.client.tsx | 25 +- .../Input/Checkbox/default.server.tsx | 48 +- .../components/Input/Color/default.server.tsx | 31 +- .../components/Input/Date/default.server.tsx | 47 +- .../Input/DatetimeLocal/default.server.tsx | 55 +- .../components/Input/Email/default.server.tsx | 44 +- .../src/components/Input/File/File.client.tsx | 117 +- .../components/Input/File/default.server.tsx | 32 +- .../Input/Hidden/default.server.tsx | 21 +- .../components/Input/Radio/default.server.tsx | 44 +- .../components/Input/Text/default.server.tsx | 85 +- .../components/RichText/default.server.tsx | 30 +- .../src/components/Select/default.server.tsx | 45 +- .../src/components/Step/compact.server.tsx | 21 +- .../src/components/Step/default.server.tsx | 23 +- .../src/components/Step/minimal.server.tsx | 19 +- .../components/Textarea/default.server.tsx | 63 +- .../src/design/HelpText/HelpText.tsx | 6 +- .../src/design/HelpText/index.ts | 4 +- .../src/design/Spinner/CSS-VARIABLES.md | 81 +- .../src/design/Spinner/Spinner.module.css | 8 +- .../src/design/Spinner/Spinner.tsx | 30 +- .../src/design/Spinner/index.ts | 6 +- formidable-elements/src/design/validation.css | 1 - .../src/hooks/useCustomFormValidation.ts | 37 +- .../src/hooks/useFormSubmission.ts | 55 +- formidable-elements/src/hooks/useMask.tsx | 31 +- formidable-elements/src/hooks/useMultiStep.ts | 39 +- formidable-elements/src/types.d.ts | 6 +- formidable-elements/src/utils/choiceUtils.ts | 13 +- .../src/utils/conditionalLogic.ts | 164 +- formidable-elements/src/utils/fileUtils.ts | 8 +- formidable-elements/src/utils/messageUtils.ts | 22 +- .../src/utils/richTextUtils.ts | 7 +- .../src/utils/validationProps.ts | 43 +- .../src/utils/validationUtils.ts | 130 +- formidable-elements/tsconfig.json | 17 +- formidable-elements/vite.config.mjs | 10 +- formidable-engine/pom.xml | 18 + .../ConditionalLogic.types.ts | 136 +- .../ConditionalLogic.utils.ts | 410 +- .../ConditionalLogic/ConditionalLogicCmp.tsx | 755 +- .../src/javascript/ConditionalLogic/README.md | 16 +- .../ConditionalLogic/graphql/index.ts | 3 +- .../ConditionalLogic/graphql/queries.ts | 101 +- .../FormResults/FormResults.utils.ts | 316 +- .../javascript/FormResults/FormResultsApp.tsx | 507 +- .../components/FilePreviewDialog.tsx | 210 +- .../components/FormResultsList.tsx | 171 +- .../components/SubmissionDetailPanel.tsx | 534 +- .../components/SubmissionsTable.tsx | 503 +- .../FormResults/components/index.ts | 9 +- .../delete/DeleteResultsDialog.tsx | 505 +- .../javascript/FormResults/delete/index.ts | 2 +- .../export/ExportResultsDialog.tsx | 508 +- .../FormResults/export/export.utils.ts | 45 +- .../export/formats/ExportFormat.ts | 17 +- .../FormResults/export/formats/csv.ts | 118 +- .../FormResults/export/formats/index.ts | 13 +- .../FormResults/export/formats/json.ts | 100 +- .../javascript/FormResults/export/index.ts | 3 +- .../javascript/FormResults/graphql/index.ts | 10 +- .../javascript/FormResults/graphql/queries.ts | 241 +- .../src/javascript/FormResults/index.ts | 3 +- .../SelectOptions/SelectOptionsCmp.tsx | 133 +- .../src/javascript/graphql/fragments.ts | 15 +- .../src/javascript/graphql/index.ts | 3 +- formidable-engine/src/javascript/init.tsx | 112 +- .../servlet/FormFieldMetadataCollector.java | 16 +- .../servlet/FormSubmissionPipeline.java | 66 + .../engine/servlet/FormSubmitServlet.java | 17 +- .../engine/servlet/JsFieldValidator.java | 113 + .../fieldsets/fmdbmix_formLogicElement.json | 14 +- .../forms/fmdbmix_formLogicElement.json | 34 +- .../javascript/ckeditor/helpTextConfig.js | 12 +- .../main/resources/javascript/locales/en.json | 250 +- .../main/resources/javascript/locales/fr.json | 250 +- .../servlet/FormSubmissionPipelineTest.java | 2 +- formidable-engine/tsconfig.json | 44 +- .../framework/registerFormFieldValidator.ts | 68 + .../src/registerFieldValidators.server.tsx | 58 + .../package.json | 104 +- .../forms/fmdbsamplemix_customStyle.json | 19 +- .../Fieldset/customStyle.server.tsx | 62 +- .../tsconfig.json | 61 +- .../vite.config.mjs | 10 +- .../package.json | 98 +- .../src/templates/Layout.tsx | 2 +- .../tsconfig.json | 61 +- .../vite.config.mjs | 10 +- schema.graphql | 7959 ++++++++--------- tests/assets/provisioning.yaml | 6 +- tests/cypress.config.ts | 72 +- .../e2e/fields/20-all-field-types.cy.ts | 469 +- .../21-file-multiple-restrictions.cy.ts | 57 +- tests/cypress/e2e/fields/210-help-text.cy.ts | 533 +- tests/cypress/e2e/fields/22-checkbox.cy.ts | 84 +- tests/cypress/e2e/fields/23-radio.cy.ts | 73 +- .../e2e/fields/24-select-options.cy.ts | 75 +- tests/cypress/e2e/fields/25-richtext.cy.ts | 58 +- .../26-email-pattern-suggestions-length.cy.ts | 101 +- tests/cypress/e2e/fields/27-fieldset.cy.ts | 69 +- .../fields/28-file-save-all-fixtures.cy.ts | 339 +- .../fields/29-file-fake-txt-rejected.cy.ts | 66 +- tests/cypress/e2e/fields/support.ts | 28 +- .../60-content-integrity-full-form.cy.ts | 240 +- ...content-integrity-negative-detection.cy.ts | 119 +- ...integrity-conditional-logic-negative.cy.ts | 146 +- ...ontent-integrity-submission-deletion.cy.ts | 125 +- .../64-content-integrity-clean-deletion.cy.ts | 99 +- ...ntegrity-submission-payload-semantic.cy.ts | 171 +- ...-content-integrity-reference-targets.cy.ts | 151 +- .../50-conditional-logic-selector-type.cy.ts | 106 +- .../51-conditional-logic-copy-paste.cy.ts | 397 +- .../logics/52-conditional-logic-backend.cy.ts | 115 +- .../logics/53-conditional-logic-import.cy.ts | 125 +- .../e2e/security/40-direct-http-guards.cy.ts | 389 +- .../security/41-submission-tampering.cy.ts | 302 +- tests/cypress/e2e/security/support.ts | 181 +- .../cypress/e2e/support/useFormidableSite.ts | 28 +- .../validation/30-required-validation.cy.ts | 347 +- .../validation/31-textual-validation.cy.ts | 302 +- .../e2e/validation/32-range-validation.cy.ts | 176 +- .../e2e/validation/33-validation-flow.cy.ts | 181 +- .../validation/34-multistep-validation.cy.ts | 210 +- tests/cypress/e2e/validation/support.ts | 28 +- .../page-object/ConditionalLogicEditor.ts | 55 +- .../page-object/ConditionalLogicField.ts | 296 +- tests/cypress/page-object/Fieldset.ts | 289 +- tests/cypress/page-object/Form.ts | 522 +- .../page-object/elements/ButtonInput.ts | 64 +- .../page-object/elements/CheckboxGroup.ts | 143 +- .../page-object/elements/CheckboxInput.ts | 97 +- .../page-object/elements/ColorInput.ts | 29 +- .../cypress/page-object/elements/DateInput.ts | 73 +- .../elements/DateTimeLocalInput.ts | 89 +- .../page-object/elements/EmailInput.ts | 123 +- .../cypress/page-object/elements/FileInput.ts | 237 +- .../page-object/elements/FormElement.ts | 220 +- .../page-object/elements/HiddenInput.ts | 53 +- .../page-object/elements/RadioGroup.ts | 167 +- .../page-object/elements/RadioInput.ts | 68 +- .../page-object/elements/SelectInput.ts | 155 +- .../cypress/page-object/elements/TextInput.ts | 190 +- .../page-object/elements/TextareaInput.ts | 204 +- tests/cypress/page-object/index.ts | 41 +- tests/cypress/plugins/env.js | 4 +- tests/cypress/plugins/index.js | 36 +- tests/cypress/support/constants.ts | 31 +- tests/cypress/support/contentIntegrity.ts | 696 +- tests/cypress/support/e2e.js | 41 +- tests/cypress/support/fixtures/fieldset.ts | 22 +- tests/cypress/support/fixtures/forms.ts | 371 +- tests/cypress/support/fixtures/index.ts | 38 +- tests/cypress/support/fixtures/inputButton.ts | 45 +- .../cypress/support/fixtures/inputCheckbox.ts | 67 +- tests/cypress/support/fixtures/inputColor.ts | 26 +- tests/cypress/support/fixtures/inputDate.ts | 39 +- .../support/fixtures/inputDatetimeLocal.ts | 49 +- tests/cypress/support/fixtures/inputEmail.ts | 76 +- tests/cypress/support/fixtures/inputFile.ts | 41 +- tests/cypress/support/fixtures/inputText.ts | 96 +- tests/cypress/support/fixtures/logics.ts | 578 +- tests/cypress/support/fixtures/radio.ts | 65 +- tests/cypress/support/fixtures/richText.ts | 20 +- tests/cypress/support/fixtures/select.ts | 109 +- tests/cypress/support/fixtures/site.ts | 34 +- tests/cypress/support/fixtures/step.ts | 22 +- tests/cypress/support/fixtures/textarea.ts | 107 +- tests/cypress/support/fixtures/types.ts | 186 +- tests/cypress/support/fixtures/validation.ts | 74 +- tests/cypress/tsconfig.json | 13 +- tests/docker-compose.yml | 12 +- tests/lib-tsconfig.json | 6 +- tests/package.json | 70 +- tests/provisioning-manifest-build.yml | 6 +- tests/provisioning-manifest-snapshot.yml | 18 +- tests/scenarios/README.md | 1 + tests/scenarios/content-integrity.md | 86 +- tests/scenarios/logics.md | 14 +- tests/scenarios/permissions.md | 14 +- tests/scenarios/security.md | 74 + tests/scenarios/todo/conditional-logic.md | 17 + tests/tsconfig.json | 13 +- 283 files changed, 17141 insertions(+), 16314 deletions(-) create mode 100644 formidable-engine/src/main/java/org/jahia/modules/formidable/engine/servlet/JsFieldValidator.java create mode 100644 formidable-extended-inputs/src/framework/registerFormFieldValidator.ts create mode 100644 formidable-extended-inputs/src/registerFieldValidators.server.tsx diff --git a/.agents/skills/jahia-content-create-content/SKILL.md b/.agents/skills/jahia-content-create-content/SKILL.md index b53b90ed..d9cab872 100644 --- a/.agents/skills/jahia-content-create-content/SKILL.md +++ b/.agents/skills/jahia-content-create-content/SKILL.md @@ -16,6 +16,7 @@ Creates content nodes in a running Jahia instance using the GraphQL JCR mutation - GraphQL endpoint: `http://localhost:8080/modules/graphql` **Auth pattern — always use both flags:** + ```bash curl -u root:root1234 \ -H "Content-Type: application/json" \ @@ -64,6 +65,7 @@ wait # all uploads complete in parallel > ⚠️ Always include `mixins: ["jmix:image"]` in the upload. Without it, the file node **cannot be used as a WEAKREFERENCE** in image properties. To collect UUIDs after parallel uploads, query them in one batch: + ```bash curl -s -u root:root1234 -H "Content-Type: application/json" -H "Origin: http://localhost:8080" \ -X POST http://localhost:8080/modules/graphql \ @@ -113,8 +115,18 @@ curl -s -u root:root1234 \ ``` The response contains the UUID: + ```json -{"data":{"jcr":{"addNode":{"addChild":{"content":{"setValue":true},"contentType":{"setValue":true}},"uuid":"xxxxxxxx-..."}}}} +{ + "data": { + "jcr": { + "addNode": { + "addChild": { "content": { "setValue": true }, "contentType": { "setValue": true } }, + "uuid": "xxxxxxxx-..." + } + } + } +} ``` ### Use a file UUID as an image property @@ -128,6 +140,7 @@ properties: [ ``` > After uploading, publish the files folder so images are accessible on the live site: +> > ```bash > curl -s -u root:root1234 -H "Content-Type: application/json" -H "Origin: http://localhost:8080" \ > -X POST http://localhost:8080/modules/graphql \ @@ -202,6 +215,7 @@ curl -s -u root:root1234 -H "Content-Type: application/json" -H "Origin: http:// ## Step 1 — Identify target site and content folder Standard content folder paths: + - `/sites//contents/articles/` — for article nodes - `/sites//contents/tutorials/` — for tutorial nodes - `/sites//contents/` — for any other content folder @@ -238,15 +252,16 @@ curl -s -u root:root1234 \ ### Property rules -| Situation | GraphQL syntax | -|-----------|---------------| -| i18n property (declared `i18n` in CND) | `{name: "body", value: "...", language: "en"}` | -| Non-i18n property | `{name: "product", value: "jahia"}` | -| Title (from `mix:title`) | `{name: "jcr:title", value: "...", language: "en"}` | -| Date property | `{name: "updatedAt", value: "2024-01-15T00:00:00.000Z", type: DATE}` | -| Multiple values | `{name: "tags", values: ["a", "b"]}` | +| Situation | GraphQL syntax | +| -------------------------------------- | -------------------------------------------------------------------- | +| i18n property (declared `i18n` in CND) | `{name: "body", value: "...", language: "en"}` | +| Non-i18n property | `{name: "product", value: "jahia"}` | +| Title (from `mix:title`) | `{name: "jcr:title", value: "...", language: "en"}` | +| Date property | `{name: "updatedAt", value: "2024-01-15T00:00:00.000Z", type: DATE}` | +| Multiple values | `{name: "tags", values: ["a", "b"]}` | ### Node name rules + - Use lowercase kebab-case: `my-article`, `getting-started` - No spaces, no special characters - Must be unique within the parent folder @@ -314,14 +329,14 @@ curl -s -u root:root1234 \ ## Common errors -| Error | Cause | Fix | -|-------|-------|-----| -| `Permission denied` | Missing `Origin` header | Add `-H "Origin: http://localhost:8080"` | -| `Couldn't find definition for property X` | Wrong property name or non-i18n prop given with `language:` | Check CND definition; remove `language:` for non-i18n props | -| `ConstraintViolationException: mandatory property` | A mandatory CND property was not provided | Provide all mandatory properties | -| `ItemExistsException` | Node name already taken | Use `useAvailableNodeName: true` or choose a different name | -| WEAKREFERENCE image constraint error | Uploaded file missing `jmix:image` mixin | Always include `mixins: ["jmix:image"]` in the `addNode` upload mutation | -| `deletePropertiesBatch fails with missing required fields` | `language` is NON_NULL in `InputJCRDeletedProperty` — required even for non-i18n properties | Always provide `language: "en"` in every `deletePropertiesBatch` entry | +| Error | Cause | Fix | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| `Permission denied` | Missing `Origin` header | Add `-H "Origin: http://localhost:8080"` | +| `Couldn't find definition for property X` | Wrong property name or non-i18n prop given with `language:` | Check CND definition; remove `language:` for non-i18n props | +| `ConstraintViolationException: mandatory property` | A mandatory CND property was not provided | Provide all mandatory properties | +| `ItemExistsException` | Node name already taken | Use `useAvailableNodeName: true` or choose a different name | +| WEAKREFERENCE image constraint error | Uploaded file missing `jmix:image` mixin | Always include `mixins: ["jmix:image"]` in the `addNode` upload mutation | +| `deletePropertiesBatch fails with missing required fields` | `language` is NON_NULL in `InputJCRDeletedProperty` — required even for non-i18n properties | Always provide `language: "en"` in every `deletePropertiesBatch` entry | --- @@ -339,9 +354,9 @@ mutation { jcr { mutateNode(pathOrId: "/sites/mySite/home/features/my-card") { addMixins(mixins: ["jmix:internalLink"]) - setPropertiesBatch(properties: [ - {name: "j:linkType", value: "internal"} - ]) { path } + setPropertiesBatch(properties: [{ name: "j:linkType", value: "internal" }]) { + path + } } } } @@ -365,11 +380,15 @@ mutation { jcr { mutateNode(pathOrId: "/sites/mySite/home/features/my-card") { addMixins(mixins: ["jmix:externalLink"]) - setPropertiesBatch(properties: [ - {name: "j:linkType", value: "external"} - {name: "j:url", value: "https://example.com", language: "en"} - {name: "j:linkTitle", value: "Visit Example", language: "en"} - ]) { path } + setPropertiesBatch( + properties: [ + { name: "j:linkType", value: "external" } + { name: "j:url", value: "https://example.com", language: "en" } + { name: "j:linkTitle", value: "Visit Example", language: "en" } + ] + ) { + path + } } } } diff --git a/.agents/skills/jahia-content-explore-structure/SKILL.md b/.agents/skills/jahia-content-explore-structure/SKILL.md index fcefbe2e..d23d3e95 100644 --- a/.agents/skills/jahia-content-explore-structure/SKILL.md +++ b/.agents/skills/jahia-content-explore-structure/SKILL.md @@ -25,6 +25,7 @@ Use this skill **before** creating content on an unfamiliar Jahia site. It produ Use GraphQL aliases to retrieve everything you need in a **single HTTP request**: site metadata, page structure, file assets, and available content types. First, find the site key: + ```bash curl -s -u root:root1234 -H "Content-Type: application/json" -H "Origin: http://localhost:8080" \ -X POST http://localhost:8080/modules/graphql \ @@ -32,6 +33,7 @@ curl -s -u root:root1234 -H "Content-Type: application/json" -H "Origin: http:// ``` Then run the full batch query (replace `SITE_KEY` and `TEMPLATE_MODULE`): + ```bash curl -s -u root:root1234 \ -H "Content-Type: application/json" -H "Origin: http://localhost:8080" \ @@ -40,6 +42,7 @@ curl -s -u root:root1234 \ ``` From the response: + - `site.properties` → `j:templatesSet` (the template module name) and `j:defaultLanguage` - `home.children` → page area structure; look for area nodes and check their children to see which content types are in use - `files.children` → existing file folders and UUIDs diff --git a/.agents/skills/jahia-content-move-content/SKILL.md b/.agents/skills/jahia-content-move-content/SKILL.md index f86a0752..9f22e45c 100644 --- a/.agents/skills/jahia-content-move-content/SKILL.md +++ b/.agents/skills/jahia-content-move-content/SKILL.md @@ -16,6 +16,7 @@ Reorganizes the JCR content tree — moving nodes into sub-folders, renaming the - GraphQL endpoint: `http://localhost:8080/modules/graphql` **Always include both auth flags:** + ```bash curl -s -u root:root1234 \ -H "Content-Type: application/json" \ @@ -214,13 +215,13 @@ curl -s -u root:root1234 \ ## Common errors -| Error | Cause | Fix | -|-------|-------|-----| -| `Permission denied` | Missing `Origin` header | Add `-H "Origin: http://localhost:8080"` | -| `ItemExistsException` | A node with that name already exists at the destination | Choose a different name or use `rename` after moving | -| `PathNotFoundException` | Source or destination path doesn't exist | Verify paths with `nodeByPath` first | -| `move` returns `null` | Node was already at that location | Verify the current path first | -| Content disappears from live after move | Move unpublishes — normal JCR behaviour | Run `publish` after every move | +| Error | Cause | Fix | +| --------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------- | +| `Permission denied` | Missing `Origin` header | Add `-H "Origin: http://localhost:8080"` | +| `ItemExistsException` | A node with that name already exists at the destination | Choose a different name or use `rename` after moving | +| `PathNotFoundException` | Source or destination path doesn't exist | Verify paths with `nodeByPath` first | +| `move` returns `null` | Node was already at that location | Verify the current path first | +| Content disappears from live after move | Move unpublishes — normal JCR behaviour | Run `publish` after every move | --- diff --git a/.agents/skills/jahia-content-query-content/SKILL.md b/.agents/skills/jahia-content-query-content/SKILL.md index ebeea30a..2893f7d7 100644 --- a/.agents/skills/jahia-content-query-content/SKILL.md +++ b/.agents/skills/jahia-content-query-content/SKILL.md @@ -16,6 +16,7 @@ Retrieves JCR content from a running Jahia instance using the GraphQL JCR query - GraphQL endpoint: `http://localhost:8080/modules/graphql` **Auth pattern — always use both flags:** + ```bash curl -u root:root1234 \ -H "Content-Type: application/json" \ @@ -130,11 +131,11 @@ SELECT * FROM [ns:typeName] WHERE ISDESCENDANTNODE('/sites/mySite') ORDER BY [jc ## Common errors -| Error | Cause | Fix | -|-------|-------|-----| -| `Permission denied` | Missing `Origin` header | Add `-H "Origin: http://localhost:8080"` | -| i18n properties returned empty | `language:` not specified | Add `language: "en"` to `properties()` call | -| Node not found | Wrong path or node doesn't exist | Verify path with `nodeByPath(path: "/sites")` first | +| Error | Cause | Fix | +| ------------------------------ | -------------------------------- | --------------------------------------------------- | +| `Permission denied` | Missing `Origin` header | Add `-H "Origin: http://localhost:8080"` | +| i18n properties returned empty | `language:` not specified | Add `language: "en"` to `properties()` call | +| Node not found | Wrong path or node doesn't exist | Verify path with `nodeByPath(path: "/sites")` first | --- diff --git a/.agents/skills/jahia-content-translate-content/SKILL.md b/.agents/skills/jahia-content-translate-content/SKILL.md index 7c0c87ad..97f04dde 100644 --- a/.agents/skills/jahia-content-translate-content/SKILL.md +++ b/.agents/skills/jahia-content-translate-content/SKILL.md @@ -16,6 +16,7 @@ Adds languages to a Jahia site and populates i18n properties on existing content - GraphQL endpoint: `http://localhost:8080/modules/graphql` **Always include both auth flags:** + ```bash curl -s -u root:root1234 \ -H "Content-Type: application/json" \ @@ -274,13 +275,13 @@ curl -s -u root:root1234 \ ## Common errors -| Error | Cause | Fix | -|-------|-------|-----| -| `Permission denied` | Missing `Origin` header | Add `-H "Origin: http://localhost:8080"` | -| i18n property returned empty after set | Missing `language:` in `properties()` query | Add `language: "fr"` to read call | -| `ConstraintViolationException` on title set | Mandatory i18n field not set first | Use `setPropertiesBatch` with all mandatory fields in one call | -| Language not appearing in site | `j:languages` mutation only had the new language | Pass the full list: `["en", "fr"]` | -| Choicelist key changed per language | Property incorrectly declared `i18n` in CND | Remove `i18n` from the CND property; translate keys in the view | +| Error | Cause | Fix | +| ------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------- | +| `Permission denied` | Missing `Origin` header | Add `-H "Origin: http://localhost:8080"` | +| i18n property returned empty after set | Missing `language:` in `properties()` query | Add `language: "fr"` to read call | +| `ConstraintViolationException` on title set | Mandatory i18n field not set first | Use `setPropertiesBatch` with all mandatory fields in one call | +| Language not appearing in site | `j:languages` mutation only had the new language | Pass the full list: `["en", "fr"]` | +| Choicelist key changed per language | Property incorrectly declared `i18n` in CND | Remove `i18n` from the CND property; translate keys in the view | --- diff --git a/.agents/skills/jahia-content/SKILL.md b/.agents/skills/jahia-content/SKILL.md index f293ec7f..cbe52994 100644 --- a/.agents/skills/jahia-content/SKILL.md +++ b/.agents/skills/jahia-content/SKILL.md @@ -25,6 +25,7 @@ curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/cms/login Run both checks in parallel to understand what's currently in the CMS: ### A. List available sites + ```bash curl -s -u root:root1234 \ -H "Content-Type: application/json" \ @@ -34,6 +35,7 @@ curl -s -u root:root1234 \ ``` ### B. List top-level content folders + ```bash curl -s -u root:root1234 \ -H "Content-Type: application/json" \ @@ -60,16 +62,16 @@ curl -s -u root:root1234 \ Use the task description to pick the right skill(s): -| What the user wants to do | Skill | -|---------------------------|-------| -| Explore an unknown site's content types, property names, enum values, mixins | **`/jahia-content-explore-structure`** | -| Find out what content exists, audit the tree, run a search | **`/jahia-content-query-content`** | -| Create pages, articles, tutorials, folders, populate a site | **`/jahia-content-create-content`** | -| Move, rename, restructure content into sub-folders | **`/jahia-content-move-content`** | -| Translate existing content to another language | **`/jahia-content-translate-content`** | -| Publish content to the live site | Use `publish` mutation (see below) | -| Delete content | Use `deleteNode` mutation (see below) | -| Do several of the above in sequence | Run the skills in order — start with **explore-structure** if site is unfamiliar, then create or move | +| What the user wants to do | Skill | +| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| Explore an unknown site's content types, property names, enum values, mixins | **`/jahia-content-explore-structure`** | +| Find out what content exists, audit the tree, run a search | **`/jahia-content-query-content`** | +| Create pages, articles, tutorials, folders, populate a site | **`/jahia-content-create-content`** | +| Move, rename, restructure content into sub-folders | **`/jahia-content-move-content`** | +| Translate existing content to another language | **`/jahia-content-translate-content`** | +| Publish content to the live site | Use `publish` mutation (see below) | +| Delete content | Use `deleteNode` mutation (see below) | +| Do several of the above in sequence | Run the skills in order — start with **explore-structure** if site is unfamiliar, then create or move | --- @@ -78,6 +80,7 @@ Use the task description to pick the right skill(s): Use these when the task is simple enough to not need a full sub-skill. ### Publish a node (and all its children) + ```bash curl -s -u root:root1234 \ -H "Content-Type: application/json" \ @@ -87,6 +90,7 @@ curl -s -u root:root1234 \ ``` ### Publish all content at once + ```bash curl -s -u root:root1234 \ -H "Content-Type: application/json" \ @@ -96,6 +100,7 @@ curl -s -u root:root1234 \ ``` ### Delete a node + ```bash curl -s -u root:root1234 \ -H "Content-Type: application/json" \ @@ -105,6 +110,7 @@ curl -s -u root:root1234 \ ``` ### Update a property on an existing node + ```bash curl -s -u root:root1234 \ -H "Content-Type: application/json" \ diff --git a/.agents/skills/jahia-dev-accessibility/SKILL.md b/.agents/skills/jahia-dev-accessibility/SKILL.md index 786609ba..e5ffd3e1 100644 --- a/.agents/skills/jahia-dev-accessibility/SKILL.md +++ b/.agents/skills/jahia-dev-accessibility/SKILL.md @@ -97,15 +97,18 @@ Common violations in Jahia JS modules and their fixes: ### 🔴 Critical / Serious **`color-contrast`** — text fails WCAG AA contrast ratio (4.5:1 for normal text, 3:1 for large text) + ```css /* Bad */ -color: #aaa; background: #fff; +color: #aaa; +background: #fff; /* Fix — use a contrast checker: https://webaim.org/resources/contrastchecker/ */ color: #595959; /* 7:1 ratio on white */ ``` **`image-alt`** — `` missing `alt` attribute + ```tsx /* Bad */ @@ -113,9 +116,11 @@ color: #595959; /* 7:1 ratio on white */ /* Fix — use content from CND, fall back to empty string for decorative */ {props.imageAlt ``` + Add `- imageAlt (string) i18n` to the CND and `imageAlt?: string` to `types.ts`. **`button-name`** — ` @@ -125,20 +130,21 @@ Add `- imageAlt (string) i18n` to the CND and `imageAlt?: string` to `types.ts`. ``` **`landmark-one-main`** — page has no `
` landmark + ```tsx /* Fix — wrap page content in
*/ -
- {/* page content */} -
+
{/* page content */}
``` **`page-has-heading-one`** — page has no `

` element + ```tsx /* Fix — ensure the hero or first section renders an

*/

{props.title}

``` **`link-name`** — `
` with no accessible text + ```tsx /* Bad */ @@ -150,6 +156,7 @@ Add `- imageAlt (string) i18n` to the CND and `imageAlt?: string` to `types.ts`. ### 🟡 Moderate **`heading-order`** — headings skip levels (e.g. `

` → `

`) + ```tsx /* Review: the first heading in a component should be h2 (after the page h1) */ /* Section titles: h2, subsections: h3 */ @@ -159,6 +166,7 @@ Add `- imageAlt (string) i18n` to the CND and `imageAlt?: string` to `types.ts`. Wrap page sections in semantic elements: `
`, `