@pega/custom-dx-components 26.1.11 → 26.1.12-alpha-0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/package.json +44 -61
- package/src/ai/README.md +44 -0
- package/src/ai/catalog/README.md +133 -0
- package/src/ai/catalog/best-practices/README.md +24 -0
- package/src/ai/catalog/best-practices/accessibility-for-async-components.md +53 -0
- package/src/ai/catalog/best-practices/error-handling-and-resiliency.md +116 -0
- package/src/ai/catalog/best-practices/genai-assistant-integration-patterns.md +49 -0
- package/src/ai/catalog/best-practices/performance-and-efficiency.md +109 -0
- package/src/ai/catalog/best-practices/security-considerations.md +93 -0
- package/src/ai/catalog/best-practices/testing-pconnect-components.md +245 -0
- package/src/ai/catalog/best-practices/troubleshooting-common-pitfalls.md +132 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/SKILL.md +44 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/cookbooks/README.md +39 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/cookbooks/approval-workflow-decision.md +47 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/cookbooks/attachment-upload.md +45 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/cookbooks/bulk-action-submit.md +50 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/cookbooks/confirm-before-destructive-action.md +43 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/cookbooks/create-and-stage-new-case.md +45 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/cookbooks/graceful-submit-failure.md +39 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/cookbooks/modal-assignment-submit.md +58 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/cookbooks/multi-step-wizard.md +44 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/cookbooks/resolve-or-ignore-duplicate.md +47 -0
- package/src/ai/catalog/pconnect-assignment-lifecycle/cookbooks/safe-case-deletion.md +36 -0
- package/src/ai/catalog/pconnect-case-collaboration/SKILL.md +42 -0
- package/src/ai/catalog/pconnect-case-collaboration/cookbooks/README.md +36 -0
- package/src/ai/catalog/pconnect-case-collaboration/cookbooks/look-up-user-details.md +43 -0
- package/src/ai/catalog/pconnect-case-collaboration/cookbooks/manage-case-attachments.md +51 -0
- package/src/ai/catalog/pconnect-case-collaboration/cookbooks/manage-case-participants.md +52 -0
- package/src/ai/catalog/pconnect-case-collaboration/cookbooks/participant-and-follower.md +40 -0
- package/src/ai/catalog/pconnect-case-collaboration/cookbooks/post-and-manage-feed.md +50 -0
- package/src/ai/catalog/pconnect-case-collaboration/cookbooks/relate-cases.md +35 -0
- package/src/ai/catalog/pconnect-case-collaboration/cookbooks/tag-case-and-find-related.md +44 -0
- package/src/ai/catalog/pconnect-data-subscriptions/SKILL.md +42 -0
- package/src/ai/catalog/pconnect-data-subscriptions/cookbooks/README.md +35 -0
- package/src/ai/catalog/pconnect-data-subscriptions/cookbooks/data-page-subscription-cleanup.md +41 -0
- package/src/ai/catalog/pconnect-data-subscriptions/cookbooks/publish-and-subscribe-events.md +40 -0
- package/src/ai/catalog/pconnect-data-subscriptions/cookbooks/pubsub-cleanup.md +38 -0
- package/src/ai/catalog/pconnect-data-subscriptions/cookbooks/query-paginated-list-data.md +44 -0
- package/src/ai/catalog/pconnect-data-subscriptions/cookbooks/realtime-case-subscription.md +46 -0
- package/src/ai/catalog/pconnect-data-subscriptions/cookbooks/refresh-and-update-data.md +41 -0
- package/src/ai/catalog/pconnect-forms-and-fields/SKILL.md +44 -0
- package/src/ai/catalog/pconnect-forms-and-fields/cookbooks/README.md +36 -0
- package/src/ai/catalog/pconnect-forms-and-fields/cookbooks/autosave-tracking.md +45 -0
- package/src/ai/catalog/pconnect-forms-and-fields/cookbooks/cascading-dependent-fields.md +51 -0
- package/src/ai/catalog/pconnect-forms-and-fields/cookbooks/collect-form-changes-for-manual-save.md +45 -0
- package/src/ai/catalog/pconnect-forms-and-fields/cookbooks/custom-field-validators.md +44 -0
- package/src/ai/catalog/pconnect-forms-and-fields/cookbooks/discard-changes-and-reset-form.md +41 -0
- package/src/ai/catalog/pconnect-forms-and-fields/cookbooks/full-form-validation.md +38 -0
- package/src/ai/catalog/pconnect-forms-and-fields/cookbooks/persist-widget-ui-state.md +41 -0
- package/src/ai/catalog/pconnect-lists-and-views/SKILL.md +44 -0
- package/src/ai/catalog/pconnect-lists-and-views/cookbooks/README.md +35 -0
- package/src/ai/catalog/pconnect-lists-and-views/cookbooks/activate-and-switch-container-items.md +52 -0
- package/src/ai/catalog/pconnect-lists-and-views/cookbooks/container-item-lifecycle.md +61 -0
- package/src/ai/catalog/pconnect-lists-and-views/cookbooks/insert-delete-list-rows.md +40 -0
- package/src/ai/catalog/pconnect-lists-and-views/cookbooks/reorder-list-rows.md +39 -0
- package/src/ai/catalog/pconnect-lists-and-views/cookbooks/repeating-row-edit.md +39 -0
- package/src/ai/catalog/pconnect-lists-and-views/cookbooks/view-personalization.md +60 -0
- package/src/ai/catalog/pconnect-navigation-locale-genai/SKILL.md +45 -0
- package/src/ai/catalog/pconnect-navigation-locale-genai/cookbooks/README.md +35 -0
- package/src/ai/catalog/pconnect-navigation-locale-genai/cookbooks/agent-conversation-for-tasks.md +54 -0
- package/src/ai/catalog/pconnect-navigation-locale-genai/cookbooks/bookmarkable-filter-state.md +40 -0
- package/src/ai/catalog/pconnect-navigation-locale-genai/cookbooks/deep-link-semantic-url.md +39 -0
- package/src/ai/catalog/pconnect-navigation-locale-genai/cookbooks/genai-assistant-conversation.md +43 -0
- package/src/ai/catalog/pconnect-navigation-locale-genai/cookbooks/locale-switch-and-relocalize.md +38 -0
- package/src/ai/catalog/pconnect-navigation-locale-genai/cookbooks/persist-operator-preferences.md +40 -0
- package/src/ai/catalog/references/architecture-overview.md +52 -0
- package/src/ai/catalog/references/pconnect-actions-api.md +948 -0
- package/src/ai/catalog/references/pconnect-case-info-data-info-localization.md +618 -0
- package/src/ai/catalog/references/pconnect-case-validation-utilities.md +238 -0
- package/src/ai/catalog/references/pconnect-container-manager.md +215 -0
- package/src/ai/catalog/references/pconnect-list-actions.md +250 -0
- package/src/ai/catalog/references/pconnect-misc-suggestions-creation.md +231 -0
- package/src/ai/catalog/references/pconnect-reference-context-component.md +471 -0
- package/src/ai/catalog/references/pconnect-state-config.md +316 -0
- package/src/ai/catalog/references/pconnect-validation-api.md +45 -0
- package/src/ai/catalog/references/pcore-analytics-genai-details.md +50 -0
- package/src/ai/catalog/references/pcore-bootstrap-init-details.md +121 -0
- package/src/ai/catalog/references/pcore-bootstrap-registry-routing.md +253 -0
- package/src/ai/catalog/references/pcore-case-collaboration-details.md +191 -0
- package/src/ai/catalog/references/pcore-case-collaboration-genai.md +264 -0
- package/src/ai/catalog/references/pcore-environment-info-details.md +96 -0
- package/src/ai/catalog/references/pcore-environment-locale-namespace.md +175 -0
- package/src/ai/catalog/references/pcore-messaging-details.md +99 -0
- package/src/ai/catalog/references/pcore-misc-details.md +207 -0
- package/src/ai/catalog/references/pcore-misc-utilities.md +325 -0
- package/src/ai/catalog/references/pcore-rest-data-details.md +164 -0
- package/src/ai/catalog/references/pcore-rest-data-messaging.md +308 -0
- package/src/ai/catalog/references/pcore-store-context-details.md +40 -0
- package/src/ai/catalog/references/pcore-store-context-state.md +130 -0
- package/src/ai/index.js +154 -0
- package/src/ai/project/.claude/skills/boolean/SKILL.md +43 -0
- package/src/ai/project/.claude/skills/case/SKILL.md +58 -0
- package/src/ai/project/.claude/skills/create-dx-component/SKILL.md +98 -0
- package/src/ai/project/.claude/skills/create-dx-component/assets/PConnProps.d.ts +24 -0
- package/src/ai/project/.claude/skills/create-dx-component/assets/create-nonce.ts +5 -0
- package/src/ai/project/.claude/skills/create-dx-component/assets/event-utils.ts +18 -0
- package/src/ai/project/.claude/skills/create-dx-component/assets/styles.ts +7 -0
- package/src/ai/project/.claude/skills/create-dx-component/cookbooks/README.md +28 -0
- package/src/ai/project/.claude/skills/create-dx-component/cookbooks/build-a-data-widget.md +281 -0
- package/src/ai/project/.claude/skills/create-dx-component/cookbooks/field-with-limits-and-validation.md +156 -0
- package/src/ai/project/.claude/skills/create-dx-component/cookbooks/status-badge-field.md +97 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/accessibility.md +71 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/choosing-type-and-subtype.md +79 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/cli-workflow.md +125 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/component-patterns.md +241 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/config-schema.md +296 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/cosmos-components.md +137 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/field-behavior.md +155 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/field-essentials.md +90 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/field-subtypes-extra.md +68 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/localization.md +30 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/pcore-pconnect-apis.md +77 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/presentation-essentials.md +57 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/props-contract.md +182 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/state-matrix.md +102 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/storybook-pattern.md +262 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/styles-pattern.md +54 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/template-essentials.md +87 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/template-patterns.md +131 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/test-pattern.md +215 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/ui-gallery-reuse.md +93 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/widget-essentials.md +84 -0
- package/src/ai/project/.claude/skills/create-dx-component/references/widget-patterns.md +164 -0
- package/src/ai/project/.claude/skills/currency/SKILL.md +46 -0
- package/src/ai/project/.claude/skills/date/SKILL.md +46 -0
- package/src/ai/project/.claude/skills/decimal/SKILL.md +49 -0
- package/src/ai/project/.claude/skills/details/SKILL.md +60 -0
- package/src/ai/project/.claude/skills/email/SKILL.md +42 -0
- package/src/ai/project/.claude/skills/field/SKILL.md +68 -0
- package/src/ai/project/.claude/skills/form/SKILL.md +51 -0
- package/src/ai/project/.claude/skills/generate-component-set/SKILL.md +80 -0
- package/src/ai/project/.claude/skills/generate-component-set/component-manifest.json +161 -0
- package/src/ai/project/.claude/skills/identify-components/SKILL.md +76 -0
- package/src/ai/project/.claude/skills/integer/SKILL.md +45 -0
- package/src/ai/project/.claude/skills/page/SKILL.md +60 -0
- package/src/ai/project/.claude/skills/page-widget/SKILL.md +55 -0
- package/src/ai/project/.claude/skills/percentage/SKILL.md +52 -0
- package/src/ai/project/.claude/skills/phone/SKILL.md +44 -0
- package/src/ai/project/.claude/skills/picklist/SKILL.md +50 -0
- package/src/ai/project/.claude/skills/plan-dx-component/SKILL.md +51 -0
- package/src/ai/project/.claude/skills/plan-dx-component/references/design-system-mapping.md +108 -0
- package/src/ai/project/.claude/skills/plan-dx-component/references/intake.md +146 -0
- package/src/ai/project/.claude/skills/plan-dx-component/references/plan-template.md +76 -0
- package/src/ai/project/.claude/skills/plan-dx-component/references/requirements-template.md +88 -0
- package/src/ai/project/.claude/skills/presentation/SKILL.md +61 -0
- package/src/ai/project/.claude/skills/publish-library/SKILL.md +106 -0
- package/src/ai/project/.claude/skills/radiobuttons/SKILL.md +51 -0
- package/src/ai/project/.claude/skills/review-component/SKILL.md +49 -0
- package/src/ai/project/.claude/skills/review-component/references/checklist.md +77 -0
- package/src/ai/project/.claude/skills/template/SKILL.md +60 -0
- package/src/ai/project/.claude/skills/text/SKILL.md +45 -0
- package/src/ai/project/.claude/skills/update-dx-component/SKILL.md +56 -0
- package/src/ai/project/.claude/skills/update-dx-component/references/maintenance.md +43 -0
- package/src/ai/project/.claude/skills/url/SKILL.md +46 -0
- package/src/ai/project/.claude/skills/validate-component/SKILL.md +78 -0
- package/src/ai/project/.claude/skills/validate-component/references/runtime-troubleshooting.md +56 -0
- package/src/ai/project/.claude/skills/validate-component/scripts/check-component.mjs +327 -0
- package/src/ai/project/.claude/skills/widget/SKILL.md +59 -0
- package/src/ai/project/.gemini/settings.json +5 -0
- package/src/ai/project/AGENTS.md +74 -0
- package/src/ai/readme-section.md +91 -0
- package/src/cli.js +48 -7
- package/src/constants.js +54 -53
- package/src/internal-tests.js +27 -0
- package/src/tasks/ai-toggle/index.js +46 -0
- package/src/tasks/authenticate/helper.js +38 -38
- package/src/tasks/authenticate/index.js +17 -15
- package/src/tasks/build-all/helper.js +4 -4
- package/src/tasks/build-all/index.js +7 -7
- package/src/tasks/build-comp/helper.js +17 -17
- package/src/tasks/build-comp/index.js +10 -10
- package/src/tasks/build-jest-assets/index.js +7 -9
- package/src/tasks/build-lib/helper.js +8 -8
- package/src/tasks/build-lib/index.js +8 -8
- package/src/tasks/bundle/builder/index.js +9 -12
- package/src/tasks/bundle/builder/modules.js +8 -9
- package/src/tasks/bundle/builder/paths.js +2 -19
- package/src/tasks/bundle/builder/webpack.config.js +60 -77
- package/src/tasks/bundle/index.js +8 -8
- package/src/tasks/create/index.js +35 -35
- package/src/tasks/create/templates/Constellation/Field/Boolean/index.tsx.mustache +1 -36
- package/src/tasks/create/templates/Constellation/Field/Date/date.ts.mustache +2 -2
- package/src/tasks/create/templates/Constellation/Field/Date/index.tsx.mustache +2 -27
- package/src/tasks/create/templates/Constellation/Field/DateTime/date-utils.ts.mustache +2 -2
- package/src/tasks/create/templates/Constellation/Field/DateTime/date.ts.mustache +2 -2
- package/src/tasks/create/templates/Constellation/Field/DateTime/index.tsx.mustache +2 -35
- package/src/tasks/create/templates/Constellation/Field/Decimal/index.tsx.mustache +3 -36
- package/src/tasks/create/templates/Constellation/Field/Decimal-Currency/index.tsx.mustache +4 -39
- package/src/tasks/create/templates/Constellation/Field/Decimal-Percentage/index.tsx.mustache +4 -53
- package/src/tasks/create/templates/Constellation/Field/Icon-Button-URL/index.tsx.mustache +5 -5
- package/src/tasks/create/templates/Constellation/Field/Integer/index.tsx.mustache +3 -36
- package/src/tasks/create/templates/Constellation/Field/Picklist/index.tsx.mustache +7 -42
- package/src/tasks/create/templates/Constellation/Field/Picklist-Autocomplete/component-constants.ts +1 -1
- package/src/tasks/create/templates/Constellation/Field/Picklist-Autocomplete/date.ts.mustache +0 -2
- package/src/tasks/create/templates/Constellation/Field/Picklist-Autocomplete/embed-utils.ts.mustache +1 -1
- package/src/tasks/create/templates/Constellation/Field/Picklist-Autocomplete/index.tsx.mustache +11 -11
- package/src/tasks/create/templates/Constellation/Field/Picklist-Autocomplete/repeat-utils.ts.mustache +3 -13
- package/src/tasks/create/templates/Constellation/Field/Picklist-Autocomplete/textformat-utils.tsx.mustache +2 -3
- package/src/tasks/create/templates/Constellation/Field/Picklist-Autocomplete/utils.ts.mustache +28 -28
- package/src/tasks/create/templates/Constellation/Field/Picklist-RadioButtons/index.tsx.mustache +2 -2
- package/src/tasks/create/templates/Constellation/Field/Picklist-RadioButtons/textformat-utils.tsx.mustache +2 -3
- package/src/tasks/create/templates/Constellation/Field/Text/index.tsx.mustache +4 -33
- package/src/tasks/create/templates/Constellation/Field/Text-Email/index.tsx.mustache +3 -33
- package/src/tasks/create/templates/Constellation/Field/Text-Input/index.tsx.mustache +3 -31
- package/src/tasks/create/templates/Constellation/Field/Text-Paragraph/index.tsx.mustache +3 -31
- package/src/tasks/create/templates/Constellation/Field/Text-Phone/index.tsx.mustache +3 -35
- package/src/tasks/create/templates/Constellation/Field/Text-URL/index.tsx.mustache +3 -33
- package/src/tasks/create/templates/Constellation/Field/Text-URL/text-url.tsx.mustache +2 -3
- package/src/tasks/create/templates/Constellation/Field/TimeofDay/index.tsx.mustache +2 -35
- package/src/tasks/create/templates/Constellation/Field/TimeofDay/time-of-day.ts.mustache +2 -2
- package/src/tasks/create/templates/Constellation/Template/DETAILS/Avatar.tsx.mustache +19 -15
- package/src/tasks/create/templates/Constellation/Template/DETAILS/Operator.tsx.mustache +6 -6
- package/src/tasks/create/templates/Constellation/Template/DETAILS/demo.stories.tsx.mustache +1 -1
- package/src/tasks/create/templates/Constellation/Template/DETAILS/index.tsx.mustache +4 -8
- package/src/tasks/create/templates/Constellation/Template/DETAILS/mock.ts.mustache +9 -9
- package/src/tasks/create/templates/Constellation/Template/DETAILS/utils.js.mustache +4 -8
- package/src/tasks/create/templates/Constellation/Template/DETAILS-region/Avatar.tsx.mustache +19 -15
- package/src/tasks/create/templates/Constellation/Template/DETAILS-region/Operator.tsx.mustache +6 -6
- package/src/tasks/create/templates/Constellation/Template/DETAILS-region/demo.stories.tsx.mustache +1 -1
- package/src/tasks/create/templates/Constellation/Template/DETAILS-region/index.tsx.mustache +1 -1
- package/src/tasks/create/templates/Constellation/Template/DETAILS-region/mock.ts.mustache +9 -9
- package/src/tasks/create/templates/Constellation/Template/DETAILS-region/utils.ts.mustache +4 -8
- package/src/tasks/create/templates/Constellation/Template/FORM/demo.stories.tsx.mustache +8 -8
- package/src/tasks/create/templates/Constellation/Template/FORM-region/demo.stories.tsx.mustache +8 -8
- package/src/tasks/create/templates/Constellation/Template/PAGE/utils.ts.mustache +4 -9
- package/src/tasks/create/templates/Constellation/Widget/CASE/demo.test.tsx.mustache +3 -3
- package/src/tasks/create/templates/Constellation/Widget/PAGE-CASE/Avatar.tsx.mustache +19 -14
- package/src/tasks/create/templates/Constellation/Widget/PAGE-CASE/Operator.tsx.mustache +6 -6
- package/src/tasks/create/templates/Constellation/Widget/PAGE-CASE/index.tsx.mustache +1 -1
- package/src/tasks/create/templates/Constellation/Widget/PAGE-CASE/mock.ts.mustache +1 -1
- package/src/tasks/create/templates/Launchpad/Field/Icon-Button-URL/index.tsx.mustache +5 -5
- package/src/tasks/create/templates/Launchpad/Field/Text-Phone/index.tsx.mustache +3 -35
- package/src/tasks/create/templates/Launchpad/Field/__components.md +1 -1
- package/src/tasks/create/templates/Launchpad/Template/FORM/demo.stories.tsx.mustache +8 -8
- package/src/tasks/create/templates/Launchpad/Template/FORM-region/demo.stories.tsx.mustache +8 -8
- package/src/tasks/create/templates/Launchpad/Widget/CASE/demo.test.tsx.mustache +3 -3
- package/src/tasks/create/templates/Launchpad/Widget/PAGE-CASE/index.tsx.mustache +1 -1
- package/src/tasks/create/templates/Launchpad/Widget/PAGE-CASE/mock.ts.mustache +1 -1
- package/src/tasks/create-all/index.js +42 -41
- package/src/tasks/create-lib/helper.js +13 -13
- package/src/tasks/create-lib/index.js +5 -5
- package/src/tasks/create-version/helper.js +10 -10
- package/src/tasks/create-version/index.js +3 -4
- package/src/tasks/delete/helper.js +20 -19
- package/src/tasks/delete/index.js +2 -2
- package/src/tasks/delete-all/helper.js +26 -25
- package/src/tasks/delete-all/index.js +2 -2
- package/src/tasks/delete-lib/helper.js +6 -6
- package/src/tasks/delete-lib/index.js +6 -6
- package/src/tasks/delete-pub/helper.js +18 -18
- package/src/tasks/delete-pub/index.js +8 -8
- package/src/tasks/delete-version/index.js +2 -2
- package/src/tasks/dev-server/index.js +1 -1
- package/src/tasks/export-archive/helper.js +21 -21
- package/src/tasks/export-archive/index.js +27 -40
- package/src/tasks/export-rc/helper.js +34 -34
- package/src/tasks/export-rc/index.js +21 -31
- package/src/tasks/import-archive/helper.js +39 -39
- package/src/tasks/import-archive/index.js +69 -89
- package/src/tasks/import-comp/helper.js +8 -8
- package/src/tasks/import-comp/index.js +30 -42
- package/src/tasks/index.js +5 -0
- package/src/tasks/init/Node.gitignore +149 -0
- package/src/tasks/init/helper.js +15 -8
- package/src/tasks/init/index.js +115 -35
- package/src/tasks/init/template/.prettierrc.json +4 -1
- package/src/tasks/init/template/.storybook/main.ts +1 -4
- package/src/tasks/init/template/.storybook/manager.ts +1 -1
- package/src/tasks/init/template/.storybook/preview.tsx +2 -2
- package/src/tasks/init/template/.storybook/theme.js +1 -1
- package/src/tasks/init/template/.vscode/extensions.json +0 -1
- package/src/tasks/init/template/README.md +2 -2
- package/src/tasks/init/template/eslint.config.mjs +13 -155
- package/src/tasks/init/template/jest.config.cjs +2 -0
- package/src/tasks/init/template/package-lock.json +26864 -0
- package/src/tasks/init/template/package.json.mustache +19 -74
- package/src/tasks/init/template/setupTests.ts +10 -4
- package/src/tasks/init/template/tsconfig.json +14 -6
- package/src/tasks/internal-test/index.js +21 -0
- package/src/tasks/internal-test-full/index.js +15 -0
- package/src/tasks/internal-test-functional/index.js +15 -0
- package/src/tasks/internal-test-unit/index.js +13 -0
- package/src/tasks/linter/index.js +2 -2
- package/src/tasks/list/helper.js +41 -40
- package/src/tasks/list/index.js +3 -3
- package/src/tasks/list-lib/helper.js +5 -5
- package/src/tasks/list-lib/index.js +8 -8
- package/src/tasks/list-pub/helper.js +23 -16
- package/src/tasks/list-pub/index.js +24 -21
- package/src/tasks/move/helper.js +8 -8
- package/src/tasks/move/index.js +19 -23
- package/src/tasks/promote-webpack/index.js +4 -4
- package/src/tasks/promote-webpack/webpack.config.js +62 -93
- package/src/tasks/publish/helper.js +21 -21
- package/src/tasks/publish/index.js +16 -16
- package/src/tasks/publish-all/helper.js +20 -20
- package/src/tasks/publish-all/index.js +11 -12
- package/src/tasks/publish-archive/helper.js +9 -9
- package/src/tasks/publish-archive/index.js +12 -14
- package/src/tasks/publish-lib/helper.js +3 -3
- package/src/tasks/publish-lib/index.js +28 -34
- package/src/tasks/quarantine/index.js +8 -8
- package/src/tasks/rename/index.js +26 -30
- package/src/tasks/rename-lib/index.js +21 -25
- package/src/tasks/restore/index.js +8 -8
- package/src/tasks/restore-config/index.js +3 -5
- package/src/tasks/set-perm/index.js +8 -8
- package/src/tasks/show-help/index.js +17 -9
- package/src/tasks/show-status/index.js +2 -2
- package/src/tasks/store/index.js +8 -8
- package/src/tasks/switch-lib/helper.js +7 -7
- package/src/tasks/switch-lib/index.js +7 -7
- package/src/tasks/switch-version/helper.js +6 -6
- package/src/tasks/switch-version/index.js +7 -7
- package/src/tasks/update/index.js +1 -1
- package/src/tasks/upgrade/index.js +7 -7
- package/src/tasks/validator/index.js +49 -203
- package/src/util.js +169 -186
- package/src/rules/no-pcore-pconnect-in-presentation.js +0 -103
- package/src/tasks/init/template/.stylelintrc.json +0 -4
- package/src/tasks/init/template/babel.config.cjs +0 -22
- package/src/tasks/init/template/cspell.json +0 -14
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Performance & Efficiency
|
|
3
|
+
description: Debouncing, buffering, caching/invalidation, request cancellation, and subscription hygiene patterns for components that call PCore/PConnect APIs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Performance & Efficiency
|
|
7
|
+
|
|
8
|
+
> Part of [Cross-Cutting Best Practices](./README.md).
|
|
9
|
+
|
|
10
|
+
Most PCore/PConnect calls cross a network boundary, and several return values the framework already caches for you. The patterns below keep a custom component from doing unnecessary work — its own or the server's.
|
|
11
|
+
|
|
12
|
+
## Debounce or Buffer User-Triggered Calls
|
|
13
|
+
|
|
14
|
+
A field wired directly to `onChange` → REST/data-page call fires once per keystroke, which wastes network round trips and can make results arrive out of order (see [Troubleshooting: an older async response overwrites a newer one](./troubleshooting-common-pitfalls.md#an-older-async-response-overwrites-a-newer-one-a-fast-typed-search-shows-results-for-an-earlier-query)). Two documented options:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// Option 1: a debounced Observable — react only after input goes quiet.
|
|
18
|
+
const debouncedSubject = PCore.getAsynchronousUtils().getDebouncedSubject(250);
|
|
19
|
+
const subscription = debouncedSubject.subscribe((value) => runSearch(value));
|
|
20
|
+
// ...on each keystroke: debouncedSubject.next(currentValue);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// Option 2: buffered/batched fetches built into a live DataApi instance (typeahead-style fields).
|
|
25
|
+
const dataApiInstance = await PCore.getDataApi().init(dataConfig, contextName);
|
|
26
|
+
dataApiInstance.registerForBufferedCall({ waitTime: 50 });
|
|
27
|
+
const results = await dataApiInstance.fetchData(searchTerm);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
See [pcore-misc-details.md](../references/pcore-misc-details.md) and [pcore-rest-data-details.md](../references/pcore-rest-data-details.md) for full signatures.
|
|
31
|
+
|
|
32
|
+
## Cache Data Pages, But Invalidate Deliberately
|
|
33
|
+
|
|
34
|
+
Data page reads (`DataPageUtils.getDataAsync`/`getPageDataAsync`, `DataApi.init`) are cached by the framework — calling them again with the same parameters typically reuses the cached result instead of re-fetching. That's a performance win, but it means a stale read is a common bug after a write:
|
|
35
|
+
|
|
36
|
+
- After a save/update that should be reflected on next read, call `DataPageUtils.markDataInstanceDirty(dataPageName, parameters)` or re-fetch with `{ invalidateCache: true }`.
|
|
37
|
+
- When a `DataApi` instance is scoped to a context that's going away (e.g. a container item closing), call `DataApi.clearContextedCache(context)` so the next open of that context doesn't reuse a stale cached instance.
|
|
38
|
+
- For small, component-local values (not full data page results), `NavigationUtils.getComponentCache`/`setComponentCache`/`resetComponentCache` (see [pcore-misc-details.md](../references/pcore-misc-details.md)) offer a lighter-weight, shorter-lived cache than a data page round trip.
|
|
39
|
+
|
|
40
|
+
Caching is only a net win if invalidation is deliberate — don't cache a value and then forget which write paths should invalidate it.
|
|
41
|
+
|
|
42
|
+
## Cancel Requests That Are No Longer Needed
|
|
43
|
+
|
|
44
|
+
If a component can trigger a new request before a previous one resolves (a search box, a fast-changing filter, an unmounting component), cancel the stale request instead of letting it complete and race the newer one:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const { invokeRestApi, getCancelTokenSource, isRequestCanceled } = PCore.getRestClient();
|
|
48
|
+
const cancelTokenSource = getCancelTokenSource();
|
|
49
|
+
|
|
50
|
+
invokeRestApi(routeKey, { ...payload, cancelTokenSource }).catch((error) => {
|
|
51
|
+
if (isRequestCanceled(error)) return; // expected — a newer request superseded this one
|
|
52
|
+
handleRealError(error);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// When a newer request starts, or the component unmounts:
|
|
56
|
+
cancelTokenSource.cancel();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This isn't only about correctness (see [Error Handling & Resiliency: distinguish cancellation from failure](./error-handling-and-resiliency.md#distinguish-cancellation-from-failure)) — it also frees the response payload the server would otherwise still compute and send for a result nobody will use.
|
|
60
|
+
|
|
61
|
+
## Avoid Redundant Subscriptions
|
|
62
|
+
|
|
63
|
+
If multiple sibling components need the same data page or the same pub/sub event, prefer a single subscription owned by a shared ancestor/context over each sibling independently calling `subscribeToDataPageUpdates`/`PubSubUtils.subscribe`. Duplicate subscriptions mean duplicate network/server work for identical data, and duplicate cleanup surface area for the same bug class described in the troubleshooting guide's [subscription cleanup entry](./troubleshooting-common-pitfalls.md#subscription-callback-keeps-firing-after-the-component-unmounts).
|
|
64
|
+
|
|
65
|
+
## Minimize Store Reads in the Render Path
|
|
66
|
+
|
|
67
|
+
`StateUtils.getSharedState`/`ContextTreeManager.getFieldsList` (see [pcore-store-context-details.md](../references/pcore-store-context-details.md)) can return large structures. Read only the narrow piece of state a component actually renders, rather than pulling a broad object and destructuring it afterward — a narrower read makes it easier to reason about what a re-render actually depends on, and avoids re-rendering on changes to sibling state the component never displays.
|
|
68
|
+
|
|
69
|
+
## Batch Related List Operations
|
|
70
|
+
|
|
71
|
+
When applying the same change to many rows of a repeating structure, prefer `ListActions`' batch-capable operations (see [pconnect-list-actions.md](../references/pconnect-list-actions.md)) over looping and calling a single-row update once per row — a loop of individual calls multiplies both client-side dispatches and, depending on the operation, server round trips.
|
|
72
|
+
|
|
73
|
+
## Parallelize Independent Calls Instead of Awaiting Them One at a Time
|
|
74
|
+
|
|
75
|
+
If a component needs the results of two or more calls that don't depend on each other's output (for example, a data page lookup and a separate `AnalyticsUtils` call), issue them together with `Promise.all` rather than `await`-ing each in sequence. Sequential awaits of independent calls create an artificial waterfall — the total wait time becomes the sum of both round trips instead of the slower of the two:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// Avoid: the second call doesn't start until the first one finishes, for no reason.
|
|
79
|
+
const lookupResult = await PCore.getDataPageUtils().getDataAsync(dataPage, contextName);
|
|
80
|
+
const insightIDs = await PCore.getAnalyticsUtils().getInsightIDs();
|
|
81
|
+
|
|
82
|
+
// Prefer: both requests are in flight at the same time.
|
|
83
|
+
const [lookupResult, insightIDs] = await Promise.all([
|
|
84
|
+
PCore.getDataPageUtils().getDataAsync(dataPage, contextName),
|
|
85
|
+
PCore.getAnalyticsUtils().getInsightIDs()
|
|
86
|
+
]);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Only sequence calls when the second genuinely depends on the first's resolved value (for example, using a fetched record's ID as a parameter to the next call).
|
|
90
|
+
|
|
91
|
+
## Virtualize Long Repeating Structures
|
|
92
|
+
|
|
93
|
+
Rendering every row of a large repeating structure or list view at once means the browser builds and lays out DOM nodes for rows the user can't currently see. For lists that can grow into the hundreds of rows, render only the visible window (plus a small overscan buffer) instead of the full data set — check whether the presentation component already used for the list (in `cosmos-core`/`dx-components`) has built-in virtualization support before building a custom windowing solution.
|
|
94
|
+
|
|
95
|
+
## Lazy-Load Assets That Aren't Needed on Initial Render
|
|
96
|
+
|
|
97
|
+
`AssetLoader.loadAssets(assets, options)`/`getSvcImage(key)`/`getSvcImageUrl(key)` (see [pcore-misc-details.md](../references/pcore-misc-details.md)) fetch assets on demand rather than requiring them to be bundled/loaded upfront. Prefer loading an image, icon set, or other optional asset only when the view that displays it actually renders (or becomes visible), instead of loading every asset a component might ever need at mount time — this matters most for content behind a tab, an accordion panel, or a modal that many users never open.
|
|
98
|
+
|
|
99
|
+
## Checklist
|
|
100
|
+
|
|
101
|
+
- Debounce or buffer any call wired to a fast-firing user input (typing, dragging, rapid toggles).
|
|
102
|
+
- After a write that affects a cached data page, invalidate it deliberately (`markDataInstanceDirty`/`invalidateCache`) rather than assuming the next read will be fresh.
|
|
103
|
+
- Cancel a request that a newer one has superseded, and check `isRequestCanceled` before treating its rejection as a real error.
|
|
104
|
+
- Share one subscription across siblings that need the same data instead of each subscribing independently.
|
|
105
|
+
- Read only the state slice a component renders, not a broad object it partially uses.
|
|
106
|
+
- Batch repeating-row operations instead of looping a single-row call.
|
|
107
|
+
- Run independent calls with `Promise.all` instead of awaiting them one after another.
|
|
108
|
+
- Virtualize (render only the visible window of) large repeating structures instead of every row at once.
|
|
109
|
+
- Lazy-load optional assets (`AssetLoader`) when the view that needs them renders, not unconditionally at mount.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Security Considerations
|
|
3
|
+
description: OWASP-aligned guidance for safely rendering, uploading, linking, authenticating, and logging data obtained through PCore/PConnect APIs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Security Considerations
|
|
7
|
+
|
|
8
|
+
> Part of [Cross-Cutting Best Practices](./README.md).
|
|
9
|
+
|
|
10
|
+
The APIs cataloged in these skills return content that can originate from other users, an AI model, or the current URL — none of it should be treated as trusted by default. The items below call out where that matters, organized roughly by OWASP Top 10 category.
|
|
11
|
+
|
|
12
|
+
| OWASP category | Where it applies here |
|
|
13
|
+
| --------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
14
|
+
| A01: Broken Access Control | Client-side checks as a security boundary; trusting client-computed permissions |
|
|
15
|
+
| A03: Injection (incl. XSS) | Rendering feed/comment/GenAI content; prompt injection into GenAI Assistant calls |
|
|
16
|
+
| A05: Security Misconfiguration | Manually managing auth headers/tokens instead of using the provided REST client |
|
|
17
|
+
| A08: Software and Data Integrity Failures | Trusting uploaded/downloaded attachment content without a type check |
|
|
18
|
+
| A09: Security Logging and Monitoring Failures | Logging raw error payloads or field values that may contain PII/tokens |
|
|
19
|
+
|
|
20
|
+
## Sanitize Free-Text Content Before Rendering It as HTML
|
|
21
|
+
|
|
22
|
+
Case feed posts/comments (`FeedUtils`), correspondence, and GenAI Assistant replies (`GenAIAssistantUtils.sendMessage`/`sendMessageForAgent`) are free text written by another user or generated by a model — not markup you authored. **NEVER** render this content with `dangerouslySetInnerHTML`/`innerHTML` without first sanitizing it through a library such as `dompurify`. Treat an AI-generated response exactly like user-generated content for this purpose: a model can be prompted (directly or indirectly, via injected content it summarizes) into emitting markup or script, and it is not a trusted source of pre-sanitized HTML.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import DOMPurify from 'dompurify';
|
|
26
|
+
|
|
27
|
+
const safeHtml = DOMPurify.sanitize(feedPost.message);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If the content is only ever rendered as plain text (no formatting needed), render it as text content instead of HTML and skip the sanitization step entirely — that's a stronger guarantee than sanitizing and still using an HTML sink.
|
|
31
|
+
|
|
32
|
+
## Treat Feed Content Forwarded Into a GenAI Prompt as Untrusted Input Too
|
|
33
|
+
|
|
34
|
+
If a component builds a GenAI Assistant message (`sendMessage`/`sendMessageForAgent`) that incorporates case feed text, attachment names, or other user-authored content, remember that content can itself contain text crafted to influence the model ("prompt injection") — for example, embedded instructions telling the assistant to ignore prior context. Don't concatenate raw user content directly into a system-level instruction string; keep user-authored content clearly delimited as data being discussed, not as instructions to the assistant, and don't let a model's response alone gate a subsequent privileged action (like a submit or delete) without the normal validation/confirmation flow.
|
|
35
|
+
|
|
36
|
+
## Client-Side Checks Are UX, Never the Security Boundary
|
|
37
|
+
|
|
38
|
+
Hiding a button, disabling an action, or skipping a client-side field because a role/permission check failed is a UX affordance, not access control. The server (case type stage, access control rules) is the actual authority — never assume that because a component doesn't render a "Delete" button, the corresponding `ActionsApi.deleteWork` call is unreachable, and never treat a client-computed permission flag (from cached state, a prior response, or a stale subscription) as authoritative for anything higher-stakes than what to render. Fail closed: if a permission/config value that gates a sensitive action hasn't loaded yet or failed to load, treat it as "not permitted" rather than defaulting to showing the action.
|
|
39
|
+
|
|
40
|
+
## Only Build Deep Links From a Trusted `routeKey`
|
|
41
|
+
|
|
42
|
+
When constructing a shareable link with `SemanticUrlUtils.getResolvedSemanticURL(routeKey, payload, params, locale?)` (see [deep-link-semantic-url](../pconnect-navigation-locale-genai/cookbooks/deep-link-semantic-url.md)), source `routeKey` from `PCore.getSemanticUrlUtils().getActions()` — never from a raw query string, hash fragment, or other user-controllable input. Passing an attacker-influenced value as a route key risks resolving to an unintended destination (an open-redirect-style issue) rather than the case/view you meant to link to.
|
|
43
|
+
|
|
44
|
+
## Treat Client-Side Upload Checks as UX Only, Not a Security Control
|
|
45
|
+
|
|
46
|
+
`AttachmentUtils.uploadAttachment` performs server-side size/type rejection — that is the authoritative check. A client-side file-type or size pre-check (to show a fast error before the network round trip) is fine for UX, but never rely on it as the actual security boundary: always still handle the rejection from `uploadAttachment` itself, and never assume a file "passed" validation just because the client-side check didn't flag it.
|
|
47
|
+
|
|
48
|
+
## Don't Trust Downloaded Attachment Content Either
|
|
49
|
+
|
|
50
|
+
`AttachmentUtils.downloadAttachment`/`streamDownloadAttachment` return binary content another user uploaded. If a component renders that content inline (previewing an SVG, HTML, or text file, for example) rather than triggering a normal file download, apply the same rule as feed content: an SVG can carry an embedded `<script>`, and an HTML file can carry arbitrary markup. Prefer the browser's native download/save behavior for attachment content, and if inline preview is genuinely required, sanitize it first and never render it as same-origin HTML without isolation (for example, a sandboxed `<iframe>`).
|
|
51
|
+
|
|
52
|
+
## Let the Provided REST Client Own Authentication
|
|
53
|
+
|
|
54
|
+
`RestClient` (see [pcore-rest-data-details.md](../references/pcore-rest-data-details.md)) already attaches the operator's authenticated session/auth headers to `invokeRestApi`/`invokeCustomRestApi` calls, and exposes `getHeaderProcessor()` for the supported way to add custom headers. Never manually read, store, or forward an auth token from component code (e.g. into `localStorage`, a query string, or a custom header built by hand) — that both duplicates a security-relevant responsibility the platform already owns and creates a new place a token can leak (browser storage is readable by any script on the page, and tokens in URLs end up in server access logs and browser history).
|
|
55
|
+
|
|
56
|
+
## Don't Leak Sensitive Details Through Error Handling
|
|
57
|
+
|
|
58
|
+
When a `Promise`-returning PCore/PConnect method rejects, `ErrorHandler`/`FormUtils` (see [pcore-misc-details.md](../references/pcore-misc-details.md)) let you show a message to the user and/or log the failure. Show the user a clear, actionable, generic message via `ErrorHandler.setGenericFailedMessage`/the component's own error state — never the raw rejection object, a stack trace, or an internal error code that could reveal implementation details. If you log the error for diagnostics, make sure the logged payload doesn't include field values that could contain PII or auth tokens; log identifiers (case ID, context name) instead of full field state.
|
|
59
|
+
|
|
60
|
+
## Unsubscribe When a Component Loses Access, Not Just to Avoid Leaks
|
|
61
|
+
|
|
62
|
+
`PubSubUtils.subscribe`/`MessagingServiceManager.subscribe` keep delivering events to a callback until you unsubscribe. Beyond the memory-leak concern already covered in the [data-page-subscription-cleanup](../pconnect-data-subscriptions/cookbooks/data-page-subscription-cleanup.md) and [pubsub-cleanup](../pconnect-data-subscriptions/cookbooks/pubsub-cleanup.md) recipes, unsubscribing promptly on unmount also matters from a least-privilege standpoint — a component that has navigated away from a case shouldn't keep receiving that case's pushed updates in memory for longer than it's displayed.
|
|
63
|
+
|
|
64
|
+
## Treat User-Supplied Values as Untrusted Before Passing Them as API Parameters
|
|
65
|
+
|
|
66
|
+
Search boxes, list filters, and other user-editable inputs sometimes feed directly into a data page or REST call parameter (`DataApiUtils`, `RestClient` — see [pcore-rest-data-details.md](../references/pcore-rest-data-details.md)). The server enforces access control on the resulting request, but still validate/constrain the shape of user input client-side before sending it (for example, an expected numeric ID field should not accept arbitrary text, and an expected enum value should be checked against an allow-list rather than passed through) — this reduces malformed-request noise and avoids accidentally surfacing another record's error details in a client-visible error message.
|
|
67
|
+
|
|
68
|
+
## Practice Data Minimization When Forwarding Content to a GenAI Model
|
|
69
|
+
|
|
70
|
+
`GenAIAssistantUtils.sendMessage`/`sendMessageForAgent` and `GenAIUtils.rewriteText` (see [pcore-analytics-genai-details.md](../references/pcore-analytics-genai-details.md)) send whatever text you pass to a model, which may be hosted by a third party outside the case's normal data boundary. Don't forward more of the case than the task actually needs — for example, summarizing one field's free text doesn't require serializing the entire case payload into the prompt. Passing an entire case (including fields the current user shouldn't be exfiltrating verbatim into a model's context, such as another party's contact details embedded in an unrelated field) is a data-minimization problem independent of whether the model itself is trustworthy.
|
|
71
|
+
|
|
72
|
+
## Don't Assume a Case/Assignment ID From a URL Is Valid for the Current User
|
|
73
|
+
|
|
74
|
+
A deep link, browser history entry, or bookmarked route can carry a case ID, assignment handle, or `routeKey` that the current user is no longer (or was never) authorized to open. The server-side access check on the resulting request is the real control, but the component should treat "the ID resolved to a permission/not-found error" as an expected, handled outcome — not a bug — and show a clear "you don't have access to this" or "not found" state rather than a raw error, a blank screen, or (worse) a partially-rendered view built from a previous, unrelated case still held in memory.
|
|
75
|
+
|
|
76
|
+
## Be Deliberate About What `EnvironmentInfo` Details You Surface
|
|
77
|
+
|
|
78
|
+
`PCore.getEnvironmentInfo()` (see [pcore-environment-info-details.md](../references/pcore-environment-info-details.md)) exposes details about the running application/environment. Avoid logging or otherwise surfacing its full contents in a way that ends up in client-visible debug output, error reports, or telemetry sent to a third party — prefer reading and forwarding only the specific field a feature needs (e.g. the current locale) over passing the whole object along.
|
|
79
|
+
|
|
80
|
+
## Checklist
|
|
81
|
+
|
|
82
|
+
- Sanitize (or render as plain text) any feed post, comment, or GenAI Assistant reply before putting it in an HTML sink.
|
|
83
|
+
- Never let raw user-authored content become part of a GenAI system instruction — keep it clearly framed as data, not instructions.
|
|
84
|
+
- Never treat a hidden/disabled UI element, or a client-cached permission flag, as the actual access-control boundary — fail closed on unknown/unloaded permission state.
|
|
85
|
+
- Source deep-link `routeKey` values from `getActions()`, never from user-controllable URL input.
|
|
86
|
+
- Treat client-side upload/download type checks as UX only; the server's validation is authoritative, and previewed attachment content needs the same sanitization as any other untrusted HTML.
|
|
87
|
+
- Never hand-manage auth tokens/headers — use `RestClient`'s built-in mechanisms.
|
|
88
|
+
- Show users a generic, actionable error message; log identifiers, not raw field values or tokens.
|
|
89
|
+
- Unsubscribe on unmount/navigation-away, not only to avoid leaks but to stop receiving updates a component no longer has a reason to see.
|
|
90
|
+
- Validate/allow-list user-supplied values before they become REST or data page parameters.
|
|
91
|
+
- Forward only the case content a GenAI call actually needs — don't serialize an entire case into a prompt when one field would do.
|
|
92
|
+
- Handle a permission/not-found error from a URL-sourced case or assignment ID as an expected outcome, with a clear message — not a raw error or a stale view left over from a previous case.
|
|
93
|
+
- Read only the specific `EnvironmentInfo` field a feature needs rather than logging or forwarding the whole object.
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Testing Components That Use PCore/PConnect
|
|
3
|
+
description: How to mock PCore and PConnect in Jest + React Testing Library tests, covering type safety, the four API-driven UI states, cancellation, debouncing, and cleanup — without over-mocking or coupling tests to internal implementation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Testing Components That Use PCore/PConnect
|
|
7
|
+
|
|
8
|
+
> Part of [Cross-Cutting Best Practices](./README.md).
|
|
9
|
+
|
|
10
|
+
A component that calls `PCore.*` or `pConn.*` needs those calls mocked in a unit test — `PCore` and `PConnect` are runtime globals provided by the Constellation shell, never available in a plain Jest environment. This guide covers how to mock them correctly, and how to test the behaviors that are specific to API-driven components (loading/error states, cancellation, debouncing, cleanup).
|
|
11
|
+
|
|
12
|
+
## Only Mock What the Component Actually Uses
|
|
13
|
+
|
|
14
|
+
Do not create a large, all-purpose mock of `PCore` or `pConn` shared across every test. Mock only the methods the component under test actually calls, scoped to that test file:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
globalThis.PCore = {
|
|
19
|
+
getEnvironmentInfo: () => ({ getLocale: () => 'en-US' }),
|
|
20
|
+
getAttachmentUtils: () => ({ uploadAttachment: jest.fn() })
|
|
21
|
+
// Add more only as the component grows to call more of PCore.
|
|
22
|
+
} as unknown as typeof PCore;
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**NEVER** add `PCore`/`pConn` mocks to a shared `setupTests.ts` or `setupGlobals.ts` file. A shared global mock hides which methods a given component actually depends on, and one test's mock shape silently leaks into unrelated tests. Each test file owns its own mock.
|
|
27
|
+
|
|
28
|
+
## Mocking `pConn` (PConnect)
|
|
29
|
+
|
|
30
|
+
`pConn` is normally obtained via a `getPConnect` prop. Mock the prop function to return an object literal with only the methods the component calls:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
const mockPConnect = {
|
|
34
|
+
getValue: jest.fn().mockReturnValue(''),
|
|
35
|
+
getActionsApi: jest.fn().mockReturnValue({
|
|
36
|
+
updateFieldValue: jest.fn()
|
|
37
|
+
})
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
render(<MyComponent getPConnect={() => mockPConnect} />);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For a method the component calls conditionally (`pConn.someMethod?.()`), still provide it in the mock when the test exercises that branch — an optional-chained call that resolves to `undefined` will silently skip the branch instead of failing, which can mask a broken test.
|
|
44
|
+
|
|
45
|
+
## Keep Mocks Type-Safe, Not `any`
|
|
46
|
+
|
|
47
|
+
A mock typed as `any` compiles no matter how far it drifts from the real API — it will happily accept a call with the wrong argument count or a typo'd method name. Cast the mock object to the real type instead of typing it as `any`, so a genuine signature mismatch is caught at compile time rather than discovered at runtime:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const mockPConnect: Partial<PConnect> = {
|
|
51
|
+
getValue: jest.fn().mockReturnValue('')
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
render(<MyComponent getPConnect={() => mockPConnect as PConnect} />);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This is also why a full, shared, all-purpose mock is a trap even when it's convenient: a giant object that "implements everything" tends to get typed as `any`/`unknown` to avoid maintenance overhead, which silently defeats this safety net. Small, per-test, precisely typed mocks keep the type checker useful.
|
|
58
|
+
|
|
59
|
+
## Test the Four States of an API-Driven Component
|
|
60
|
+
|
|
61
|
+
Any component whose rendered output depends on a `Promise`-returning `PCore`/`pConn` call has (at least) four distinct states worth a test each — it's easy to only cover the success path and ship the other three unverified:
|
|
62
|
+
|
|
63
|
+
| State | What to assert |
|
|
64
|
+
| ------- | -------------------------------------------------------------------------------------------------------- |
|
|
65
|
+
| Loading | A loading indicator/skeleton is shown while the promise is still pending. |
|
|
66
|
+
| Success | The resolved data renders correctly, and the loading indicator is gone. |
|
|
67
|
+
| Empty | A resolved-but-empty result (e.g. `[]` or `null`) renders an empty state, not a blank screen or a crash. |
|
|
68
|
+
| Error | A rejected promise renders a visible error state — never a silently blank component. |
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
it('renders an empty state when the data page returns no rows', async () => {
|
|
72
|
+
const getListDataView = jest.fn().mockResolvedValue({ data: [] });
|
|
73
|
+
globalThis.PCore = { getDataApiUtils: () => ({ getListDataView }) } as unknown as typeof PCore;
|
|
74
|
+
|
|
75
|
+
render(<MyListComponent getPConnect={() => mockPConnect} />);
|
|
76
|
+
|
|
77
|
+
expect(await screen.findByText(/no results/i)).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Testing Promise-Returning Methods
|
|
82
|
+
|
|
83
|
+
Most PCore/PConnect methods that hit the network return a `Promise`. Mock both the success and rejection path, and assert on the resulting UI state rather than on the mock call itself:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
it('shows an error state when the upload fails', async () => {
|
|
87
|
+
const uploadAttachment = jest.fn().mockRejectedValue(new Error('upload failed'));
|
|
88
|
+
globalThis.PCore = { getAttachmentUtils: () => ({ uploadAttachment }) } as unknown as typeof PCore;
|
|
89
|
+
|
|
90
|
+
render(<AttachmentUploader />);
|
|
91
|
+
await userEvent.upload(screen.getByLabelText(/choose file/i), someFile);
|
|
92
|
+
|
|
93
|
+
expect(await screen.findByRole('alert')).toHaveTextContent(/upload failed/i);
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Testing Cancellation-Aware Requests
|
|
98
|
+
|
|
99
|
+
If a component uses `RestClient.getCancelTokenSource()`/`isRequestCanceled()` (see [pcore-rest-data-details.md](../references/pcore-rest-data-details.md)) to abort an in-flight request — for example, when a search term changes before the previous search resolved — test that a canceled request's resolution is ignored rather than shown as an error:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
it('ignores a canceled request instead of showing an error', async () => {
|
|
103
|
+
const cancel = jest.fn();
|
|
104
|
+
const invokeRestApi = jest.fn().mockRejectedValue(new Error('canceled'));
|
|
105
|
+
globalThis.PCore = {
|
|
106
|
+
getRestClient: () => ({
|
|
107
|
+
getCancelTokenSource: () => ({ token: {}, cancel }),
|
|
108
|
+
invokeRestApi,
|
|
109
|
+
isRequestCanceled: () => true
|
|
110
|
+
})
|
|
111
|
+
} as unknown as typeof PCore;
|
|
112
|
+
|
|
113
|
+
const { rerender } = render(<SearchField getPConnect={() => mockPConnect} query="a" />);
|
|
114
|
+
rerender(<SearchField getPConnect={() => mockPConnect} query="ab" />);
|
|
115
|
+
|
|
116
|
+
expect(cancel).toHaveBeenCalled();
|
|
117
|
+
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Testing Debounced or Buffered Input
|
|
122
|
+
|
|
123
|
+
If a component uses `AsynchronousUtils.getDebouncedSubject(delay)` or `DataApi.registerForBufferedCall({ waitTime })` (see [pcore-misc-details.md](../references/pcore-misc-details.md) and [pcore-rest-data-details.md](../references/pcore-rest-data-details.md)) to avoid firing a request on every keystroke, use fake timers to assert the call is debounced rather than fired once per keystroke:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
it('fires one lookup after typing stops, not one per keystroke', async () => {
|
|
127
|
+
jest.useFakeTimers();
|
|
128
|
+
const fetchData = jest.fn().mockResolvedValue([]);
|
|
129
|
+
// ...render, then simulate rapid input...
|
|
130
|
+
jest.advanceTimersByTime(300);
|
|
131
|
+
|
|
132
|
+
expect(fetchData).toHaveBeenCalledTimes(1);
|
|
133
|
+
jest.useRealTimers();
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Testing Subscribe/Unsubscribe Cleanup
|
|
138
|
+
|
|
139
|
+
Any component that subscribes to a data page, `PubSubUtils`, or `MessagingServiceManager` should have its unmount behavior tested — a missed unsubscribe is a real bug (see [Troubleshooting: subscription keeps firing after unmount](./troubleshooting-common-pitfalls.md#subscription-callback-keeps-firing-after-the-component-unmounts)), and it is straightforward to verify:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
it('unsubscribes on unmount', () => {
|
|
143
|
+
const subscribe = jest.fn().mockReturnValue('sub-1'); // MessagingServiceManager.subscribe returns the subscription id
|
|
144
|
+
const unsubscribe = jest.fn();
|
|
145
|
+
globalThis.PCore = { getMessagingServiceManager: () => ({ subscribe, unsubscribe }) } as unknown as typeof PCore;
|
|
146
|
+
|
|
147
|
+
const { unmount } = render(<MyComponent getPConnect={() => mockPConnect} />);
|
|
148
|
+
unmount();
|
|
149
|
+
|
|
150
|
+
expect(unsubscribe).toHaveBeenCalledWith('sub-1');
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`PubSubUtils.subscribe` returns nothing: it unsubscribes by event type and subscriber name, so assert `unsubscribe` was called with `(eventType, name)` instead.
|
|
155
|
+
|
|
156
|
+
## Avoid Testing Implementation Details
|
|
157
|
+
|
|
158
|
+
Assert on what the user would see or experience (rendered text, roles, an emitted callback prop), not on private internals. A test that asserts "component calls `getValue` exactly once" is brittle — it breaks on a harmless refactor (e.g. reading the value once and memoizing it vs. reading it twice) without the actual behavior changing. The one legitimate exception is cleanup/lifecycle behavior (unsubscribe, cancel) where the call itself **is** the behavior under test — there, asserting the mock was called is correct, not an implementation-detail test.
|
|
159
|
+
|
|
160
|
+
## Guard Against Mock/Reality Drift
|
|
161
|
+
|
|
162
|
+
A mock that always resolves happily gives false confidence once the real method's signature or behavior changes upstream. Periodically re-check a component's mocks against the method's actual documented signature in `references/` — an outdated mock (missing a newly required parameter, or resolving a shape the real method no longer returns) can keep a test green long after the component itself is broken against the real API.
|
|
163
|
+
|
|
164
|
+
## Prefer `findBy`/`waitFor` Over Manual Timers for Async Assertions
|
|
165
|
+
|
|
166
|
+
Once a mock resolves a `Promise`, the resulting state update is asynchronous — asserting on it immediately (before the microtask queue flushes) is a common source of flaky tests. Use RTL's `findBy*` queries (which internally retry until timeout) or `waitFor` instead of an arbitrary `setTimeout`/manual `await Promise.resolve()` chain:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Avoid: a magic delay that's either too short (flaky) or too long (slow suite).
|
|
170
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
171
|
+
expect(screen.getByText(/loaded/i)).toBeInTheDocument();
|
|
172
|
+
|
|
173
|
+
// Prefer: retries automatically until the assertion passes or times out.
|
|
174
|
+
expect(await screen.findByText(/loaded/i)).toBeInTheDocument();
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The one exception is a debounced/buffered call under fake timers (see above) — there, `jest.advanceTimersByTime` is the correct, deterministic way to advance past the delay, not a substitute for `findBy`/`waitFor` on the resulting UI update.
|
|
178
|
+
|
|
179
|
+
## Testing Locale-Dependent Rendering
|
|
180
|
+
|
|
181
|
+
If a component reads `pConn.getLocalizationService().getLocalizedText(...)`/`getLocalizedMessage(...)` (see [pcore-environment-info-details.md](../references/pcore-environment-info-details.md)) or calls `PCore.switchLocale(locale)`, mock the localization service to return a distinguishable value per key rather than echoing the key back unchanged — an identity mock (`getLocalizedText: (key) => key`) can hide a bug where the component accidentally renders the raw key instead of the resolved value in production:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
const mockPConnect = {
|
|
185
|
+
getLocalizationService: () => ({
|
|
186
|
+
getLocalizedText: (key: string) => `[localized]${key}`
|
|
187
|
+
})
|
|
188
|
+
};
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
If the component also needs to react to a locale switch (see [Troubleshooting: locale switched but labels didn't update](./troubleshooting-common-pitfalls.md#locale-switched-but-already-rendered-labels-didnt-update)), add a test that re-invokes the localization mock after simulating the switch and asserts the rendered text actually changed — not just that the switch function was called.
|
|
192
|
+
|
|
193
|
+
## Testing GenAI Assistant Conversations
|
|
194
|
+
|
|
195
|
+
`GenAIAssistantUtils.sendMessage`/`sendMessageForAgent` (see [pcore-analytics-genai-details.md](../references/pcore-analytics-genai-details.md)) resolve a `Promise`, and `GenAIUtils.rewriteText` resolves a discriminated union (`{ success: true, rewrittenText }` or `{ success: false, ... }`). Test both branches of the union explicitly — a mock that only ever resolves the success shape hides a missing/incorrect handler for the failure shape:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
it('shows a fallback message when text rewriting reports failure', async () => {
|
|
199
|
+
const rewriteText = jest.fn().mockResolvedValue({ success: false, message: 'unavailable' });
|
|
200
|
+
globalThis.PCore = {
|
|
201
|
+
getEnvironmentInfo: () => ({ isGenAIEnabled: () => true }),
|
|
202
|
+
getGenAIUtils: () => ({ rewriteText })
|
|
203
|
+
} as unknown as typeof PCore;
|
|
204
|
+
|
|
205
|
+
render(<RewriteButton getPConnect={() => mockPConnect} />);
|
|
206
|
+
await userEvent.click(screen.getByRole('button', { name: /rewrite/i }));
|
|
207
|
+
|
|
208
|
+
expect(await screen.findByText(/unavailable/i)).toBeInTheDocument();
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Also test the gate itself: when `isEnvironmentInfo().isGenAIEnabled()` returns `false`, assert the component doesn't render the GenAI-dependent affordance at all, rather than rendering it and letting the call fail.
|
|
213
|
+
|
|
214
|
+
## Build Small, Local Test Data Factories Instead of One Shared Fixture
|
|
215
|
+
|
|
216
|
+
A single large shared fixture object (for example, one exported `mockCaseData` used across many test files) tends to accumulate fields for whichever test needed them first, and every later test either depends on unrelated fields it doesn't understand or has to override several to get a clean state. Prefer a small factory function local to (or near) the test file that returns a minimal valid object, with overrides for the specific field a test cares about:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const buildCaseInfo = (overrides: Partial<CaseInfo> = {}): CaseInfo => ({
|
|
220
|
+
ID: 'WORK-1',
|
|
221
|
+
caseTypeName: 'Case',
|
|
222
|
+
status: 'Open',
|
|
223
|
+
...overrides
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('shows a resolved badge when the case is closed', () => {
|
|
227
|
+
render(<CaseStatusBadge caseInfo={buildCaseInfo({ status: 'Resolved' })} />);
|
|
228
|
+
expect(screen.getByText(/resolved/i)).toBeInTheDocument();
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Checklist
|
|
233
|
+
|
|
234
|
+
- Mock only the methods the component under test calls — no shared, all-purpose global mock.
|
|
235
|
+
- Type mocks against the real interface (`Partial<PConnect>`/`typeof PCore`) instead of `any`, so drift is caught at compile time.
|
|
236
|
+
- Cover all four states for any API-driven view: loading, success, empty, and error.
|
|
237
|
+
- If the component cancels stale requests, test that a canceled/superseded response doesn't produce a visible error.
|
|
238
|
+
- If the component debounces/buffers input, use fake timers to assert a single call, not one per keystroke.
|
|
239
|
+
- If the component subscribes to anything, add a test asserting the unsubscribe function is called on unmount.
|
|
240
|
+
- Assert on rendered output (`screen.getByRole`/`getByText`) rather than on internal state or an incidental mock call.
|
|
241
|
+
- Never make a real network call from a unit test — every `PCore`/`pConn` method the component touches must be mocked.
|
|
242
|
+
- Use `findBy*`/`waitFor` for async assertions instead of arbitrary `setTimeout` delays.
|
|
243
|
+
- Mock localization with a distinguishable value per key, not an identity echo, so a raw-key rendering bug can't hide.
|
|
244
|
+
- For a GenAI-gated affordance, test both the enabled/disabled gate and the success/failure branches of any discriminated-union result.
|
|
245
|
+
- Prefer small, local test-data factory functions over one large shared fixture that accumulates unrelated fields.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Troubleshooting Common Pitfalls
|
|
3
|
+
description: Symptom-indexed guide to the handful of mistakes that recur across all the PCore/PConnect skills — search by what's going wrong, not by method name.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Troubleshooting Common Pitfalls
|
|
7
|
+
|
|
8
|
+
> Part of [Cross-Cutting Best Practices](./README.md).
|
|
9
|
+
|
|
10
|
+
The reference files document what each method does; this guide documents what tends to go wrong even when a method is called correctly according to its signature. Search this page by symptom before assuming a method is broken or under-documented.
|
|
11
|
+
|
|
12
|
+
## Systematic Diagnosis Checklist
|
|
13
|
+
|
|
14
|
+
Before assuming a documented method is broken, work through this order — most "the API doesn't work" reports turn out to be one of these:
|
|
15
|
+
|
|
16
|
+
1. **Check the browser console for the actual error.** A `TypeError`, a rejected-promise stack, or a validation warning almost always names the real problem more precisely than the symptom you started with.
|
|
17
|
+
2. **Re-read the exact signature in the relevant `references/` file.** A missing/misordered required parameter is the single most common root cause — verify argument order and required vs. optional parameters before suspecting the method itself.
|
|
18
|
+
3. **Confirm the method exists in the version you're running against.** If it's a recently-documented method, check whether the runtime/mock you're testing against is old enough to not implement it yet (see the entry below on this exact symptom).
|
|
19
|
+
4. **Check subscribe/unsubscribe and async ordering.** A large share of "state is stale" or "still running after unmount" bugs are a missing cleanup or an out-of-order async resolution, not a wrong method call — see the dedicated entries below.
|
|
20
|
+
5. **If it's failing only in a test, check the mock, not the component.** A test mock can drift out of sync with the real method's shape; confirm the failure reproduces against the real API (or a fresh mock built directly from the reference file) before debugging the component itself.
|
|
21
|
+
|
|
22
|
+
## `TypeError: pConn.someMethod is not a function` (or the same for `PCore.someMethod`)
|
|
23
|
+
|
|
24
|
+
**Cause:** the method exists in a newer platform version than the one the current mock, test, or runtime is on. This is especially common right after a new `PConnect`/`PCore` method is added — existing Jest mocks of `pConn`/`PCore` across the codebase don't automatically gain new methods.
|
|
25
|
+
|
|
26
|
+
**Fix:** call new/optional methods with optional chaining, `pConn.someMethod?.()`, rather than assuming every method is always present. Don't remove the optional chaining just because it works locally — other call sites or older mocks may not implement the method yet.
|
|
27
|
+
|
|
28
|
+
## `MessageManager.clearMessages(...)` runs without error, but the message is still shown
|
|
29
|
+
|
|
30
|
+
**Cause:** `type` was omitted from the options object. The underlying filter only removes messages whose `type` matches what you passed — omitting it means nothing matches, so nothing is removed, silently.
|
|
31
|
+
|
|
32
|
+
**Fix:** always pass the `type` that matches the message you're clearing (for example, `'error'`), alongside `category`/`context`, when calling `PCore.getMessageManager().clearMessages(...)` (see [pcore-messaging-details.md](../references/pcore-messaging-details.md)).
|
|
33
|
+
|
|
34
|
+
## Deleting a case throws, or deletes the wrong thing
|
|
35
|
+
|
|
36
|
+
**Cause:** `ActionsApi.deleteWork` and `ActionsApi.deleteCaseInCreateStage` are not interchangeable — one is for a case that has already left its create stage (persisted, has a confirmed ID), the other is for a case still being created.
|
|
37
|
+
|
|
38
|
+
**Fix:** branch on the case's stage before choosing which method to call — see [safe-case-deletion](../pconnect-assignment-lifecycle/cookbooks/safe-case-deletion.md).
|
|
39
|
+
|
|
40
|
+
## Subscription callback keeps firing after the component unmounts
|
|
41
|
+
|
|
42
|
+
**Cause:** the unsubscribe/cancel function returned by `subscribe` (data page, `PubSubUtils`, or `MessagingServiceManager`) was never called. This can also surface as a React warning about updating state on an unmounted component.
|
|
43
|
+
|
|
44
|
+
**Fix:** always store and invoke the returned unsubscribe handle in a cleanup function (e.g. a `useEffect` cleanup) — see [data-page-subscription-cleanup](../pconnect-data-subscriptions/cookbooks/data-page-subscription-cleanup.md), [pubsub-cleanup](../pconnect-data-subscriptions/cookbooks/pubsub-cleanup.md), and [realtime-case-subscription](../pconnect-data-subscriptions/cookbooks/realtime-case-subscription.md).
|
|
45
|
+
|
|
46
|
+
## Locale switched, but already-rendered labels didn't update
|
|
47
|
+
|
|
48
|
+
**Cause:** `PCore.switchLocale(locale)` changes the active locale going forward — it does not retroactively re-resolve strings that a component already fetched and stored (e.g. in state) before the switch.
|
|
49
|
+
|
|
50
|
+
**Fix:** re-call `pConn.getLocalizationService().getLocalizedText(...)`/`getLocalizedMessage(...)` after the locale switch resolves, for any string held in component state — see [locale-switch-and-relocalize](../pconnect-navigation-locale-genai/cookbooks/locale-switch-and-relocalize.md).
|
|
51
|
+
|
|
52
|
+
## Two users' (or two components') personalization saves overwrite each other
|
|
53
|
+
|
|
54
|
+
**Cause:** `PersonalizationUtils` saves are keyed by `listComponentID` — if two different components/instances reuse the same ID, their saves collide, last write wins.
|
|
55
|
+
|
|
56
|
+
**Fix:** make `listComponentID` unique and stable per component instance (max 32 chars) — see [view-personalization](../pconnect-lists-and-views/cookbooks/view-personalization.md).
|
|
57
|
+
|
|
58
|
+
## A cascading dependent field doesn't refresh after the field it depends on changes
|
|
59
|
+
|
|
60
|
+
**Cause:** the dependent field's cascade subscription wasn't set up before the source field's change was dispatched, or the subscription's cleanup fired too early.
|
|
61
|
+
|
|
62
|
+
**Fix:** follow the subscribe-before-trigger ordering in [cascading-dependent-fields](../pconnect-forms-and-fields/cookbooks/cascading-dependent-fields.md).
|
|
63
|
+
|
|
64
|
+
## `ContainerUtils.closeContainerItem` doesn't behave like `ContainerManager.removeContainerItem`
|
|
65
|
+
|
|
66
|
+
**Cause:** these two methods are not interchangeable. `closeContainerItem` performs a dirty-state check before closing (part of `ContainerUtils`); `removeContainerItem` (part of `ContainerManager`) removes an item from the container's item list without that check.
|
|
67
|
+
|
|
68
|
+
**Fix:** use the one that matches your intent — see [container-item-lifecycle](../pconnect-lists-and-views/cookbooks/container-item-lifecycle.md).
|
|
69
|
+
|
|
70
|
+
## A data page still shows old data right after a save
|
|
71
|
+
|
|
72
|
+
**Cause:** data page results are cached; saving a record through a different call doesn't automatically invalidate a data page instance that already cached the old result.
|
|
73
|
+
|
|
74
|
+
**Fix:** call `DataPageUtils.markDataInstanceDirty(dataPageName, parameters)` after the save, or re-fetch with the `{ invalidateCache: true }` option, before reading the data page again — see [pcore-rest-data-details.md](../references/pcore-rest-data-details.md). See also [Performance & Efficiency: cache deliberately](./performance-and-efficiency.md#cache-data-pages-but-invalidate-deliberately).
|
|
75
|
+
|
|
76
|
+
## A typeahead/search field hammers the server on every keystroke
|
|
77
|
+
|
|
78
|
+
**Cause:** the field's `fetchData`/REST call is wired directly to the input's `onChange`, with no debouncing or buffering.
|
|
79
|
+
|
|
80
|
+
**Fix:** debounce with `AsynchronousUtils.getDebouncedSubject(delay)` or configure `DataApi.registerForBufferedCall({ waitTime, maxSize })` before fetching — see [Performance & Efficiency: debounce and buffer](./performance-and-efficiency.md#debounce-or-buffer-user-triggered-calls).
|
|
81
|
+
|
|
82
|
+
## The UI shows "online" right after the network actually dropped (or vice versa)
|
|
83
|
+
|
|
84
|
+
**Cause:** the component reads `navigator.onLine` directly or adds its own `window` `online`/`offline` listeners instead of using the platform's own connectivity tracking, and misses an edge case the platform already handles.
|
|
85
|
+
|
|
86
|
+
**Fix:** use `PCore.getNetworkUtils().isOnline()`/`onStatusChange(callback)` instead — see [pcore-environment-locale-namespace.md](../references/pcore-environment-locale-namespace.md).
|
|
87
|
+
|
|
88
|
+
## An older async response overwrites a newer one (a fast-typed search shows results for an earlier query)
|
|
89
|
+
|
|
90
|
+
**Cause:** two overlapping requests were in flight (e.g. triggered by two keystrokes) and the one that started first happened to resolve last, and nothing discarded the stale response.
|
|
91
|
+
|
|
92
|
+
**Fix:** cancel the superseded request with `RestClient.getCancelTokenSource()`/check `isRequestCanceled(error)` before treating a rejection as a real error, or otherwise tag each request and ignore a resolution that isn't for the latest one — see [pcore-rest-data-details.md](../references/pcore-rest-data-details.md) and [Error Handling & Resiliency: distinguish cancellation from failure](./error-handling-and-resiliency.md#distinguish-cancellation-from-failure).
|
|
93
|
+
|
|
94
|
+
## The same generic error message shows for every failure, which makes triage hard
|
|
95
|
+
|
|
96
|
+
**Cause:** `ErrorHandler`'s generic failure message (shown when a component fails to render) hasn't been customized, so every unrelated failure looks identical to the user/tester.
|
|
97
|
+
|
|
98
|
+
**Fix:** call `PCore.getErrorHandler().setGenericFailedMessage(message)` with a clearer default, and prefer surfacing a specific, component-level error state over relying solely on the generic fallback — see [pcore-misc-details.md](../references/pcore-misc-details.md).
|
|
99
|
+
|
|
100
|
+
## A field's validation error message won't clear even after the value is fixed
|
|
101
|
+
|
|
102
|
+
**Cause:** `ValidationApi.validate(value, propertyName?)` (see [pconnect-validation-api.md](../references/pconnect-validation-api.md)) both validates and writes the result to application state — if the corrected value is never re-passed through `validate()` (for example, a fix applied programmatically rather than through the field's own change handler), the stale message stays in state because nothing told it to re-evaluate.
|
|
103
|
+
|
|
104
|
+
**Fix:** re-run `validate()` with the corrected value through the same path a normal user edit would take, rather than only updating the underlying value and assuming the displayed message updates itself.
|
|
105
|
+
|
|
106
|
+
## An attachment upload appears stuck and never resolves
|
|
107
|
+
|
|
108
|
+
**Cause:** `AttachmentUtils.uploadAttachment` (see [pcore-case-collaboration-details.md](../references/pcore-case-collaboration-details.md)) rejected or the resolved promise's status was never checked, and nothing surfaces a stuck/failed state to the user — the UI is left showing a permanent "uploading" indicator instead of an error.
|
|
109
|
+
|
|
110
|
+
**Fix:** always attach a `.catch`/handle the rejection path for `uploadAttachment` (see [Error Handling & Resiliency: never fail silently](./error-handling-and-resiliency.md#never-fail-silently)), and pair it with a reasonable timeout guard (see [Error Handling & Resiliency: guard against a call that never resolves](./error-handling-and-resiliency.md#guard-against-a-call-that-never-resolves)) so a genuinely hung request doesn't leave the indicator spinning forever.
|
|
111
|
+
|
|
112
|
+
## A GenAI Assistant conversation's history is empty after a page reload
|
|
113
|
+
|
|
114
|
+
**Cause:** `GenAIAssistantUtils.createConversation`/`sendMessage` (see [pcore-analytics-genai-details.md](../references/pcore-analytics-genai-details.md)) manage the conversation server-side, but the component only reads messages from its own local state (populated as the user sends/receives messages in that session) rather than re-fetching the conversation on mount.
|
|
115
|
+
|
|
116
|
+
**Fix:** on mount, call `getMessages(assistantID, conversationID, context)`/`getConversations(...)` to rehydrate history from the server instead of assuming local state is the source of truth — local state should be treated as a cache of what `getMessages` would return, not the only copy.
|
|
117
|
+
|
|
118
|
+
## The autosave indicator stays "unsaved" after a successful save
|
|
119
|
+
|
|
120
|
+
**Cause:** a change was made through a path that doesn't call `AutosaveUtils.notifyChange(context, propReference, value)` (see [pcore-misc-details.md](../references/pcore-misc-details.md)) — for example, a value set directly via a lower-level state update instead of the field's normal change handler — so the autosave engine never learns the edit happened, and its status subscription never transitions out of the stale state.
|
|
121
|
+
|
|
122
|
+
**Fix:** route every user-facing edit path through the same mechanism a normal field edit uses, so `notifyChange` fires consistently; don't bypass it for "programmatic" updates that are still meant to be autosaved.
|
|
123
|
+
|
|
124
|
+
## A buffered/typeahead call never fires, even though the user kept typing
|
|
125
|
+
|
|
126
|
+
**Cause:** `DataApi.registerForBufferedCall({ waitTime, maxSize })` (see [pcore-rest-data-details.md](../references/pcore-rest-data-details.md)) batches calls until either `waitTime` elapses with no new input or `maxSize` queued calls are reached — if `waitTime` is set too high relative to how the component's lifecycle unmounts/resets between keystrokes (for example, the component re-creates the `DataApi` instance on every render instead of reusing one), the buffer can be discarded before it ever fires.
|
|
127
|
+
|
|
128
|
+
**Fix:** create the buffered `DataApi` instance once (not on every render) and confirm `waitTime` is tuned to the input's actual typing cadence — see [Performance & Efficiency: debounce or buffer user-triggered calls](./performance-and-efficiency.md#debounce-or-buffer-user-triggered-calls).
|
|
129
|
+
|
|
130
|
+
## Still stuck?
|
|
131
|
+
|
|
132
|
+
If the symptom isn't listed above, first re-check the exact signature in the relevant `references/` file — many "the method doesn't work" reports turn out to be a missed required parameter, not a bug. If the method genuinely isn't documented anywhere in these skills, treat it as unsupported rather than assuming it exists at runtime.
|