@salesforce/afv-skills 1.45.0 → 1.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/agentforce-observe/SKILL.md +32 -4
- package/skills/agentforce-observe/references/ahm-alerts.md +719 -0
- package/skills/automation-flow-generate/SKILL.md +11 -5
- package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +275 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/README.md +32 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls +75 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls-meta.xml +5 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/interview-answers.json +13 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +10 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/create.json +20 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/update.json +16 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +273 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +236 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/reference-example-set-comment-value.md +132 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +211 -0
- package/skills/dx-code-analyzer-configure/scripts/validate-config.sh +14 -10
- package/skills/dx-code-analyzer-run/scripts/apply-fixes.js +45 -4
- package/skills/dx-code-analyzer-run/scripts/describe-rule.js +52 -32
- package/skills/dx-devops-project-manage/SKILL.md +197 -0
- package/skills/dx-devops-project-manage/examples/common-workflows.md +197 -0
- package/skills/dx-devops-project-manage/references/cli-commands.md +295 -0
- package/skills/dx-devops-project-manage/scripts/create-project.sh +48 -0
- package/skills/dx-devops-project-manage/scripts/list-projects.sh +51 -0
- package/skills/dx-devops-project-manage/scripts/update-project.sh +96 -0
- package/skills/education-cloud-academic-calendar-generate/SKILL.md +225 -0
- package/skills/education-cloud-academic-calendar-generate/examples/quarter-calendar.json +47 -0
- package/skills/education-cloud-academic-calendar-generate/examples/sample-output.md +57 -0
- package/skills/education-cloud-academic-calendar-generate/examples/semester-calendar.json +54 -0
- package/skills/education-cloud-academic-calendar-generate/references/calendar-systems.md +127 -0
- package/skills/education-cloud-academic-calendar-generate/references/date-validation.md +222 -0
- package/skills/education-cloud-academic-calendar-generate/references/foundation_prerequisites.md +40 -0
- package/skills/education-cloud-academic-calendar-generate/scripts/validate_calendar_dates.py +143 -0
- package/skills/education-cloud-course-catalog-migrate/SKILL.md +321 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas-detail.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/large-catalog-handling.md +42 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/batch_courses.py +36 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_linked_courses.py +51 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_modality_variants.py +48 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/resolve_api_version.py +43 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/split_course_code.py +39 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/validate_completeness.py +54 -0
- package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +3 -5
- package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +177 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/agent-and-subagents.md +151 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/customer-narration.md +34 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/execution-model.md +54 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/flows.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/grounding.md +199 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/permissions.md +183 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/platform-enablement.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +158 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/routing.md +141 -0
- package/skills/experience-cms-brand-apply/SKILL.md +5 -5
- package/skills/experience-cms-brand-create/SKILL.md +2 -2
- package/skills/experience-cms-content-generate/SKILL.md +1 -0
- package/skills/experience-cms-content-render/SKILL.md +173 -0
- package/skills/experience-cms-content-render/assets/angular/DetailPage.component.ts +25 -0
- package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +133 -0
- package/skills/experience-cms-content-render/assets/angular/TypeList.component.ts +38 -0
- package/skills/experience-cms-content-render/assets/angular/TypeRenderer.component.ts +90 -0
- package/skills/experience-cms-content-render/assets/angular/cms-content.component.ts +248 -0
- package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +100 -0
- package/skills/experience-cms-content-render/assets/react/DetailPage.tsx +20 -0
- package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +129 -0
- package/skills/experience-cms-content-render/assets/react/TypeList.tsx +40 -0
- package/skills/experience-cms-content-render/assets/react/TypeRenderer.tsx +64 -0
- package/skills/experience-cms-content-render/assets/react/heuristicRenderer.tsx +310 -0
- package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +129 -0
- package/skills/experience-cms-content-render/assets/shared/cmsContentType.ts +49 -0
- package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +96 -0
- package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +55 -0
- package/skills/experience-cms-content-render/references/bulk-loading.md +60 -0
- package/skills/experience-cms-content-render/references/codegen-guardrails.md +111 -0
- package/skills/experience-cms-content-render/references/detail-pages.md +87 -0
- package/skills/experience-cms-content-render/references/embed-recipes.md +127 -0
- package/skills/experience-cms-content-render/references/failure-modes.md +96 -0
- package/skills/experience-cms-content-render/references/heuristic-render-rules.md +131 -0
- package/skills/experience-cms-content-render/references/init-scaffold.md +122 -0
- package/skills/experience-cms-content-render/references/interaction-model.md +173 -0
- package/skills/experience-cms-content-render/references/package-api.md +106 -0
- package/skills/experience-cms-content-render/references/schema-sync.md +114 -0
- package/skills/experience-cms-content-render/references/styling-scopes.md +65 -0
- package/skills/experience-cms-content-render/references/verify.md +49 -0
- package/skills/experience-cms-content-type-generate/SKILL.md +2 -2
- package/skills/experience-content-media-stock-image-search/SKILL.md +5 -4
- package/skills/experience-search-coordinate/SKILL.md +198 -0
- package/skills/experience-search-coordinate/assets/search-payload-template.json +25 -0
- package/skills/experience-search-coordinate/references/content-route.md +313 -0
- package/skills/experience-search-coordinate/references/content-type-discovery.md +57 -0
- package/skills/experience-search-coordinate/references/media-route.md +172 -0
- package/skills/experience-search-coordinate/references/scope-resolution.md +14 -0
- package/skills/experience-ui-bundle-localize/SKILL.md +1 -1
- package/skills/experience-ui-bundle-localize/references/i18n-setup.md +5 -3
- package/skills/experience-ui-bundle-project-generate/SKILL.md +18 -14
- package/skills/experience-ui-bundle-project-generate/references/angular-project-generate.md +22 -0
- package/skills/experience-ui-bundle-project-generate/references/react-project-generate.md +20 -0
- package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +58 -54
- package/skills/experience-ui-bundle-salesforce-data-access/references/caching.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/graphiti-cli.md +2 -2
- package/skills/experience-ui-bundle-salesforce-data-access/references/migration.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/rest-and-integration.md +2 -1
- package/skills/experience-ui-bundle-salesforce-data-access/references/sdk-api.md +6 -0
- package/skills/experience-ui-bundle-site-generate/SKILL.md +59 -8
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md +8 -3
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-language-settings.md +120 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +336 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +143 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +127 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-deploy-commands.md +116 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +111 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-overview.md +312 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +171 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-state-tracking.md +64 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-trigger-handlers.md +122 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-overview.md +335 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-user-provisioning-details.md +140 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +196 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +307 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +211 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/state-machine-and-changes.md +108 -0
- package/skills/life-sciences-kam-coordinate/SKILL.md +241 -0
- package/skills/life-sciences-kam-coordinate/references/orchestration-flow.md +152 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +79 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-deploy-commands.md +131 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-kam-config-records.md +85 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +112 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-overview.md +202 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +67 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-state-tracking.md +65 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-trigger-handlers.md +123 -0
- package/skills/life-sciences-kam-coordinate/references/stage-4-participant-role-and-sprint.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +337 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +248 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-ipad-validation-script.md +35 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-details.md +146 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-overview.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/state-machine-and-changes.md +114 -0
- package/skills/life-sciences-prerequisites-validate/SKILL.md +138 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-org-settings.md +190 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-user-and-package.md +211 -0
- package/skills/life-sciences-territory-configure/SKILL.md +217 -0
- package/skills/life-sciences-territory-configure/references/territory-metadata.md +262 -0
- package/skills/platform-apex-logs-debug/SKILL.md +7 -7
- package/skills/platform-custom-application-generate/SKILL.md +4 -4
- package/skills/platform-custom-object-generate/SKILL.md +7 -7
- package/skills/platform-custom-tab-generate/SKILL.md +1 -1
- package/skills/platform-dsar-policy-manage/SKILL.md +272 -0
- package/skills/platform-dsar-policy-manage/references/configure.md +106 -0
- package/skills/platform-dsar-policy-manage/references/export-and-history.md +123 -0
- package/skills/platform-dsar-policy-manage/references/gap-analysis-guide.md +150 -0
- package/skills/platform-dsar-policy-manage/references/gap-scan.md +129 -0
- package/skills/platform-dsar-policy-manage/references/headless-sor.md +59 -0
- package/skills/platform-dsar-policy-manage/references/report-format.md +59 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/__init__.py +0 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/test_validate_policy_tree.py +76 -0
- package/skills/platform-dsar-policy-manage/scripts/validate-policy-tree.py +130 -0
- package/skills/platform-flexipage-generate/SKILL.md +4 -0
- package/skills/platform-list-view-generate/SKILL.md +1 -0
- package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +359 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/official-examples.md +69 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/scenarios.md +187 -0
- package/skills/platform-soql-query/SKILL.md +8 -8
- package/skills/platform-value-set-generate/SKILL.md +2 -2
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +20 -27
- package/skills/service-native-voice-recording-transcription-configure/SKILL.md +47 -27
- package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +13 -9
- package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +104 -45
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Init scaffold — runtime tree, verbatim copy, foundation sets
|
|
2
|
+
|
|
3
|
+
Detail for the **Init** mode and **Init vs Embed detection** (SKILL.md → *Modes*,
|
|
4
|
+
*Init vs Embed detection*). SKILL.md holds the decision rules; this file holds the
|
|
5
|
+
file tree, the Read→Write copy mechanics, and the per-framework foundation sets used
|
|
6
|
+
for detection.
|
|
7
|
+
|
|
8
|
+
## Runtime tree (framework-scoped under `src/cms/`)
|
|
9
|
+
|
|
10
|
+
Init writes the shared runtime once, plus the detected framework's pair:
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
src/cms/
|
|
14
|
+
shared/ ← framework-agnostic, always written
|
|
15
|
+
cmsCore.types.ts ← CmsExternalRef, CmsRef, AnyCmsRef, CmsFieldType; re-exports CmsImageField
|
|
16
|
+
externalRefs.ts ← ref catalog + CMS_CHANNEL_ID_FALLBACK (marker-blocked)
|
|
17
|
+
react/ ← written ONLY when framework=react
|
|
18
|
+
useCmsItem.ts ← item hook: cache + inflight dedup (StrictMode-safe)
|
|
19
|
+
heuristicRenderer.tsx ← schema-less renderer: field-pick + value-shape dispatch + sanitizer plug + resolveImageSrc (the ONE RichText injection site)
|
|
20
|
+
angular/ ← written ONLY when framework=angular
|
|
21
|
+
cms-item.service.ts ← item service: shared cache + inflight dedup
|
|
22
|
+
cms-content.component.ts ← schema-less renderer (the ONE RichText injection site)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Per-type generated code lands under the framework's `types/` dir, created on the
|
|
26
|
+
first Embed; each renderer imports its sibling by direct path, so there is no barrel
|
|
27
|
+
to maintain. Detail pages land under `src/pages/<type>/`. The skill never creates
|
|
28
|
+
tests, CSS files, router files, or app entry; `main.tsx`/`App.tsx` / Angular
|
|
29
|
+
bootstrap must pre-exist.
|
|
30
|
+
|
|
31
|
+
The toolkit's `content-metadata.json` catalog (`<appRoot>/public/content-metadata.json`)
|
|
32
|
+
is written **on demand at the first `CmsRef` embed that needs a channel** — NOT at Init,
|
|
33
|
+
and NOT for foreign-ref-only apps (Rule 1; shape + policy in `references/package-api.md`).
|
|
34
|
+
It is a pipeline-regenerated data seam (the deploy pipeline overwrites it per target org),
|
|
35
|
+
so it is **explicitly NOT part of the foundation detection sets below** — otherwise every
|
|
36
|
+
pre-existing app that already carries a catalog would mis-classify as drift.
|
|
37
|
+
|
|
38
|
+
## Emit runtime files VERBATIM — Read→Write, don't retype
|
|
39
|
+
|
|
40
|
+
The foundation files are pure copies: never reconstruct API behaviour from memory
|
|
41
|
+
(the templates encode the StrictMode hook, the sanitizer seam, and the correct
|
|
42
|
+
package call sites). Only `cmsContentType.ts` and the renderer templates carry
|
|
43
|
+
`{{…}}` substitution — those are handled in Embed, not here.
|
|
44
|
+
|
|
45
|
+
For each file in the set, **`Read` the template by its skill-relative path and
|
|
46
|
+
`Write` it unchanged to the destination**. The harness loads `assets/` alongside
|
|
47
|
+
SKILL.md, so no CWD, absolute path, or shell command is involved. **Skip any
|
|
48
|
+
destination that already exists — never overwrite**, so this is safe for Init *and*
|
|
49
|
+
the drift-complete branch. If a `Read` fails because the asset is not loadable in
|
|
50
|
+
this harness, FALL BACK to the byte-by-byte VERBATIM copy for that file.
|
|
51
|
+
|
|
52
|
+
| `Read` (skill-relative) | `Write` (under `<appRoot>/`) |
|
|
53
|
+
|-------------------------|------------------------------|
|
|
54
|
+
| `assets/shared/cmsCore.types.ts` | `src/cms/shared/cmsCore.types.ts` |
|
|
55
|
+
| `assets/shared/externalRefs.ts` | `src/cms/shared/externalRefs.ts` |
|
|
56
|
+
| `assets/react/useCmsItem.ts` *(react)* | `src/cms/react/useCmsItem.ts` |
|
|
57
|
+
| `assets/react/heuristicRenderer.tsx` *(react)* | `src/cms/react/heuristicRenderer.tsx` |
|
|
58
|
+
| `assets/angular/cms-item.service.ts` *(angular)* | `src/cms/angular/cms-item.service.ts` |
|
|
59
|
+
| `assets/angular/cms-content.component.ts` *(angular)* | `src/cms/angular/cms-content.component.ts` |
|
|
60
|
+
|
|
61
|
+
Copy the shared pair always, plus the detected framework's pair. This NEVER touches
|
|
62
|
+
the `{{…}}`-substituted templates (`cmsContentType.ts`, the per-type renderers,
|
|
63
|
+
detail pages).
|
|
64
|
+
|
|
65
|
+
No dev proxy is scaffolded: the CMS delivery backend serves CORS headers, so the
|
|
66
|
+
browser fetches the CDN directly in dev and `vite.config.ts` stays the standard
|
|
67
|
+
React config. (The former dev-proxy rule was removed; SKILL.md → *Codegen
|
|
68
|
+
guardrails*.)
|
|
69
|
+
|
|
70
|
+
## Media renderer — VERBATIM, emitted on the first media embed
|
|
71
|
+
|
|
72
|
+
Standalone media (`sfdc_cms__{image,audio,video,document}`) is a predefined type: it
|
|
73
|
+
renders through one shared, verbatim `MediaRenderer` per framework, not a generated
|
|
74
|
+
per-type renderer. On the FIRST media embed, `Read`→`Write` the framework's media
|
|
75
|
+
renderer (skip if it already exists — never overwrite):
|
|
76
|
+
|
|
77
|
+
| `Read` (skill-relative) | `Write` (under `<appRoot>/`) |
|
|
78
|
+
|-------------------------|------------------------------|
|
|
79
|
+
| `assets/react/MediaRenderer.tsx` *(react)* | `src/cms/react/MediaRenderer.tsx` |
|
|
80
|
+
| `assets/angular/MediaRenderer.component.ts` *(angular)* | `src/cms/angular/MediaRenderer.component.ts` |
|
|
81
|
+
|
|
82
|
+
The media types (`CmsMediaBody`, `CmsMediaField`, `CmsMediaType`) ship inside the
|
|
83
|
+
always-written `shared/cmsCore.types.ts`, so a media embed onto an existing runtime
|
|
84
|
+
needs only the one renderer file plus the ref entry — provided the runtime's
|
|
85
|
+
`cmsCore.types.ts` carries the media exports (a pre-media scaffold that lacks them is
|
|
86
|
+
Init drift on that file; HALT, don't silently patch). On the `contentKey` path URL
|
|
87
|
+
resolution is entirely toolkit-owned (`resolveCmsImageUrl` / `resolveMediaUrl`); no
|
|
88
|
+
local prefix helper ships. On the foreign `url` path the renderer uses `ref.url`
|
|
89
|
+
directly — no resolver, no fetch (Rule 1/Rule 2).
|
|
90
|
+
|
|
91
|
+
**The media renderer is NOT in the foundation sets below.** Init/Embed detection is
|
|
92
|
+
unchanged: a runtime scaffolded before media existed is still a complete `embed`
|
|
93
|
+
runtime, and the media renderer is written lazily on the first media embed exactly
|
|
94
|
+
like a per-type `<type>.ts`. Do not add `MediaRenderer.*` to the detection sets, or
|
|
95
|
+
every pre-media app would mis-classify as drift.
|
|
96
|
+
|
|
97
|
+
## Foundation sets (used by Init vs Embed detection)
|
|
98
|
+
|
|
99
|
+
Check every path for existence against the target uiBundle root (the dir with
|
|
100
|
+
`src/`), then classify by how many are present — not a single canary:
|
|
101
|
+
|
|
102
|
+
- **React foundation set:** `src/cms/shared/cmsCore.types.ts`,
|
|
103
|
+
`src/cms/shared/externalRefs.ts`, `src/cms/react/heuristicRenderer.tsx`,
|
|
104
|
+
`src/cms/react/useCmsItem.ts`.
|
|
105
|
+
- **Angular foundation set:** `src/cms/shared/cmsCore.types.ts`,
|
|
106
|
+
`src/cms/shared/externalRefs.ts`, `src/cms/angular/cms-content.component.ts`,
|
|
107
|
+
`src/cms/angular/cms-item.service.ts`.
|
|
108
|
+
|
|
109
|
+
The app root is inside a uiBundle, not the SFDX project root, so foundation files
|
|
110
|
+
resolve to `force-app/main/default/uiBundles/<bundleName>/src/cms/…`. Run the check
|
|
111
|
+
against the target bundle when known; otherwise scan each subdir of
|
|
112
|
+
`force-app/main/default/uiBundles/` and pick the scaffolded one, asking the user
|
|
113
|
+
with options if multiple qualify (`references/interaction-model.md` →
|
|
114
|
+
*Candidate uiBundle*).
|
|
115
|
+
|
|
116
|
+
### Drift-complete branch
|
|
117
|
+
|
|
118
|
+
On a **drift** classification, when the user answers **Yes, write the missing
|
|
119
|
+
files**: write ONLY the missing files VERBATIM from `assets/…` via the Read→Write
|
|
120
|
+
copy set above (it skips destinations that already exist), then re-check. Never
|
|
121
|
+
touch or "refresh" the present ones; never silently complete, overwrite, or reset a
|
|
122
|
+
present file.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Interaction model — ask with options, and where the session ends
|
|
2
|
+
|
|
3
|
+
This skill is **interactive**. At every decision the prompt leaves open, ASK the
|
|
4
|
+
user with an **option-select** question (the `AskUserQuestion` tool) instead of
|
|
5
|
+
free-text: one decision per question, a short header, 2–4 labeled options, and
|
|
6
|
+
wait for the answer. Never guess past an unanswered required decision — it HALTs.
|
|
7
|
+
|
|
8
|
+
Two answers are **terminal**: they END the session with no scaffolding. They are called out below and in `SKILL.md` (*HALT behaviour*) and `references/failure-modes.md`.
|
|
9
|
+
|
|
10
|
+
## Bypass mode (non-interactive / automation)
|
|
11
|
+
|
|
12
|
+
When the prompt explicitly asks to bypass the interaction model — e.g. "bypass the
|
|
13
|
+
interaction model", "non-interactive", "don't ask", "proceed/yes to all prompts",
|
|
14
|
+
"auto-proceed" — do **not** call `AskUserQuestion` at all. Treat every would-be
|
|
15
|
+
question as answered with the **affirmative / recommended** intent ("proceed/yes")
|
|
16
|
+
and continue. This is for automation/eval where nothing can reply to a prompt.
|
|
17
|
+
|
|
18
|
+
Per-decision defaults in bypass mode (the Recommended option, or the obvious value):
|
|
19
|
+
|
|
20
|
+
| Decision | Bypass default |
|
|
21
|
+
|----------|----------------|
|
|
22
|
+
| Framework — ambiguous | The framework the prompt names; if truly none, HALT (no safe default). |
|
|
23
|
+
| Run Init? | **Yes, scaffold it** (proceed — the affirmative). |
|
|
24
|
+
| Complete a partial runtime? (drift) | **Yes, write the missing files.** |
|
|
25
|
+
| Content type — unnamed | The type the prompt states or most strongly implies. |
|
|
26
|
+
| Media alt text — foreign `url` image/document, none from hand-off | Register with `altText: ''` (image → decorative) / no `title` (document → "Download"). Bypass ships the a11y fallback rather than HALT — this is not a safe-default-less stop. |
|
|
27
|
+
| Channel ID — unresolved for a contentKey | **No safe default — HALT.** Bypass never invents a channel; write only a catalog/prompt-provided one, else HALT (like unnamed in-place placement). |
|
|
28
|
+
| Channel conflict — prompt channel differs from catalog | **Keep the catalog channel** (the Recommended option): leave `public/content-metadata.json` unchanged, do NOT overwrite it with the prompt channel. The catalog is primary. |
|
|
29
|
+
| Render target | **Dedicated detail page/route.** |
|
|
30
|
+
| Group render target | **A detail page for each item.** |
|
|
31
|
+
| Candidate uiBundle | The single scaffoldable/named bundle. |
|
|
32
|
+
|
|
33
|
+
Bypass changes ONLY the answer source (recommended option instead of a user reply)
|
|
34
|
+
— it makes NO pipeline-logic change. It does **not** suppress stops that are not
|
|
35
|
+
questions: a genuine **zero-content** search result still ends the session (there is
|
|
36
|
+
nothing to render), and any true HALT with no safe default still stops — e.g. an
|
|
37
|
+
**in-place** render with no placement named anywhere in the prompt (bypass defaults
|
|
38
|
+
to a detail page, so this only bites if the prompt forces in-place without a target),
|
|
39
|
+
a candidate-bundle choice that can't be resolved to one bundle, or a **`CmsRef` whose
|
|
40
|
+
channel is unresolvable** (no catalog, no prompt-provided channelId) — bypass never
|
|
41
|
+
invents a channel, so this HALTs.
|
|
42
|
+
|
|
43
|
+
## How to ask
|
|
44
|
+
|
|
45
|
+
- **One decision per question.** Short `header` chip (≤12 chars); 2–4 options.
|
|
46
|
+
- **Recommended option FIRST**, its label suffixed " (Recommended)".
|
|
47
|
+
- Each option carries a one-line description of what happens if chosen.
|
|
48
|
+
- **Free-text only for open answers** — a component/region name, a URL, a slug.
|
|
49
|
+
There the option list is a fallback; let the user type the value ("Other").
|
|
50
|
+
- After a **terminal** option (declined Init, zero search results), END the turn.
|
|
51
|
+
Do not scaffold, do not proceed, do not re-ask in a loop.
|
|
52
|
+
|
|
53
|
+
## Decision points
|
|
54
|
+
|
|
55
|
+
### Framework — ambiguous
|
|
56
|
+
Both `react` and `@angular/core` are dependencies (unknown/neither → HALT, not a
|
|
57
|
+
supported app; do not ask).
|
|
58
|
+
- Question: "Which framework should I target for the CMS renderer?"
|
|
59
|
+
- Header: `Framework`
|
|
60
|
+
- Options: **React** — use the `assets/react/*` templates · **Angular** — use the
|
|
61
|
+
`assets/angular/*` templates.
|
|
62
|
+
|
|
63
|
+
### Run Init? — runtime not scaffolded · ⛔ TERMINAL on "No"
|
|
64
|
+
All foundation files are missing (see *Init vs Embed detection*).
|
|
65
|
+
- Question: "The CMS runtime isn't scaffolded in this uiBundle yet. Scaffold it now (Init)?"
|
|
66
|
+
- Header: `Init`
|
|
67
|
+
- Options:
|
|
68
|
+
- **Yes, scaffold it (Recommended)** — run `npm i` for the toolkit and write the
|
|
69
|
+
foundation runtime once, then continue to Embed.
|
|
70
|
+
- **No — stop here; the skill will HALT and the session will end** — nothing is
|
|
71
|
+
installed or written; the skill cannot Embed without the runtime.
|
|
72
|
+
- On **No**: state that Init was declined, so the skill HALTS and the **session
|
|
73
|
+
ends**. Do NOT scaffold, install, or partially write anything. End the turn.
|
|
74
|
+
|
|
75
|
+
### Complete a partial runtime? — drift
|
|
76
|
+
Some foundation files present, some missing (never Embed onto a partial runtime).
|
|
77
|
+
- Question: "The CMS runtime is partially scaffolded (present: {present}; missing:
|
|
78
|
+
{missing}). Complete the {N} missing foundation file(s) from the templates?"
|
|
79
|
+
- Header: `Drift`
|
|
80
|
+
- Options:
|
|
81
|
+
- **Yes, write the missing files (Recommended)** — write ONLY the {N} missing
|
|
82
|
+
files verbatim from `assets/…`; never touch the present ones. Then re-check.
|
|
83
|
+
- **No — stop** — leave the tree as-is and HALT; the user restores or removes the
|
|
84
|
+
partial set.
|
|
85
|
+
|
|
86
|
+
### Content type — unnamed item
|
|
87
|
+
The item's `fqn` is unknown (URL pasted, `contentKey` given, or search returned an empty `fqn`). Ask ONCE.
|
|
88
|
+
- Question: "What content type is this item?"
|
|
89
|
+
- Header: `Type`
|
|
90
|
+
- Options: **News** (`sfdc_cms__news`) · **Blog** · **Announcement** · plus let the
|
|
91
|
+
user type another type name (open answer) when it isn't one of these.
|
|
92
|
+
|
|
93
|
+
### Media alt text — foreign `url` image/document, none from the hand-off
|
|
94
|
+
Only for a **foreign `url` media ref** of type `image` or `document` when the search
|
|
95
|
+
hand-off supplied no `altText`/`title` (the direct-URL path has no fetched body to read
|
|
96
|
+
them from — SKILL.md step 3). `audio`/`video` never ask (controls + optional aria-label).
|
|
97
|
+
Ask ONCE, open answer.
|
|
98
|
+
- Question: "What alt text describes '{title}'? (Leave blank if it's decorative.)" — for a
|
|
99
|
+
document instead: "What label should the download link for '{title}' show?"
|
|
100
|
+
- Header: `Alt text`
|
|
101
|
+
- Open answer — the user types the alt text / link label; the skill sets it as `altText`
|
|
102
|
+
(image) or `title` (document) on the `CmsExternalRef`. Blank is a valid answer for an
|
|
103
|
+
image (decorative → `alt=''`, WCAG 1.1.1). In **bypass** mode do NOT HALT: register with
|
|
104
|
+
the a11y fallback (`altText: ''` / no `title`) and continue.
|
|
105
|
+
|
|
106
|
+
### Channel ID — unresolved for a contentKey
|
|
107
|
+
A `CmsRef` (contentKey) needs a channel and none was found in `public/content-metadata.json`
|
|
108
|
+
or provided with the prompt (channel resolution, SKILL.md step 3). Ask ONCE, open answer.
|
|
109
|
+
- Question: "Which channel serves '{title}'? Paste the channelId (e.g. `0apSG0000000…`)."
|
|
110
|
+
- Header: `Channel`
|
|
111
|
+
- Open answer — the user pastes the channelId; the skill writes it to
|
|
112
|
+
`public/content-metadata.json`. No safe default: no answer → HALT (never invent or
|
|
113
|
+
placeholder a channel). In **bypass** mode there is NO auto-fill — an unresolved
|
|
114
|
+
channel HALTs like an unnamed in-place placement.
|
|
115
|
+
|
|
116
|
+
### Channel conflict — prompt channel differs from the catalog
|
|
117
|
+
A `channelId` came with the prompt/identity, but `public/content-metadata.json` already
|
|
118
|
+
holds a DIFFERENT non-empty `channelId` (channel resolution, SKILL.md step 3). One
|
|
119
|
+
channel serves EVERY contentKey ref in the app, so overwriting repoints them all — never
|
|
120
|
+
silently. Ask ONCE; both concrete channels are known, so this is option-select, not open.
|
|
121
|
+
- Question: "'{title}' names channel `{promptChannel}`, but the catalog already uses
|
|
122
|
+
`{catalogChannel}`. Which channel is correct?"
|
|
123
|
+
- Header: `Channel`
|
|
124
|
+
- Options:
|
|
125
|
+
- **Keep the catalog channel `{catalogChannel}` (Recommended)** — leave
|
|
126
|
+
`public/content-metadata.json` UNCHANGED (no write); register the ref and proceed. The
|
|
127
|
+
catalog is primary and cross-org-safe, and any other refs already resolve through it.
|
|
128
|
+
- **Use the prompt channel `{promptChannel}`** — overwrite ONLY the `channelId` in
|
|
129
|
+
`public/content-metadata.json` (preserve the existing `contents` array); then register
|
|
130
|
+
the ref and proceed. This repoints EVERY contentKey ref in the app to `{promptChannel}`.
|
|
131
|
+
- Overwrite the catalog channel only on the explicit **Use the prompt channel** answer.
|
|
132
|
+
Same channel, or an empty/absent catalog, is NO conflict — use/write per *Channel ID*.
|
|
133
|
+
|
|
134
|
+
### Render target — neither placement nor page stated
|
|
135
|
+
Only when the prompt names no route AND no in-place placement (step 4).
|
|
136
|
+
- Question: "How should I render '{title}'?"
|
|
137
|
+
- Header: `Render as`
|
|
138
|
+
- Options:
|
|
139
|
+
- **Dedicated detail page/route** — generate a page under `src/pages/<type>/` and
|
|
140
|
+
add its route.
|
|
141
|
+
- **In place in an existing view** — embed the renderer at a location you name.
|
|
142
|
+
- On **In place**, follow up for the location (open answer): "Where should '{title}'
|
|
143
|
+
render? Name a component, page, or region." An unanswered location HALTs — never
|
|
144
|
+
guess a placement.
|
|
145
|
+
|
|
146
|
+
### Group render target — N items, same type
|
|
147
|
+
Only when neither a list/grid nor per-page layout is stated (step 4, *Groups*).
|
|
148
|
+
- Question: "Render the {N} items as separate detail pages, or together on one page?"
|
|
149
|
+
- Header: `Group as`
|
|
150
|
+
- Options:
|
|
151
|
+
- **A detail page for each item (Recommended)** — loop the Detail Page branch, one
|
|
152
|
+
page + slug per item.
|
|
153
|
+
- **One list/grid on a single page** — generate the list wrapper, place one node.
|
|
154
|
+
|
|
155
|
+
### Candidate uiBundle — more than one qualifies
|
|
156
|
+
Multiple bundles under `force-app/main/default/uiBundles/` look scaffolded/targetable and the prompt didn't name one.
|
|
157
|
+
- Question: "Which uiBundle should I render into?"
|
|
158
|
+
- Header: `Bundle`
|
|
159
|
+
- Options: one per candidate bundle name (label = bundle dir); if >4, list the most
|
|
160
|
+
likely and allow an open answer.
|
|
161
|
+
|
|
162
|
+
## Terminal stops (session ends — no scaffolding)
|
|
163
|
+
|
|
164
|
+
| Stop | When | What to say and do |
|
|
165
|
+
|------|------|--------------------|
|
|
166
|
+
| **Init declined** | User picks "No" at the *Run Init?* question | State Init was declined → the skill HALTS and the session ends. Write nothing; end the turn. |
|
|
167
|
+
| **Zero search content** | experience-search-coordinate returns ZERO content for the phrase (search succeeded, found nothing) | The item isn't authored in this uiBundle space yet. STOP the session and tell the user to generate the content there first — author + publish it (e.g. via `experience-cms-content-generate` / `experience-cms-content-type-generate`) — then re-run this skill. Do NOT guess an identity or scaffold. |
|
|
168
|
+
|
|
169
|
+
**Zero content vs. search failure** — distinct. ZERO content (nothing exists yet)
|
|
170
|
+
ends the session with an *author-the-content-first* instruction. A **cancelled,
|
|
171
|
+
errored, or unavailable** search is a recoverable HALT: ask the user to paste the
|
|
172
|
+
item's public delivery URL (`unauthenticatedUrl`) OR its `contentKey` (plus
|
|
173
|
+
`channelId` / content type), and resume once they do — see `SKILL.md` step 1.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Toolkit package API + delivery response contract
|
|
2
|
+
|
|
3
|
+
Ground truth for reading CMS content. Transport, envelope-unwrap, catalog remap, channel resolution, image-URL resolution, and entity-decoding all live in the npm package **`@salesforce/ui-bundle-template-feature-cms-toolkit`**.
|
|
4
|
+
Generated code CALLS this API — never re-implement HTTP, unwrap, or URL building, and never reconstruct an endpoint or response from memory.
|
|
5
|
+
|
|
6
|
+
## Public API surface (the single import target)
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import {
|
|
10
|
+
getCmsContentByUrl, // (url, options?) → Promise<TBody> public CDN, unauthenticated; THROWS
|
|
11
|
+
getCmsContentByKey, // (contentKey, options?) → Promise<TBody> one item, authenticated Connect; THROWS
|
|
12
|
+
getCmsContentByKeys, // (keys[], options?) → Promise<CmsBulkResult<TBody>[]> many, authenticated, ONE round-trip; BEST-EFFORT (per-key error, never throws except on abort). See references/bulk-loading.md
|
|
13
|
+
resolveCmsImageUrl, // (field, options?) → string | undefined image field → loadable src (SYNC); pass no options — resolves relative urls itself
|
|
14
|
+
resolveMediaUrl, // (url, options?) → string | undefined media url string → loadable src (SYNC); pass no options
|
|
15
|
+
getCmsInstanceOrigin, // () → string | undefined instance origin (SYNC); no longer needed by generated code
|
|
16
|
+
decodeRichHtmlEntities, // (encoded) → string decode RichText before injection (SYNC)
|
|
17
|
+
resolveEffectiveContentKey, // (key, catalogUrl?) → Promise<string> source→target key remap
|
|
18
|
+
getCmsDefaultChannelId, // (catalogUrl?) → Promise<string|undefined> catalog default channel (fail-open)
|
|
19
|
+
// errors (stable for instanceof):
|
|
20
|
+
CmsDeliveryError, CmsDeliveryNotFoundError, ConnectApiError, CmsNotFoundError,
|
|
21
|
+
} from '@salesforce/ui-bundle-template-feature-cms-toolkit';
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
CmsContentBody, CmsReadOptions, CmsBulkResult, CmsContentCatalog, CmsImageField, CmsImageOptions,
|
|
25
|
+
} from '@salesforce/ui-bundle-template-feature-cms-toolkit';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Every read takes a required positional subject (url / key / keys) plus ONE optional `CmsReadOptions = { signal?: AbortSignal; channelId?: string }` (`channelId` is for authenticated reads only and overrides the catalog default).
|
|
29
|
+
New params arrive as new OPTIONAL fields — no call site ever breaks.
|
|
30
|
+
|
|
31
|
+
Both `byUrl` and `byKey` return the already-UNWRAPPED `contentBody` typed as `TBody` — there is no envelope handling in skill code.
|
|
32
|
+
`byKey` also remaps source→target and resolves the channel first.
|
|
33
|
+
|
|
34
|
+
## Delivery response contract (what `TBody` describes)
|
|
35
|
+
|
|
36
|
+
Both transports return the SAME managed content item DIRECTLY (NOT wrapped in
|
|
37
|
+
`{ items }`, `data`, or `result`). Real `news` payload, pre-unwrap:
|
|
38
|
+
|
|
39
|
+
```jsonc
|
|
40
|
+
{
|
|
41
|
+
"contentBody": { // ← per-type renderable fields (this is <Type>Body)
|
|
42
|
+
"body": "A powerful earthquake…", // RichText/Text → primitive (Rule 5)
|
|
43
|
+
"excerpt": "A devastating earthquake…",
|
|
44
|
+
"bannerImage": { "source": { "type": "url", "ref": "https://cdn.…/x.svg" } } // Image → object
|
|
45
|
+
},
|
|
46
|
+
"contentKey": "MCVIWPYY…",
|
|
47
|
+
"title": "News Content on Venezuela Earthquake", // ← ENVELOPE-level, NOT in contentBody
|
|
48
|
+
"contentType": { "fullyQualifiedName": "sfdc_cms__news" }
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **`contentBody` IS the per-type body**, and its keys VARY BY TYPE — derive
|
|
53
|
+
`<Type>Body` from the schema / a real payload (`references/schema-sync.md`), never
|
|
54
|
+
hard-code. `getCmsContentByUrl<TBody>` / `getCmsContentByKey<TBody>` return it.
|
|
55
|
+
- **`title` is ENVELOPE-level** (root, not in `contentBody`). The toolkit LIFTS it
|
|
56
|
+
into the body when the body lacks one, so `body.title` works — keep room in
|
|
57
|
+
`<Type>Body`'s index signature; do NOT emit `title` as a schema field.
|
|
58
|
+
- A root `channelSummary` block may appear — ignore it.
|
|
59
|
+
|
|
60
|
+
## Image field — `CmsImageField` + `resolveCmsImageUrl`
|
|
61
|
+
|
|
62
|
+
Images are OBJECTS, not primitives — never use one as a `src` directly; pass it to `resolveCmsImageUrl(field)`.
|
|
63
|
+
Pass NO options object — the resolver resolves a relative Connect-API url on its own. It is SYNCHRONOUS — resolve inline, there is no async image component. Three cases the resolver handles:
|
|
64
|
+
|
|
65
|
+
- **CMS image** (`source.type === "imageReference"`) — has a ROOT-RELATIVE top-level
|
|
66
|
+
`url`; the resolver returns it resolved to a loadable src (a relative Connect-API
|
|
67
|
+
url now works as-is — no instance-origin prefix needed).
|
|
68
|
+
- **Foreign image** (`source.type === "url"`) — no top-level `url`; the absolute URL
|
|
69
|
+
is the STRING `source.ref`, returned as-is.
|
|
70
|
+
- **Fallback** — `imageReference` with no top-level `url`. Rare; the common payload
|
|
71
|
+
inlines the media `url`.
|
|
72
|
+
|
|
73
|
+
Standalone media (audio/video/document) on a **`contentKey` ref** uses the sibling
|
|
74
|
+
`resolveMediaUrl(url)`, given the body `sfdc_cms:media.url` string directly (same
|
|
75
|
+
no-options, relative-safe contract). A **foreign `url` media ref** skips this entirely —
|
|
76
|
+
its `unauthenticatedUrl` is the asset itself and is set directly as the element
|
|
77
|
+
`src`/`href`, with no fetch and no resolver (Rule 1/Rule 2). The resolvers apply only
|
|
78
|
+
when a body was fetched.
|
|
79
|
+
|
|
80
|
+
The field also carries an optional `altText` string — bind it to the `<img alt>`
|
|
81
|
+
(`resolveImageAlt`), falling back to `''` (decorative, WCAG 1.1.1) when absent.
|
|
82
|
+
`resolveCmsImageUrl` returns only the `src`, so read `altText` off the field object.
|
|
83
|
+
|
|
84
|
+
## RichText & scalars
|
|
85
|
+
|
|
86
|
+
RichText arrives entity-encoded (`"<div>…"`) — decode with
|
|
87
|
+
`decodeRichHtmlEntities` before injecting, through ONE path per framework (Rule 4,
|
|
88
|
+
`references/heuristic-render-rules.md`). Scalar `contentBody` fields are primitives,
|
|
89
|
+
never `{ value }` (Rule 5, `references/schema-sync.md`). Symptoms if either is
|
|
90
|
+
violated: `references/failure-modes.md`.
|
|
91
|
+
|
|
92
|
+
## `content-metadata.json` — the catalog the skill writes (Rule 1)
|
|
93
|
+
|
|
94
|
+
The `byKey` path loads a public `content-metadata.json` to supply a default `channelId` and remap each baked-in `sourceContentKey` → the org's `targetContentKey`.
|
|
95
|
+
Loaded once, memoized, and **FAIL-OPEN**: missing / unreachable / malformed → treated as `{}` (no channel, keys used verbatim). Served from the uiBundle base at `${BASE_URL}content-metadata.json` (override with `VITE_CONTENT_CATALOG_URL`); with Vite the web root is `public/`, so the file lives at `<appRoot>/public/content-metadata.json`.
|
|
96
|
+
|
|
97
|
+
**The skill writes it — catalog-primary channel resolution.** When a `CmsRef` needs a channel and the catalog is absent, the skill creates `public/content-metadata.json` with the resolved `channelId` and an **empty `contents: []`**:
|
|
98
|
+
|
|
99
|
+
```jsonc
|
|
100
|
+
{ "channelId": "0apSG0000000ExampleChannel", "contents": [] }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Only `channelId` (string) and `contents` (`{ sourceContentKey, targetContentKey }` pairs; entries missing either key are skipped) are read — the empty `contents` is safe because per-entry remap fails open to the baked-in `contentKey`. Write it **on demand at the first `CmsRef` embed** that needs a channel, not at Init; idempotent (present channel → never overwrite; absent → create; present-but-empty → fill).
|
|
104
|
+
|
|
105
|
+
Write **only a known channel** (catalog / prompt / user) — a placeholder with a fake `channelId` breaks every `byKey` read, because fail-open triggers on a MISSING file, not a present-but-wrong one; a present-but-wrong catalog is worse than none. It is a **regenerable data seam, not source of truth**: the deploy pipeline always regenerates it against the target org (filling real `contents`), which is why the catalog is cross-org-safe where a baked-in `CMS_CHANNEL_ID_FALLBACK` source constant is not. Precedence when reading: explicit option > `CMS_CHANNEL_ID_FALLBACK` (see `externalRefs.ts`, a cautioned opt-in override) > catalog `channelId` > the toolkit throws.
|
|
106
|
+
Failure modes: `references/failure-modes.md`.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Schema Sync
|
|
2
|
+
|
|
3
|
+
Every Embed fetches the content type's shape and emits typed TS. The per-type body
|
|
4
|
+
module is FRAMEWORK-AGNOSTIC (identical for React and Angular); the renderer is
|
|
5
|
+
framework-specific. Files under `src/cms/react/` or `src/cms/angular/`:
|
|
6
|
+
|
|
7
|
+
- `<framework>/types/<type>.ts` — **above-marker** region (user-editable: layouts,
|
|
8
|
+
slots, className presets) + **below-marker** region (generated: `<Type>Body`
|
|
9
|
+
interface and `<type>FieldTypes` map). Imports `CmsFieldType`/`CmsImageField` from
|
|
10
|
+
`../../shared/cmsCore.types`.
|
|
11
|
+
- The renderer — no user region, regenerated in full. React
|
|
12
|
+
`react/types/<Type>Renderer.tsx`, Angular `angular/types/<Type>Renderer.component.ts`.
|
|
13
|
+
|
|
14
|
+
## OOTB `sfdc_cms__news` — predefined, do NOT fetch
|
|
15
|
+
|
|
16
|
+
The metadata fetch cannot resolve OOTB Salesforce content types, so `sfdc_cms__news`
|
|
17
|
+
has a FIXED schema — skip retrieval and emit exactly these fields:
|
|
18
|
+
|
|
19
|
+
| field | lightningType | `<Type>Body` TS | `CmsFieldType` |
|
|
20
|
+
|-------|---------------|-----------------|----------------|
|
|
21
|
+
| `bannerImage` | Image | `CmsImageField` | `'image'` |
|
|
22
|
+
| `body` | RichText | `string` | `'richText'` |
|
|
23
|
+
| `excerpt` | Text | `string` | `'text'` |
|
|
24
|
+
|
|
25
|
+
No `sf project retrieve` / MCP / SOQL for news. Other OOTB types are not predefined —
|
|
26
|
+
treat them as custom and fetch below.
|
|
27
|
+
|
|
28
|
+
## Schema retrieval (custom types)
|
|
29
|
+
|
|
30
|
+
Retrieve the ContentTypeBundle from the org (bare DeveloperName — no `c__` /
|
|
31
|
+
`sfdc_cms__` prefix; relative `--output-dir` inside the project):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
sf project retrieve start --metadata ContentTypeBundle:<DeveloperName> \
|
|
35
|
+
--target-org <targetOrg> --output-dir retrieved-content-types --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Resolve `<targetOrg>` via `sf config get target-org --json`; unset → HALT, ask the
|
|
39
|
+
user to `sf config set target-org=<alias>`. Read (with the `Read` tool, not `cat`)
|
|
40
|
+
`retrieved-content-types/contentTypes/<DeveloperName>/schema.json`; valid iff
|
|
41
|
+
`lightning:type === "lightning__objectType"` and `properties` is a non-empty object.
|
|
42
|
+
|
|
43
|
+
Map each `properties` entry to `{ name, lightningType (PascalCase: Text, RichText,
|
|
44
|
+
Image, …), required, localizable }`, then to the lowerCamel `CmsFieldType` literal
|
|
45
|
+
per the table below. Do NOT use Tooling/REST `/sobjects/` or Connect CMS —
|
|
46
|
+
ContentTypeBundle is Metadata-API only. Type not found → verify DeveloperName
|
|
47
|
+
(case-sensitive, no prefix); auth/session error → `sf org login web`. Retrieval
|
|
48
|
+
fails → emit the below-marker block with a `TODO` listing fields to fill manually.
|
|
49
|
+
|
|
50
|
+
## Markers
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// <experience-cms-content-render:above-begin>
|
|
54
|
+
// … user-editable …
|
|
55
|
+
// <experience-cms-content-render:above-end>
|
|
56
|
+
// <experience-cms-content-render:below-begin>
|
|
57
|
+
// … generated …
|
|
58
|
+
// <experience-cms-content-render:below-end>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Line-comments only; never inside strings/JSX. Skill greps for exact begin/end
|
|
62
|
+
pairs and requires both.
|
|
63
|
+
|
|
64
|
+
## Regeneration algorithm
|
|
65
|
+
|
|
66
|
+
1. Read `<framework>/types/<type>.ts`; locate the four markers.
|
|
67
|
+
2. Any marker missing / duplicated / out of order → **HALT** with file+line and the
|
|
68
|
+
expected marker. NEVER auto-heal — marker damage means a hand-edit the skill
|
|
69
|
+
can't safely merge.
|
|
70
|
+
3. Extract the above region byte-for-byte.
|
|
71
|
+
4. Regenerate the below region from the schema: `<Type>Body` (one prop per field) +
|
|
72
|
+
`<type>FieldTypes` (field → lowerCamel `CmsFieldType` literal).
|
|
73
|
+
5. Reassemble `[preamble][above as-read][below as-generated]`; write only if
|
|
74
|
+
changed, else report "no drift".
|
|
75
|
+
|
|
76
|
+
## Field shapes — primitives, not wrappers (Rule 5)
|
|
77
|
+
|
|
78
|
+
Delivery returns SCALAR fields as PLAIN primitives, not `{ value }` wrappers. Map
|
|
79
|
+
`lightningType` → TS type accordingly:
|
|
80
|
+
|
|
81
|
+
| lightningType | `<Type>Body` TS type | `CmsFieldType` literal | Delivery shape |
|
|
82
|
+
|-------------------|----------------------|------------------------|------------------------------------|
|
|
83
|
+
| Text | `string` | `'text'` | `"title": "Berries"` |
|
|
84
|
+
| RichText | `string` | `'richText'` | `"body": "<div>…"` (encoded) |
|
|
85
|
+
| Url | `string` | `'url'` | `"link": "https://…"` |
|
|
86
|
+
| Date | `string` | `'date'` | ISO string |
|
|
87
|
+
| DateTime | `string` | `'dateTime'` | ISO string |
|
|
88
|
+
| Number | `number` | `'number'` | `12` |
|
|
89
|
+
| Boolean | `boolean` | `'boolean'` | `true` |
|
|
90
|
+
| Image | `CmsImageField` | `'image'` | `{ url, altText, source }` — OBJECT |
|
|
91
|
+
| Reference | see RenderField | `'reference'` | `{ ref: { contentKey } }` |
|
|
92
|
+
|
|
93
|
+
`<type>FieldTypes` values are the **lowerCamel literals** in column 3, NOT the
|
|
94
|
+
PascalCase `lightningType` token — copying `'Text'`/`'RichText'` verbatim is a type
|
|
95
|
+
error against the `CmsFieldType` union in `cmsCore.types.ts`.
|
|
96
|
+
|
|
97
|
+
Only Image (and refs) are objects — NEVER emit `{ value: string }` for a scalar
|
|
98
|
+
(that bug made RichText render as nothing: `body.body.value` was `undefined`). The
|
|
99
|
+
renderer reads `body.<field>` directly, no `.value` unwrap. When the schema alone is
|
|
100
|
+
ambiguous, **confirm each field's shape against a real payload** (`unauthenticatedUrl`).
|
|
101
|
+
|
|
102
|
+
## Drift → below-region effect
|
|
103
|
+
|
|
104
|
+
New field → added; removed field → dropped (referencing code fails typecheck,
|
|
105
|
+
intentional); type changed → prop+entry change; rename → remove+add (user
|
|
106
|
+
reconciles); required flip → optional marker flips. Above region unaffected.
|
|
107
|
+
|
|
108
|
+
## No barrel
|
|
109
|
+
|
|
110
|
+
No per-type `index.ts`. Consumers import each renderer by its direct path — React
|
|
111
|
+
`import { NewsRenderer } from '../types/NewsRenderer'`, Angular
|
|
112
|
+
`import { NewsRendererComponent } from '../types/NewsRenderer.component'` — so
|
|
113
|
+
adding a type writes only `<type>.ts` + the renderer, and removing one is a plain
|
|
114
|
+
file delete (referencing imports then fail typecheck, intentional).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Styling Scopes
|
|
2
|
+
|
|
3
|
+
The skill NEVER scaffolds a CSS file, a design system, or CSS variables. Two things
|
|
4
|
+
carry presentation, and only these two:
|
|
5
|
+
|
|
6
|
+
1. **Stable class hooks** the render always emits (below) — selector anchors for the
|
|
7
|
+
app's own stylesheet.
|
|
8
|
+
2. **Presentation applied ON TOP of the render**, per the user's prompt, at the
|
|
9
|
+
render target the skill writes (the detail page's `<main>`, or the wrapper it
|
|
10
|
+
inserts for an in-place embed). Applied inline (a `style={{…}}` object or an
|
|
11
|
+
added `className` the app already defines) — never by emitting a `.css` file.
|
|
12
|
+
|
|
13
|
+
## Default reading layout
|
|
14
|
+
|
|
15
|
+
When the user asks to render / embed / create a detail page and gives NO explicit
|
|
16
|
+
styling direction, apply this baseline on the render target:
|
|
17
|
+
|
|
18
|
+
- **Centered single column**, `max-width` ~`700px`, `margin: 0 auto`.
|
|
19
|
+
- **Large, readable typography** (body ~`1.125rem`, `line-height` ~`1.6–1.7`).
|
|
20
|
+
- **Ample whitespace** (generous padding around the column, spacing between fields).
|
|
21
|
+
|
|
22
|
+
This is a starting look, not a mandate — the prompt overrides it. "Make it full-bleed",
|
|
23
|
+
"use a 2-column card", "tighter spacing", "match our brand font" all replace the
|
|
24
|
+
relevant part. If the user names their own class or stylesheet, prefer wiring that
|
|
25
|
+
over inline values.
|
|
26
|
+
|
|
27
|
+
## Detail page vs. in-place embed — style them differently
|
|
28
|
+
|
|
29
|
+
- **Detail page** owns its `<main>` route → style generously (the full reading
|
|
30
|
+
layout above: centered column, hero image, article spacing). Low clash risk.
|
|
31
|
+
- **In-place embed** is dropped into the app's own component → stay conservative.
|
|
32
|
+
Apply typography/whitespace that inherits context; do NOT impose fixed widths,
|
|
33
|
+
centering, or layout that fights the host. When unsure for an embed, lean on the
|
|
34
|
+
class hook and let the app's stylesheet own layout.
|
|
35
|
+
|
|
36
|
+
## Classes emitted (always, regardless of styling)
|
|
37
|
+
|
|
38
|
+
| Scope | Class | Source |
|
|
39
|
+
|-------|-------|--------|
|
|
40
|
+
| Per-type card | `cms-<type>-card` | `{{type}}Layouts.card.className` |
|
|
41
|
+
| Per-type detail | `cms-<type>-detail` | `{{type}}Layouts.detail.className` |
|
|
42
|
+
| Detail page wrapper | `cms-detail-page cms-<type>-detail-page` | React `assets/react/DetailPage.tsx` / Angular `assets/angular/DetailPage.component.ts` |
|
|
43
|
+
|
|
44
|
+
Same class hooks in both frameworks: React emits them on the renderer's
|
|
45
|
+
`<article>` / detail-page `<main>`; Angular's `<cms-content>` article and the
|
|
46
|
+
`{{PageName}}Component` `<main>` emit the identical strings. Inner tags (`<h1>`,
|
|
47
|
+
`<img>`, …) carry no class — reach them via descendant selectors
|
|
48
|
+
(`.cms-news-card > img { … }`) or style the container.
|
|
49
|
+
|
|
50
|
+
## Precedence & override
|
|
51
|
+
|
|
52
|
+
`className` prop on the embed beats the layout preset's className (React
|
|
53
|
+
`className="…"`, Angular `[className]="…"`). Override presets in the above-marker
|
|
54
|
+
region of `src/cms/<framework>/types/<type>.ts` (regeneration preserves it):
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
export const newsLayouts = {
|
|
58
|
+
card: { fields: ['title', 'bannerImage', 'excerpt'] as const, className: 'my-news-tile' },
|
|
59
|
+
detail: { fields: undefined, className: 'my-news-hero' },
|
|
60
|
+
} as const;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
React custom slot components (`components={{ Image: MyImg }}`) own their own
|
|
64
|
+
className; the wrapper `<article>` keeps the type-scoped class so ancestor
|
|
65
|
+
selectors work. Angular has no slot props — style via the class hooks.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Verify — bounded typecheck + fix policy
|
|
2
|
+
|
|
3
|
+
Detail for Embed pipeline step 5 (SKILL.md → *Embed pipeline* → *5. Verify*).
|
|
4
|
+
SKILL.md holds the one-pass rule; this file holds the full fix policy and the report
|
|
5
|
+
checklist.
|
|
6
|
+
|
|
7
|
+
## Typecheck ONLY the files this skill wrote or edited — bounded, never a loop
|
|
8
|
+
|
|
9
|
+
Run `npx tsc --noEmit` ONCE (React/Vite) or `npx tsc --noEmit -p tsconfig.json` ONCE
|
|
10
|
+
(Angular). Then read ONLY the diagnostics whose file path the skill touched this run:
|
|
11
|
+
foundation files, `<framework>/types/<type>.ts` + renderer, `externalRefs.ts`, the
|
|
12
|
+
render target, detail pages. Every diagnostic in a file the skill did NOT touch is
|
|
13
|
+
pre-existing; ignore it, do not fix it, do not open it.
|
|
14
|
+
|
|
15
|
+
If no TypeScript is configured (no `tsc`/`tsconfig`), skip and note it.
|
|
16
|
+
|
|
17
|
+
## Fix policy — at most ONE fix pass, then stop
|
|
18
|
+
|
|
19
|
+
- Diagnostic in a file the skill **generated** this run (`<type>.ts` below-marker
|
|
20
|
+
`<Type>Body`, a ref entry, an import specifier, an unused symbol, a `.value`
|
|
21
|
+
unwrap) → fix it, then re-run tsc **once** to confirm. One pass, one re-run, no
|
|
22
|
+
more.
|
|
23
|
+
- Diagnostic in a **verbatim foundation file** (`useCmsItem.ts`,
|
|
24
|
+
`heuristicRenderer.tsx`, `cms-item.service.ts`, `cms-content.component.ts`,
|
|
25
|
+
`cmsCore.types.ts`, …) → **do NOT edit or rewrite it.** These are copied
|
|
26
|
+
byte-for-byte and correct by construction; a diagnostic here is almost always the
|
|
27
|
+
toolkit not resolvable yet (install / tsconfig-paths / JSX config), NOT a code
|
|
28
|
+
defect. Report it as an environment/install note ("toolkit unresolved — confirm
|
|
29
|
+
`npm i` ran and tsconfig resolves `@salesforce/…`"); if certain it is a genuine
|
|
30
|
+
template bug, REPORT it, never patch in place.
|
|
31
|
+
- Do NOT run `lint`, `build`, `ng build`, or the dev server; do NOT re-derive the
|
|
32
|
+
toolkit contract from `references/*` to "fix" a template. If errors remain after
|
|
33
|
+
the single fix pass, report them verbatim and stop.
|
|
34
|
+
|
|
35
|
+
## Report
|
|
36
|
+
|
|
37
|
+
Report: files written/modified (paths + inserted line ranges); the `npm install` if
|
|
38
|
+
run; the typecheck result (clean / fixed N in generated files / skipped) plus any
|
|
39
|
+
environment or pre-existing notes; ref name + identity; renderer type; chosen render
|
|
40
|
+
target; and any HALTs. The skill does NOT run lint, a build, or the dev server.
|
|
41
|
+
|
|
42
|
+
**Media on an authenticated channel — temporary-limitation note.** When this run
|
|
43
|
+
scaffolds media (`sfdc_cms__{image,audio,video,document}`) whose ref is a `CmsRef`
|
|
44
|
+
(a `contentKey`, read via authenticated Connect / a uiBundle channel), add a note to
|
|
45
|
+
the report: *rendering that media inside an internal app is not supported yet — this
|
|
46
|
+
is a temporary platform limitation planned for a future release; media served from a
|
|
47
|
+
public delivery URL (a `CmsExternalRef`) renders today.* Scaffold normally and do not
|
|
48
|
+
HALT — the note is informational only. Media registered as a `CmsExternalRef` (public
|
|
49
|
+
URL) needs no note.
|