@salesforce/afv-skills 1.50.0 → 1.52.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/experience-ui-bundle-deploy/SKILL.md +43 -2
- package/skills/experience-ui-bundle-deploy/references/config-scaffold.md +16 -3
- package/skills/experience-ui-bundle-deploy/references/logout-url.md +97 -0
- package/skills/experience-ui-bundle-deploy/scripts/set-logout-url.mjs +304 -0
- package/skills/experience-ui-bundle-localize/SKILL.md +107 -90
- package/skills/experience-ui-bundle-localize/references/angular/check-i18n-wired.sh +159 -0
- package/skills/experience-ui-bundle-localize/references/angular/i18n-setup.md +250 -0
- package/skills/experience-ui-bundle-localize/references/angular/interpolation.md +156 -0
- package/skills/experience-ui-bundle-localize/references/angular/localize.md +111 -0
- package/skills/experience-ui-bundle-localize/references/{gotchas.md → common/gotchas.md} +27 -43
- package/skills/experience-ui-bundle-localize/references/{label-xml.md → common/label-xml.md} +27 -17
- package/skills/experience-ui-bundle-localize/references/common/platform-sdk-i18n.md +169 -0
- package/skills/experience-ui-bundle-localize/references/{verifying.md → common/verifying.md} +26 -16
- package/skills/experience-ui-bundle-localize/{scripts → references/react}/check-i18n-wired.sh +8 -3
- package/skills/experience-ui-bundle-localize/references/{i18n-setup.md → react/i18n-setup.md} +48 -10
- package/skills/experience-ui-bundle-localize/references/{interpolation.md → react/interpolation.md} +4 -4
- package/skills/experience-ui-bundle-localize/references/react/localize.md +76 -0
- package/skills/experience-ui-bundle-localize/scripts/check-manifest-registered.sh +92 -24
- package/skills/experience-ui-bundle-localize/scripts/detect-framework.sh +73 -0
- package/skills/experience-ui-bundle-site-generate/SKILL.md +1 -1
- package/skills/field-service-data-capture-form-deployer-configure/SKILL.md +1 -1
- package/skills/field-service-data-capture-form-deployer-configure/references/flow-metadata-json.md +1 -1
- package/skills/field-service-data-capture-form-editor-configure/SKILL.md +1 -1
- package/skills/field-service-data-capture-reference-configure/SKILL.md +28 -15
- package/skills/field-service-foundation-setup-designer-get/SKILL.md +1 -1
- package/skills/field-service-mobile-branding-configure/SKILL.md +1 -1
- package/skills/field-service-objective-designer-configure/SKILL.md +381 -64
- package/skills/field-service-prework-brief-deployer-configure/SKILL.md +566 -3
- package/skills/field-service-scheduling-policy-designer-query/SKILL.md +1097 -57
- package/skills/field-service-sobject-create-configure/SKILL.md +222 -7
- package/skills/field-service-voice-to-form-configure/SKILL.md +11 -11
- package/skills/field-service-work-rule-designer-configure/SKILL.md +92 -107
- package/skills/service-digital-engagement-channel-configure/SKILL.md +20 -37
- package/skills/service-digital-engagement-channel-configure/assets/messaging_channel_template.xml +3 -2
- package/skills/service-digital-engagement-channel-configure/examples/asa_agent_channel.xml +4 -4
- package/skills/service-helpagent-coordinate/SKILL.md +37 -29
- package/skills/service-helpagent-coordinate/assets/help-agent-spec.md +33 -28
- package/skills/service-helpagent-coordinate/references/agent-script.md +4 -1
- package/skills/service-helpagent-coordinate/references/channel-voice.md +1 -1
- package/skills/service-helpagent-coordinate/references/channel-web-chat.md +10 -12
package/skills/experience-ui-bundle-localize/references/{i18n-setup.md → react/i18n-setup.md}
RENAMED
|
@@ -4,6 +4,24 @@ You write two files to set up i18n in a React UI Bundle. The Platform SDK provid
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## FIRST: is this a B2E or a B2C bundle?
|
|
8
|
+
|
|
9
|
+
**Decide before you copy the example below.** The example in File 1 is **B2E**. A B2C
|
|
10
|
+
(guest / public site) bundle is **not** a copy of it — you MUST change two things, or the
|
|
11
|
+
site renders the wrong language and text direction for guest users:
|
|
12
|
+
|
|
13
|
+
| | B2E (authenticated) | B2C (guest site) |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Fallback | omit `labelFallback` (SDK default `BASE_VALUE`) | **`labelFallback: "USER_DEFAULT"`** |
|
|
16
|
+
| Init language | omit `lng` (detector resolves it) | **`lng: resolvedLang`** in `i18next.init` |
|
|
17
|
+
| Document dir/lang | `document.documentElement.dir = ctx.dir` | **`i18next.dir(resolvedLang)`**, not `ctx.dir` |
|
|
18
|
+
|
|
19
|
+
If the prompt mentions a public site, guest users, a community/Experience site, or a
|
|
20
|
+
language switcher on a public page, it is **B2C** — jump to the [B2C section](#b2c-changes)
|
|
21
|
+
and apply all three overrides. Do **not** ship the B2E `ctx.dir` + no-fallback wiring to a B2C site.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
7
25
|
## File 1: `src/i18n/index.ts` (the init wiring)
|
|
8
26
|
|
|
9
27
|
This is the only "glue" you write. It connects the SDK's i18n pieces to i18next.
|
|
@@ -25,7 +43,8 @@ export async function initI18n() {
|
|
|
25
43
|
const dataSDK = await createDataSDK();
|
|
26
44
|
const ctx = await fetchI18nContext(dataSDK);
|
|
27
45
|
|
|
28
|
-
// B2E: the session language is the display language.
|
|
46
|
+
// B2E ONLY: the session language is the display language.
|
|
47
|
+
// B2C MUST NOT use ctx.dir here — see the B2C section below.
|
|
29
48
|
document.documentElement.dir = ctx.dir;
|
|
30
49
|
document.documentElement.lang = ctx.lang;
|
|
31
50
|
|
|
@@ -40,7 +59,7 @@ export async function initI18n() {
|
|
|
40
59
|
backends: [LocalStorageBackend, SalesforceBackend],
|
|
41
60
|
backendOptions: [
|
|
42
61
|
{ expirationTime: 86400000 }, // cache labels in localStorage for a day
|
|
43
|
-
{ dataSDK, labelManifest }, // B2E: shipped BASE_VALUE fallback
|
|
62
|
+
{ dataSDK, labelManifest }, // B2E ONLY: shipped BASE_VALUE fallback. B2C MUST add labelFallback: "USER_DEFAULT" — see below.
|
|
44
63
|
],
|
|
45
64
|
},
|
|
46
65
|
interpolation: {
|
|
@@ -59,7 +78,13 @@ export async function initI18n() {
|
|
|
59
78
|
|
|
60
79
|
The example above is the **B2E** configuration. Do not set `labelFallback` for B2E: the shipped `SalesforceBackend` default is `BASE_VALUE`.
|
|
61
80
|
|
|
62
|
-
|
|
81
|
+
<a id="b2c-changes"></a>
|
|
82
|
+
### B2C changes (REQUIRED for guest sites)
|
|
83
|
+
|
|
84
|
+
For a **B2C** bundle you MUST make **all three** changes below. Omitting any one is the most
|
|
85
|
+
common defect: it renders the guest's fallback, display language, or text direction wrong.
|
|
86
|
+
|
|
87
|
+
**Change 1 — fallback.** Change the Salesforce backend options entry:
|
|
63
88
|
|
|
64
89
|
```typescript
|
|
65
90
|
backendOptions: [
|
|
@@ -74,7 +99,7 @@ backendOptions: [
|
|
|
74
99
|
|
|
75
100
|
`USER_DEFAULT` is required for B2C so fallback follows the guest/site language context. Do not copy this override into B2E wiring.
|
|
76
101
|
|
|
77
|
-
|
|
102
|
+
**Change 2 — document direction/language.** Set the document language and direction from the route-selected display language, **not `ctx.dir`**. The GraphQL i18n context direction reflects the guest session profile and can stay `ltr` after the site switches to an RTL language. Replace the B2E `document.documentElement.dir = ctx.dir` / `.lang = ctx.lang` lines with the resolved language from the site's language-switcher integration:
|
|
78
103
|
|
|
79
104
|
```typescript
|
|
80
105
|
const resolvedLang =
|
|
@@ -83,7 +108,19 @@ document.documentElement.dir = i18next.dir(resolvedLang);
|
|
|
83
108
|
document.documentElement.lang = resolvedLang.replace(/_/g, "-");
|
|
84
109
|
```
|
|
85
110
|
|
|
86
|
-
|
|
111
|
+
**Change 3 — init language.** Add `lng: resolvedLang` to the `i18next.init({ ... })` options so i18next initializes in the route-selected language:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
await i18next
|
|
115
|
+
// ...ChainedBackend / detector / initReactI18next as above...
|
|
116
|
+
.init({
|
|
117
|
+
lng: resolvedLang, // B2C: initialize in the route-selected language
|
|
118
|
+
fallbackLng: "en",
|
|
119
|
+
// ...defaultNS, backend, interpolation as above...
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The SDK detector does **not** read `SFDC_ENV.language`. Without an explicit `lng`, labels can stay in the guest-session language after the site route selects another locale. Reuse the same `resolvedLang` computed in Change 2. Do not set `lng` for B2E — the detector resolves the session language there.
|
|
87
124
|
|
|
88
125
|
Before using this configuration, have an org admin confirm that `GraphQLApiOrgPrefForGuestUsers` is already enabled. This workflow must never enable it. Without the preference, unauthenticated GraphQL label requests return HTTP 403; see dependency W-23854208.
|
|
89
126
|
|
|
@@ -122,7 +159,7 @@ The manifest is how i18next knows what to fetch. An **unregistered key fails sil
|
|
|
122
159
|
|
|
123
160
|
## B2C language context
|
|
124
161
|
|
|
125
|
-
A B2C site's configured languages and language-specific URLs are the source of truth. At boot, the site route supplies `SFDC_ENV.language`;
|
|
162
|
+
A B2C site's configured languages and language-specific URLs are the source of truth. At boot, the site route supplies `SFDC_ENV.language`; the SDK detector uses that value to resolve labels. A language switcher must navigate to the target language URL and perform a full page reload. An in-place i18next language change is insufficient because the SDK context and localStorage-backed labels are established at boot.
|
|
126
163
|
|
|
127
164
|
For local preview, use the site entry of the Vite plugin and pass the site's supported language codes, with the default first:
|
|
128
165
|
|
|
@@ -171,7 +208,7 @@ If you see an older example that vendors `salesforce-detector.ts` or `salesforce
|
|
|
171
208
|
|
|
172
209
|
1. Bundle loads, `initI18n()` runs
|
|
173
210
|
2. `createDataSDK()` initializes the SDK
|
|
174
|
-
3. `fetchI18nContext()` queries the org for language/locale/direction
|
|
211
|
+
3. `fetchI18nContext()` queries the org for language/locale/direction (B2C uses the site route's `SFDC_ENV.language`)
|
|
175
212
|
4. `SalesforceBackend` reads the manifest and issues a GraphQL query per namespace:
|
|
176
213
|
```graphql
|
|
177
214
|
query LoadLabels {
|
|
@@ -209,7 +246,8 @@ For most bundles, a single `"c"` namespace is all you need.
|
|
|
209
246
|
|
|
210
247
|
## Related
|
|
211
248
|
|
|
212
|
-
- [
|
|
249
|
+
- [../common/platform-sdk-i18n.md](../common/platform-sdk-i18n.md): the shared runtime engine (Labels query, `fetchI18nContext`, batching, fallback)
|
|
250
|
+
- [../common/label-xml.md](../common/label-xml.md): the Custom Labels metadata XML shape
|
|
213
251
|
- [interpolation.md](interpolation.md): how `{0}/{1}` placeholders work
|
|
214
|
-
- [verifying.md](verifying.md): the serve/verify flow
|
|
215
|
-
- [gotchas.md](gotchas.md): silent-fail traps to avoid
|
|
252
|
+
- [../common/verifying.md](../common/verifying.md): the serve/verify flow
|
|
253
|
+
- [../common/gotchas.md](../common/gotchas.md): silent-fail traps to avoid
|
package/skills/experience-ui-bundle-localize/references/{interpolation.md → react/interpolation.md}
RENAMED
|
@@ -163,7 +163,7 @@ t("Nonexistent_Key");
|
|
|
163
163
|
// → "Nonexistent_Key" (literal key string)
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
-
This is the **unregistered manifest key** trap (see [gotchas.md](gotchas.md)): the label wasn't fetched, so i18next has nothing to interpolate.
|
|
166
|
+
This is the **unregistered manifest key** trap (see [../common/gotchas.md](../common/gotchas.md)): the label wasn't fetched, so i18next has nothing to interpolate.
|
|
167
167
|
|
|
168
168
|
---
|
|
169
169
|
|
|
@@ -306,6 +306,6 @@ With the bridge (`prefix: "{", suffix: "}"`), **one Custom Label serves all thre
|
|
|
306
306
|
## Related
|
|
307
307
|
|
|
308
308
|
- [i18n-setup.md](i18n-setup.md): the init file where the `prefix`/`suffix` are configured
|
|
309
|
-
- [label-xml.md](label-xml.md): how to author labels with placeholders
|
|
310
|
-
- [verifying.md](verifying.md): testing interpolated labels
|
|
311
|
-
- [gotchas.md](gotchas.md): silent-fail traps
|
|
309
|
+
- [../common/label-xml.md](../common/label-xml.md): how to author labels with placeholders
|
|
310
|
+
- [../common/verifying.md](../common/verifying.md): testing interpolated labels
|
|
311
|
+
- [../common/gotchas.md](../common/gotchas.md): silent-fail traps
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# React reference — Localize a React UI Bundle
|
|
2
|
+
|
|
3
|
+
Framework-specific companion to `SKILL.md` for the **React** path. `SKILL.md` owns the
|
|
4
|
+
neutral workflow + guardrail spine (Step 0 routing, preconditions, the five steps, and
|
|
5
|
+
the guardrails); this file owns everything React/i18next-specific: the runtime library,
|
|
6
|
+
the call convention, the wiring shape, and the depth docs.
|
|
7
|
+
|
|
8
|
+
## Library & call convention
|
|
9
|
+
|
|
10
|
+
A React UI Bundle can't use `@salesforce/label/*` the way LWC does — those imports
|
|
11
|
+
resolve at compile time inside the platform's compiler, which your standalone React
|
|
12
|
+
bundle doesn't go through. Instead the app **fetches labels at runtime** through the
|
|
13
|
+
Salesforce GraphQL UI API and hands them to **i18next** (via `react-i18next`) to render.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { useTranslation } from "react-i18next";
|
|
17
|
+
|
|
18
|
+
function WelcomeBanner() {
|
|
19
|
+
const { t } = useTranslation("c"); // "c" = custom label namespace
|
|
20
|
+
return <h1>{t("Welcome_Text")}</h1>; // renders "Welcome" or "Bienvenido" per user's language
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- **Call site:** `t("Key")` (from `useTranslation("c")`).
|
|
25
|
+
- **Files scanned for user-facing strings / call sites:** `.tsx` / `.jsx`.
|
|
26
|
+
- **Import to add** when a component first calls `t()`:
|
|
27
|
+
`import { useTranslation } from "react-i18next";` and `const { t } = useTranslation("c");`.
|
|
28
|
+
|
|
29
|
+
## Step 2 (Extract) — React specifics
|
|
30
|
+
|
|
31
|
+
Replace the JSX literal with a `t()` call and add the import:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
// Before: <h1>Welcome</h1>
|
|
35
|
+
// After: <h1>{t("Welcome_Text")}</h1>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Step 4 (Wire) — React specifics
|
|
39
|
+
|
|
40
|
+
Install the i18n dependencies (tell the user to run, from the UI bundle dir):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install i18next react-i18next i18next-chained-backend i18next-localstorage-backend
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then scaffold `src/i18n/index.ts` (the `initI18n()` that wires
|
|
47
|
+
`@salesforce/platform-sdk/i18n` into i18next) and `src/i18n/label-manifest.ts`, and call
|
|
48
|
+
`initI18n()` once at boot in the entry file (usually `src/index.tsx`) before mounting the
|
|
49
|
+
app. Full init code and the B2E vs B2C `SalesforceBackend` configuration are in
|
|
50
|
+
[i18n-setup.md](./i18n-setup.md).
|
|
51
|
+
|
|
52
|
+
## Scripts
|
|
53
|
+
|
|
54
|
+
Run them from the UI bundle dir (they scan `src/` relative to the current directory). The
|
|
55
|
+
first three are framework-neutral and live in the skill's shared `scripts/` folder;
|
|
56
|
+
`check-i18n-wired.sh` is React-specific (i18next `backendOptions` detection) and ships here
|
|
57
|
+
in the React reference folder.
|
|
58
|
+
|
|
59
|
+
| Script | Purpose |
|
|
60
|
+
|--------|---------|
|
|
61
|
+
| [`check-org-api-version.sh`](../../scripts/check-org-api-version.sh) | Precondition 4 — org supports API v68.0+ |
|
|
62
|
+
| [`detect-bundle-type.sh`](../../scripts/detect-bundle-type.sh) | Precondition 5 — classify B2E / B2C / internal |
|
|
63
|
+
| [`check-manifest-registered.sh --framework react`](../../scripts/check-manifest-registered.sh) | Step 3 — every `t("Key")` is registered in the manifest (`--framework react` selects the `.tsx/.jsx` + `t()` grammar) |
|
|
64
|
+
| [`check-i18n-wired.sh`](./check-i18n-wired.sh) | Step 4 — `initI18n()` defined, called at boot, manifest passed into `backendOptions` |
|
|
65
|
+
|
|
66
|
+
## Depth docs
|
|
67
|
+
|
|
68
|
+
Framework-neutral (shared with Angular), in [`../common/`](../common/):
|
|
69
|
+
- [platform-sdk-i18n.md](../common/platform-sdk-i18n.md) — the shared runtime engine: the Labels GraphQL query, `fetchI18nContext`, the manifest format, batching, and fallback
|
|
70
|
+
- [label-xml.md](../common/label-xml.md) — Custom Labels and translation metadata XML shapes; the `namespace:Key` rules
|
|
71
|
+
- [verifying.md](../common/verifying.md) — serve URL, locale flip, and verifying labels render
|
|
72
|
+
- [gotchas.md](../common/gotchas.md) — the silent-fail traps: unregistered manifest keys, API-version bake-in, stale label cache
|
|
73
|
+
|
|
74
|
+
React-specific (this folder):
|
|
75
|
+
- [i18n-setup.md](./i18n-setup.md) — the two files you write: the i18next init and the label manifest
|
|
76
|
+
- [interpolation.md](./interpolation.md) — positional `{0}/{1}` placeholder interpolation in labels
|
|
@@ -14,22 +14,58 @@ set -euo pipefail # exit on error (-e), undefined vars (-u), and propagate pipe
|
|
|
14
14
|
# manifest's boilerplate example comment is stripped first so a commented-out
|
|
15
15
|
# sample key does not mask a genuinely missing one.
|
|
16
16
|
#
|
|
17
|
+
# This script is framework-agnostic EXCEPT for the call-site extraction (which
|
|
18
|
+
# file extensions to scan, and the translation-call grammar). That single block
|
|
19
|
+
# is selected by --framework; the rest — find the manifest, strip its comments,
|
|
20
|
+
# set-compare called-vs-registered keys, report missing — is shared. React's
|
|
21
|
+
# grammar is t("Key") in .tsx/.jsx; Angular's ngx-translate grammar is the
|
|
22
|
+
# `| translate` pipe and `[translate]="'Key'"` binding in .html/.ts templates
|
|
23
|
+
# plus translate.instant/get/stream("Key") service calls in .ts (see
|
|
24
|
+
# references/angular/localize.md).
|
|
25
|
+
#
|
|
17
26
|
# Usage (run from the UI bundle dir, or pass its src path):
|
|
18
|
-
# bash <skill-dir>/scripts/check-manifest-registered.sh [src-dir]
|
|
27
|
+
# bash <skill-dir>/scripts/check-manifest-registered.sh [--framework react|angular] [src-dir]
|
|
19
28
|
#
|
|
20
|
-
# src-dir defaults to "src"
|
|
29
|
+
# --framework defaults to "react". src-dir defaults to "src"; label-manifest.ts
|
|
30
|
+
# is found anywhere under it. Order of the flag and src-dir does not matter.
|
|
21
31
|
#
|
|
22
|
-
# Exit codes (kept aligned with check-i18n-wired.sh so the
|
|
23
|
-
# the code, not the message text):
|
|
32
|
+
# Exit codes (kept aligned with references/react/check-i18n-wired.sh so the
|
|
33
|
+
# workflow branches on the code, not the message text):
|
|
24
34
|
# 0 every t() key is registered (or there are no t() calls / no manifest to
|
|
25
35
|
# cross-check — nothing to gate)
|
|
26
|
-
# 1 one or more
|
|
27
|
-
# 64 usage error — the source dir does not exist
|
|
28
|
-
# This is NOT a "keys missing" result; do not
|
|
29
|
-
#
|
|
30
|
-
# exit 1 uniquely means "keys missing".)
|
|
36
|
+
# 1 one or more call-site keys are missing from the manifest — stop and register them
|
|
37
|
+
# 64 usage error — the source dir does not exist, an option is malformed, or the
|
|
38
|
+
# requested framework is unknown. This is NOT a "keys missing" result; do not
|
|
39
|
+
# scaffold or register. Fix the input and re-run. (64 = EX_USAGE, kept out of
|
|
40
|
+
# the 0/1 semantic range so exit 1 uniquely means "keys missing".)
|
|
41
|
+
|
|
42
|
+
FRAMEWORK="react"
|
|
43
|
+
SRC_DIR=""
|
|
44
|
+
while [ "$#" -gt 0 ]; do
|
|
45
|
+
case "$1" in
|
|
46
|
+
--framework)
|
|
47
|
+
[ "$#" -ge 2 ] || { echo "ERROR: --framework requires a value (react|angular)" >&2; exit 64; }
|
|
48
|
+
FRAMEWORK="$2"; shift 2 ;;
|
|
49
|
+
--framework=*) FRAMEWORK="${1#*=}"; shift ;;
|
|
50
|
+
-*) echo "ERROR: unknown option: $1" >&2; exit 64 ;;
|
|
51
|
+
*)
|
|
52
|
+
if [ -z "$SRC_DIR" ]; then SRC_DIR="$1"; shift
|
|
53
|
+
else echo "ERROR: unexpected extra argument: $1" >&2; exit 64; fi ;;
|
|
54
|
+
esac
|
|
55
|
+
done
|
|
56
|
+
SRC_DIR="${SRC_DIR:-src}"
|
|
57
|
+
|
|
58
|
+
# Select the framework's call-site grammar. CALL_LABEL is the human name for a
|
|
59
|
+
# call site, used only in report messages so they read for the selected framework
|
|
60
|
+
# (React `t()`, Angular `translate`) instead of hardcoding one framework's
|
|
61
|
+
# convention in this otherwise-shared script. The extraction block itself is
|
|
62
|
+
# selected by FRAMEWORK further below.
|
|
63
|
+
case "$FRAMEWORK" in
|
|
64
|
+
react) CALL_LABEL='t()' ;; # extraction below is React's t("Key") grammar
|
|
65
|
+
angular) CALL_LABEL='translate' ;; # extraction below is ngx-translate's grammar
|
|
66
|
+
*) echo "ERROR: unknown framework: '$FRAMEWORK' (expected: react | angular)" >&2; exit 64 ;;
|
|
67
|
+
esac
|
|
31
68
|
|
|
32
|
-
SRC_DIR="${1:-src}"
|
|
33
69
|
if [ ! -d "$SRC_DIR" ]; then
|
|
34
70
|
echo "ERROR: source dir not found: $SRC_DIR (run from the UI bundle dir, or pass its src path)" >&2
|
|
35
71
|
exit 64
|
|
@@ -37,23 +73,55 @@ fi
|
|
|
37
73
|
|
|
38
74
|
MANIFEST=$(find "$SRC_DIR" -type f -name 'label-manifest.ts' | head -1 || true)
|
|
39
75
|
|
|
40
|
-
# Collect
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
76
|
+
# Collect translation call sites, keeping the trailing key segment (drop any
|
|
77
|
+
# "ns:" prefix). Use POSIX character classes and explicit boundaries, not \b / \s:
|
|
78
|
+
# BSD/macOS grep -E silently ignores those, which would miss call sites on a Mac
|
|
79
|
+
# (the platform this skill targets) and let an unregistered key slip through —
|
|
80
|
+
# the exact silent-fail this script guards.
|
|
81
|
+
if [ "$FRAMEWORK" = "react" ]; then
|
|
82
|
+
# React: t("Key") / t('Key') in .tsx/.jsx. (^|[^A-Za-z0-9_]) before t( stops
|
|
83
|
+
# "insertText(" / "print(" from matching as t(.
|
|
84
|
+
CALLED=$(grep -rhoE "(^|[^A-Za-z0-9_])t\([[:space:]]*[\"'\`][^\"'\`]+[\"'\`]" "$SRC_DIR" \
|
|
85
|
+
--include='*.tsx' --include='*.jsx' 2>/dev/null \
|
|
86
|
+
| sed -E "s/.*[\"'\`]([^\"'\`]+)[\"'\`].*/\1/; s/.*://" \
|
|
87
|
+
| sort -u || true)
|
|
88
|
+
else
|
|
89
|
+
# Angular / ngx-translate: keys appear in .html templates (and inline .ts
|
|
90
|
+
# templates) via the `translate` pipe and the `[translate]` binding, and in .ts
|
|
91
|
+
# via TranslateService.instant/get/stream(...). Each pass below emits a fragment
|
|
92
|
+
# scoped to the KEY position (it stops before any trailing params object), then
|
|
93
|
+
# a shared token extractor pulls the quoted key(s). Scoping keeps a params
|
|
94
|
+
# object like {'0': x} — or an unrelated Map/HttpClient/FormGroup `.get(...)` —
|
|
95
|
+
# from being mistaken for a key. The service-call passes require a `translate`-
|
|
96
|
+
# shaped receiver ([Tt]ranslate...) precisely because a bare `.get(` is far too
|
|
97
|
+
# common in TS to treat as a translation call.
|
|
98
|
+
CALLED=$(
|
|
99
|
+
{
|
|
100
|
+
# pipe form: 'Key' | translate (.html + inline templates)
|
|
101
|
+
grep -rhoE "[\"'\`][^\"'\`]+[\"'\`][[:space:]]*\|[[:space:]]*translate" "$SRC_DIR" \
|
|
102
|
+
--include='*.html' --include='*.ts' 2>/dev/null || true
|
|
103
|
+
# property-binding form: [translate]="'Key'" (literal string only)
|
|
104
|
+
grep -rhoE "\[translate\][[:space:]]*=[[:space:]]*[\"'][[:space:]]*['\"\`][^'\"\`]+['\"\`]" "$SRC_DIR" \
|
|
105
|
+
--include='*.html' --include='*.ts' 2>/dev/null || true
|
|
106
|
+
# service call, single key: translate.instant/get/stream('Key'
|
|
107
|
+
grep -rhoE "[Tt]ranslate[A-Za-z0-9_]*[[:space:]]*\.[[:space:]]*(instant|get|stream)[[:space:]]*\([[:space:]]*[\"'\`][^\"'\`]+[\"'\`]" "$SRC_DIR" \
|
|
108
|
+
--include='*.ts' 2>/dev/null || true
|
|
109
|
+
# service call, array keys: translate.get(['A','B']) (stops at the ])
|
|
110
|
+
grep -rhoE "[Tt]ranslate[A-Za-z0-9_]*[[:space:]]*\.[[:space:]]*(instant|get|stream)[[:space:]]*\([[:space:]]*\[[^]]*\]" "$SRC_DIR" \
|
|
111
|
+
--include='*.ts' 2>/dev/null || true
|
|
112
|
+
} \
|
|
113
|
+
| grep -oE "['\"\`][^'\"\`]+['\"\`]" \
|
|
114
|
+
| sed -E "s/['\"\`]//g; s/.*://" \
|
|
115
|
+
| sort -u || true
|
|
116
|
+
)
|
|
117
|
+
fi
|
|
50
118
|
|
|
51
119
|
if [ -z "$CALLED" ]; then
|
|
52
|
-
echo "no
|
|
120
|
+
echo "no ${CALL_LABEL} call sites to check (scenario may not require the manifest) -> proceed"
|
|
53
121
|
exit 0
|
|
54
122
|
fi
|
|
55
123
|
if [ -z "$MANIFEST" ]; then
|
|
56
|
-
echo "ERROR:
|
|
124
|
+
echo "ERROR: ${CALL_LABEL} call sites exist but no label-manifest.ts was found under $SRC_DIR -> register them" >&2
|
|
57
125
|
echo "$CALLED" | sed 's/^/ missing: /' >&2
|
|
58
126
|
exit 1
|
|
59
127
|
fi
|
|
@@ -91,10 +159,10 @@ REGISTERED=$(awk '
|
|
|
91
159
|
MISSING=$(comm -23 <(echo "$CALLED") <(echo "$REGISTERED") || true)
|
|
92
160
|
|
|
93
161
|
if [ -n "$MISSING" ]; then
|
|
94
|
-
echo "ERROR:
|
|
162
|
+
echo "ERROR: ${CALL_LABEL} keys not registered in label-manifest.ts (they render as literal key names at runtime):" >&2
|
|
95
163
|
echo "$MISSING" | sed 's/^/ /' >&2
|
|
96
164
|
exit 1
|
|
97
165
|
fi
|
|
98
166
|
|
|
99
|
-
echo "all $(echo "$CALLED" | wc -l | tr -d ' ')
|
|
167
|
+
echo "all $(echo "$CALLED" | wc -l | tr -d ' ') ${CALL_LABEL} key(s) registered in the manifest -> proceed"
|
|
100
168
|
exit 0
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# detect-framework.sh — deterministic UI Bundle framework detector.
|
|
4
|
+
#
|
|
5
|
+
# Usage: bash detect-framework.sh [ROOT]
|
|
6
|
+
# ROOT app or uiBundle root to inspect (default: current directory)
|
|
7
|
+
#
|
|
8
|
+
# Prints exactly one of the following tokens to stdout, nothing else, and sets
|
|
9
|
+
# a matching exit code so callers can branch without parsing:
|
|
10
|
+
# react (exit 0) — React signals only
|
|
11
|
+
# angular (exit 0) — Angular signals only
|
|
12
|
+
# ambiguous (exit 2) — both React and Angular signals present
|
|
13
|
+
# unknown (exit 3) — neither framework detected
|
|
14
|
+
#
|
|
15
|
+
# Step 0 of the skill acts on the result: a single framework (exit 0) proceeds;
|
|
16
|
+
# `ambiguous` asks the user to disambiguate; `unknown` TERMINATES the workflow
|
|
17
|
+
# (no supported framework found — do not guess).
|
|
18
|
+
|
|
19
|
+
set -u
|
|
20
|
+
|
|
21
|
+
ROOT="${1:-.}"
|
|
22
|
+
angular=0
|
|
23
|
+
react=0
|
|
24
|
+
|
|
25
|
+
# Resolve ROOT to an absolute path when possible (for the angular.json walk-up).
|
|
26
|
+
abs_root="$(cd "$ROOT" 2>/dev/null && pwd || true)"
|
|
27
|
+
|
|
28
|
+
# 1) angular.json at or above ROOT → Angular workspace.
|
|
29
|
+
dir="$abs_root"
|
|
30
|
+
while [ -n "$dir" ]; do
|
|
31
|
+
[ -f "$dir/angular.json" ] && angular=1
|
|
32
|
+
[ "$dir" = "/" ] && break
|
|
33
|
+
dir="$(dirname "$dir")"
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
# 2) package.json dependencies (excluding node_modules).
|
|
37
|
+
while IFS= read -r pkg; do
|
|
38
|
+
[ -z "$pkg" ] && continue
|
|
39
|
+
grep -q '"@angular/core"' "$pkg" 2>/dev/null && angular=1
|
|
40
|
+
grep -Eq '"react"[[:space:]]*:' "$pkg" 2>/dev/null && react=1
|
|
41
|
+
done <<EOF
|
|
42
|
+
$(find "$ROOT" -type d -name node_modules -prune -o -type f -name package.json -print 2>/dev/null)
|
|
43
|
+
EOF
|
|
44
|
+
|
|
45
|
+
# 3) Source-file signatures (excluding node_modules).
|
|
46
|
+
has_file() {
|
|
47
|
+
find "$ROOT" -type d -name node_modules -prune -o -type f -name "$1" -print 2>/dev/null | grep -q .
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Angular: component files, routing module, or @Component-decorated classes.
|
|
51
|
+
has_file '*.component.ts' && angular=1
|
|
52
|
+
has_file 'app.routes.ts' && angular=1
|
|
53
|
+
if grep -rls --exclude-dir=node_modules --include='*.ts' '@Component' "$ROOT" 2>/dev/null | grep -q .; then
|
|
54
|
+
angular=1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# React: JSX/TSX source files.
|
|
58
|
+
has_file '*.tsx' && react=1
|
|
59
|
+
has_file '*.jsx' && react=1
|
|
60
|
+
|
|
61
|
+
if [ "$angular" -eq 1 ] && [ "$react" -eq 1 ]; then
|
|
62
|
+
echo ambiguous
|
|
63
|
+
exit 2
|
|
64
|
+
elif [ "$angular" -eq 1 ]; then
|
|
65
|
+
echo angular
|
|
66
|
+
exit 0
|
|
67
|
+
elif [ "$react" -eq 1 ]; then
|
|
68
|
+
echo react
|
|
69
|
+
exit 0
|
|
70
|
+
else
|
|
71
|
+
echo unknown
|
|
72
|
+
exit 3
|
|
73
|
+
fi
|
|
@@ -3,8 +3,8 @@ name: experience-ui-bundle-site-generate
|
|
|
3
3
|
description: "MUST activate when the project contains a uiBundles/*/src/ directory and the task involves creating or configuring site infrastructure. Use this skill when creating or configuring a Salesforce Digital Experience Site for hosting a UI bundle. Activate when files matching digitalExperiences/, networks/, customSite/, or DigitalExperienceBundle exist and need modification, or when the user wants to publish, host, or configure guest access for their app. Also use this skill to add multi-language, multi-locale, internationalization, or translation support to such a site by declaring a default locale and additional supported languages via the sfdc_cms__languageSettings content type. DO NOT TRIGGER for LWR (non-React) sites; use experience-lwr-site-generate instead."
|
|
4
4
|
metadata:
|
|
5
5
|
version: "1.3"
|
|
6
|
-
minApiVersion: "65.0"
|
|
7
6
|
domains: ["Experience"]
|
|
7
|
+
minApiVersion: "65.0"
|
|
8
8
|
relatedSkills:
|
|
9
9
|
- experience-lwr-site-generate
|
|
10
10
|
cliTools:
|
|
@@ -12,7 +12,7 @@ metadata:
|
|
|
12
12
|
This skill takes an intermediate JSON spec and produces a deployed Salesforce Flow with `processType=DataCaptureFlow`. It assumes the spec is already correct and approved — confirmation with the user happens upstream in the design skills.
|
|
13
13
|
|
|
14
14
|
> **Runtime contract:** every org interaction in this skill is a REST call
|
|
15
|
-
> dispatched through the Codey runtime (`
|
|
15
|
+
> dispatched through the Codey runtime (`dispatch` locally / the hosted
|
|
16
16
|
> Headless 360 MCP in shared surfaces). This skill has **no dependency on the
|
|
17
17
|
> execution environment** — no `sf` CLI, no shell scripts, no local Python, no
|
|
18
18
|
> temp files. Auth probes, record reads, and record writes are single REST
|
|
@@ -12,7 +12,7 @@ metadata:
|
|
|
12
12
|
This skill patches a flow that already exists in a connected org. The source of truth is the deployed flow's `Metadata` JSON, retrieved live from the Tooling `Flow` sObject — there is no spec file, no `.flow-meta.xml`, no zip, no SFDX project. The skill retrieves the JSON, edits it in memory, and PATCHes it back.
|
|
13
13
|
|
|
14
14
|
> **Runtime contract:** every org interaction in this skill is a REST call
|
|
15
|
-
> dispatched through the Codey runtime (`
|
|
15
|
+
> dispatched through the Codey runtime (`dispatch` locally / the hosted
|
|
16
16
|
> Headless 360 MCP in shared surfaces). This skill has **no dependency on the
|
|
17
17
|
> execution environment** — no `sf` CLI, no local Python, no temp files, no
|
|
18
18
|
> scratch SFDX project. Auth probes, the flow retrieve, and the redeploy are
|
|
@@ -12,6 +12,14 @@ metadata:
|
|
|
12
12
|
semver: ">=2.0.0"
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
+
# Querying Fs Data Capture Reference
|
|
16
|
+
|
|
17
|
+
## When to Use This Skill
|
|
18
|
+
|
|
19
|
+
Build, edit, and deploy Salesforce Data Capture Flows (processType DataCaptureFlow) — Field Service mobile / offline forms. Use when authoring flow-meta.xml with runtime_service_fieldservice:dc* components, Repeater loops (.AllItems), master-detail child record persistence, visual polish (gradient banners, progress bars, callouts), supporting objects with FLS/permsets, debugging DataCaptureFlow deploy errors, or troubleshooting why a deployed form doesn't appear on the FSL Mobile Forms tab (DDC/WorkPlan OWD + AssignedResource sharing prerequisites).
|
|
20
|
+
|
|
21
|
+
## Workflow
|
|
22
|
+
|
|
15
23
|
# Salesforce Data Capture Flow Skill
|
|
16
24
|
|
|
17
25
|
Build, edit, and deploy Salesforce Flows with `processType: DataCaptureFlow` (Field Service mobile / offline forms).
|
|
@@ -38,13 +46,6 @@ Optional `IsLlmTargetable` custom property — if you include it, it must be a J
|
|
|
38
46
|
|
|
39
47
|
The `<booleanValue>false</booleanValue>` form deploys but blocks activation — error: `The value of the IsLlmTargetable custom property's value field must be a string in JSON format`. Omitting the property entirely is also fine.
|
|
40
48
|
|
|
41
|
-
Required input variables:
|
|
42
|
-
```xml
|
|
43
|
-
<variables><name>recordId</name><dataType>String</dataType><isInput>true</isInput><isOutput>false</isOutput><isCollection>false</isCollection></variables>
|
|
44
|
-
<variables><name>parentRecordId</name><dataType>String</dataType><isInput>true</isInput><isOutput>false</isOutput><isCollection>false</isCollection></variables>
|
|
45
|
-
<variables><name>parentObjectType</name><dataType>String</dataType><isInput>true</isInput><isOutput>false</isOutput><isCollection>false</isCollection></variables>
|
|
46
|
-
```
|
|
47
|
-
|
|
48
49
|
---
|
|
49
50
|
|
|
50
51
|
## XML structure rules
|
|
@@ -430,8 +431,6 @@ The same accessors are also used inside `recordCreates` / `recordUpdates` `input
|
|
|
430
431
|
|
|
431
432
|
### Conditionally-hidden required fields — use `validationRule`, not `isRequired`
|
|
432
433
|
|
|
433
|
-
Never mark a field `isRequired=true` if it's behind a `visibilityRule`. The required check still fires while the field is hidden, so users can't proceed. Instead, set the field `isRequired=false` and wrap the rule:
|
|
434
|
-
|
|
435
434
|
```text
|
|
436
435
|
IF(TriggerField.selectedChoiceValues = "Yes",
|
|
437
436
|
AND(NOT(ISBLANK(value)), value >= 0, value <= 100000),
|
|
@@ -672,8 +671,6 @@ APEX
|
|
|
672
671
|
|
|
673
672
|
### 4. Tech must sign out and sign back in
|
|
674
673
|
|
|
675
|
-
FSL Mobile caches the sharing snapshot at login. Pull-to-refresh does not pick up new sharing — only a fresh auth token will. Tell the user to **sign out completely** of the FSL Mobile app, then sign back in. After that, the Forms tab fetch succeeds and DDC records render.
|
|
676
|
-
|
|
677
674
|
### What is NOT the cause (don't waste time on these)
|
|
678
675
|
|
|
679
676
|
- **Layout related-list naming** — `<relatedList>DynamicDataCapture</relatedList>` (singular) is the correct XML form. The UI API uses plural `DynamicDataCaptures` separately. Don't try to align them.
|
|
@@ -754,9 +751,25 @@ for f in r.get('details', {}).get('componentFailures', []):
|
|
|
754
751
|
|
|
755
752
|
## Reference examples
|
|
756
753
|
|
|
757
|
-
Bundled alongside this skill at `examples/`
|
|
754
|
+
Bundled alongside this skill at `examples/` (in this skill):
|
|
758
755
|
|
|
759
756
|
- `Data_Capture_All_Components.flow-meta.xml` — every component in deployment-ready XML
|
|
760
|
-
- `DataCapture_Showcase.flow-meta.xml` — full end-to-end flow: multi-screen form, continue-editing recordLookup, Repeater → Loop → child records via `.AllItems`, Create-or-Update CUD chain driven by a Decision, visual polish (banner, progress, callouts, review cards).
|
|
761
|
-
- `Repeater_with_prepopulation.flow-meta.xml` —
|
|
762
|
-
- `DataCapture_Repeater_with_data_showcase.flow` (in the org, not committed) — `.AllItems` loop spike; also documents non-working cross-row validation formula syntaxes
|
|
757
|
+
- `DataCapture_Showcase.flow-meta.xml` — full end-to-end flow: multi-screen form, continue-editing recordLookup, Repeater → Loop → child records via `.AllItems`, Create-or-Update CUD chain driven by a Decision, visual polish (banner, progress, callouts, review cards). **This is the canonical "truth" for XML format — when skill rules conflict with this file, the file wins.**
|
|
758
|
+
- `Repeater_with_prepopulation.flow-meta.xml` — canonical prepopulated-Repeater pattern: `recordLookups` (ServiceResource) → Repeater bound via `collection` inputParameter with nested field `value` defaults using `SourceCollection[$EachItem].FieldApiName` → post-Repeater Loop over `.AllItems` feeding a display screen that reads `{!LoopName.nestedField.value}` and `{!LoopName.UniqueField__Id}`. **Validated example — treat as ground truth for prepopulation and post-Repeater display XML.**
|
|
759
|
+
- `DataCapture_Repeater_with_data_showcase.flow` (in the org, not committed) — canonical `.AllItems` loop spike; also documents non-working cross-row validation formula syntaxes
|
|
760
|
+
|
|
761
|
+
─────
|
|
762
|
+
**Runtime context (Headless 360 / agentic):** When this skill runs in the Headless 360 / agentic context, prefer the platform dispatch tool (``dispatch`` in the hosted Headless 360 MCP; ``dispatch`` in the local-dev MCP) over CLI tools (``sf project deploy``, ``sfdx``, shell commands) when possible. The operations available to you are listed below in ``steps:``; each has been verified against the live org. Call the dispatch tool against the canonical paths. CLI fallback is acceptable only when no API path exists for a given capability.
|
|
763
|
+
|
|
764
|
+
## Critical Constraints
|
|
765
|
+
|
|
766
|
+
**Operational rules:**
|
|
767
|
+
|
|
768
|
+
- Required input variables:
|
|
769
|
+
```xml
|
|
770
|
+
<variables><name>recordId</name><dataType>String</dataType><isInput>true</isInput><isOutput>false</isOutput><isCollection>false</isCollection></variables>
|
|
771
|
+
<variables><name>parentRecordId</name><dataType>String</dataType><isInput>true</isInput><isOutput>false</isOutput><isCollection>false</isCollection></variables>
|
|
772
|
+
<variables><name>parentObjectType</name><dataType>String</dataType><isInput>true</isInput><isOutput>false</isOutput><isCollection>false</isCollection></variables>
|
|
773
|
+
```
|
|
774
|
+
- Never mark a field `isRequired=true` if it's behind a `visibilityRule`. The required check still fires while the field is hidden, so users can't proceed. Instead, set the field `isRequired=false` and wrap the rule:
|
|
775
|
+
- FSL Mobile caches the sharing snapshot at login. Pull-to-refresh does not pick up new sharing — only a fresh auth token will. Tell the user to **sign out completely** of the FSL Mobile app, then sign back in. After that, the Forms tab fetch succeeds and DDC records render.
|
|
@@ -51,7 +51,7 @@ Never expose internal skill names, SOR IDs, or tool references to the user.
|
|
|
51
51
|
- If not covered + no context: exact structured question
|
|
52
52
|
|
|
53
53
|
4. **Question format** (three or four parts):
|
|
54
|
-
```
|
|
54
|
+
```text
|
|
55
55
|
**Recommendation:** [Only include when context is sufficient. Lead with the best-fit option given what's known, then briefly note alternatives with the condition under which they'd apply instead — not a flat menu of equal choices, but a ranked steer. Omit entirely when confidence is low.]
|
|
56
56
|
|
|
57
57
|
**Question:** [The actual question — or a lighter "does this fit?" when a Recommendation is present]
|
|
@@ -16,7 +16,7 @@ This skill ingests a brand source and produces a 14-field color scheme that gets
|
|
|
16
16
|
> Methodology, color tables, and contrast checks are unchanged from upstream — refresh from upstream when content changes there.
|
|
17
17
|
|
|
18
18
|
> **Runtime contract:** every org interaction in this skill is a single REST
|
|
19
|
-
> call dispatched through the Codey runtime (`
|
|
19
|
+
> call dispatched through the Codey runtime (`dispatch` locally / the hosted
|
|
20
20
|
> Headless 360 MCP in shared surfaces). This skill has **no dependency on the
|
|
21
21
|
> execution environment** — no `sf` CLI, no shell scripts, no local Python, no
|
|
22
22
|
> temp files. Colors are derived and validated by the agent inline, then
|