@salesforce/afv-skills 1.49.0 → 1.51.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.
Files changed (58) hide show
  1. package/package.json +1 -1
  2. package/skills/experience-ui-bundle-deploy/SKILL.md +43 -2
  3. package/skills/experience-ui-bundle-deploy/references/config-scaffold.md +16 -3
  4. package/skills/experience-ui-bundle-deploy/references/logout-url.md +97 -0
  5. package/skills/experience-ui-bundle-deploy/scripts/set-logout-url.mjs +304 -0
  6. package/skills/experience-ui-bundle-localize/SKILL.md +107 -90
  7. package/skills/experience-ui-bundle-localize/references/angular/check-i18n-wired.sh +159 -0
  8. package/skills/experience-ui-bundle-localize/references/angular/i18n-setup.md +250 -0
  9. package/skills/experience-ui-bundle-localize/references/angular/interpolation.md +156 -0
  10. package/skills/experience-ui-bundle-localize/references/angular/localize.md +111 -0
  11. package/skills/experience-ui-bundle-localize/references/{gotchas.md → common/gotchas.md} +27 -43
  12. package/skills/experience-ui-bundle-localize/references/{label-xml.md → common/label-xml.md} +27 -17
  13. package/skills/experience-ui-bundle-localize/references/common/platform-sdk-i18n.md +169 -0
  14. package/skills/experience-ui-bundle-localize/references/{verifying.md → common/verifying.md} +26 -16
  15. package/skills/experience-ui-bundle-localize/{scripts → references/react}/check-i18n-wired.sh +8 -3
  16. package/skills/experience-ui-bundle-localize/references/{i18n-setup.md → react/i18n-setup.md} +48 -10
  17. package/skills/experience-ui-bundle-localize/references/{interpolation.md → react/interpolation.md} +4 -4
  18. package/skills/experience-ui-bundle-localize/references/react/localize.md +76 -0
  19. package/skills/experience-ui-bundle-localize/scripts/check-manifest-registered.sh +92 -24
  20. package/skills/experience-ui-bundle-localize/scripts/detect-framework.sh +73 -0
  21. package/skills/experience-ui-bundle-site-generate/SKILL.md +1 -1
  22. package/skills/field-service-data-capture-form-deployer-configure/SKILL.md +180 -0
  23. package/skills/field-service-data-capture-form-deployer-configure/examples/inventory-transfer-spec.json +135 -0
  24. package/skills/field-service-data-capture-form-deployer-configure/examples/sample-spec.json +140 -0
  25. package/skills/field-service-data-capture-form-deployer-configure/examples/sectioned-spec.json +64 -0
  26. package/skills/field-service-data-capture-form-deployer-configure/references/field-types.md +297 -0
  27. package/skills/field-service-data-capture-form-deployer-configure/references/flow-metadata-json.md +243 -0
  28. package/skills/field-service-data-capture-form-deployer-configure/references/post-screen-automation.md +127 -0
  29. package/skills/field-service-data-capture-form-designer-configure/SKILL.md +110 -0
  30. package/skills/field-service-data-capture-form-designer-configure/references/extraction-from-image.md +244 -0
  31. package/skills/field-service-data-capture-form-designer-configure/references/extraction-from-prompt.md +222 -0
  32. package/skills/field-service-data-capture-form-editor-configure/SKILL.md +134 -0
  33. package/skills/field-service-data-capture-reference-configure/SKILL.md +762 -0
  34. package/skills/field-service-data-capture-reference-configure/examples/DataCapture_Showcase.flow-meta.xml +2022 -0
  35. package/skills/field-service-data-capture-reference-configure/examples/Data_Capture_All_Components.flow-meta.xml +2170 -0
  36. package/skills/field-service-data-capture-reference-configure/examples/Repeater_with_prepopulation.flow-meta.xml +231 -0
  37. package/skills/field-service-foundation-setup-designer-get/SKILL.md +135 -0
  38. package/skills/field-service-mobile-branding-configure/SKILL.md +133 -0
  39. package/skills/field-service-mobile-branding-configure/examples/dark-blue-scheme.json +16 -0
  40. package/skills/field-service-mobile-branding-configure/examples/salesforce-default-scheme.json +16 -0
  41. package/skills/field-service-mobile-branding-configure/references/color-fields.md +66 -0
  42. package/skills/field-service-mobile-branding-configure/references/contrast-validation.md +55 -0
  43. package/skills/field-service-mobile-branding-configure/references/derivation-methodology.md +83 -0
  44. package/skills/field-service-objective-designer-configure/SKILL.md +342 -0
  45. package/skills/field-service-prework-brief-deployer-configure/SKILL.md +83 -0
  46. package/skills/field-service-scheduling-policy-designer-query/SKILL.md +121 -0
  47. package/skills/field-service-setup-orchestrator-get/SKILL.md +48 -0
  48. package/skills/field-service-sobject-create-configure/SKILL.md +56 -0
  49. package/skills/field-service-voice-to-form-configure/SKILL.md +650 -0
  50. package/skills/field-service-work-rule-designer-configure/SKILL.md +303 -0
  51. package/skills/service-digital-engagement-channel-configure/SKILL.md +20 -37
  52. package/skills/service-digital-engagement-channel-configure/assets/messaging_channel_template.xml +3 -2
  53. package/skills/service-digital-engagement-channel-configure/examples/asa_agent_channel.xml +4 -4
  54. package/skills/service-helpagent-coordinate/SKILL.md +37 -29
  55. package/skills/service-helpagent-coordinate/assets/help-agent-spec.md +33 -28
  56. package/skills/service-helpagent-coordinate/references/agent-script.md +4 -1
  57. package/skills/service-helpagent-coordinate/references/channel-voice.md +1 -1
  58. package/skills/service-helpagent-coordinate/references/channel-web-chat.md +10 -12
@@ -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
- For a **B2C** bundle, change only the Salesforce backend options entry:
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
- For B2C, explicitly pass the route-selected display language to i18next and use it for the document language and direction, rather than relying on the detector or `ctx.dir`. The GraphQL i18n context direction reflects the guest session profile and can stay `ltr` after the site switches to an RTL language. Reuse the resolved language from the site's language-switcher integration:
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
- Then add `lng: resolvedLang` to the B2C `i18next.init({ ... })` options. The B2E example above remains unchanged.
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`; pass that value explicitly as i18next's `lng`, with `ctx.lang` as fallback. The SDK detector does not read `SFDC_ENV.language` itself. 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.
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; B2C separately passes the site route's `SFDC_ENV.language` to i18next as `lng`
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
- - [label-xml.md](label-xml.md): the Custom Labels metadata XML shape
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
@@ -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". label-manifest.ts is found anywhere under it.
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 workflow branches on
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 t() keys are missing from the manifest — stop and register them
27
- # 64 usage error — the source dir does not exist (bad argument or wrong cwd).
28
- # This is NOT a "keys missing" result; do not scaffold or register. Fix the
29
- # path and re-run. (64 = EX_USAGE, kept out of the 0/1 semantic range so
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 t("Key") / t('Key') call sites from component files, keeping the
41
- # trailing key segment (drop any "ns:" prefix). Use POSIX character classes and
42
- # an explicit boundary, not \b / \s: BSD/macOS grep -E silently ignores those,
43
- # which would miss call sites on a Mac (the platform this skill targets) and let
44
- # an unregistered key slip through — the exact silent-fail this script guards.
45
- # (^|[^A-Za-z0-9_]) before t( stops "insertText(" / "print(" matching as t(.
46
- CALLED=$(grep -rhoE "(^|[^A-Za-z0-9_])t\([[:space:]]*[\"'\`][^\"'\`]+[\"'\`]" "$SRC_DIR" \
47
- --include='*.tsx' --include='*.jsx' 2>/dev/null \
48
- | sed -E "s/.*[\"'\`]([^\"'\`]+)[\"'\`].*/\1/; s/.*://" \
49
- | sort -u || true)
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 t() call sites to check (scenario may not require the manifest) -> proceed"
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: t() call sites exist but no label-manifest.ts was found under $SRC_DIR -> register them" >&2
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: t() keys not registered in label-manifest.ts (they render as literal key names at runtime):" >&2
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 ' ') t() key(s) registered in the manifest -> proceed"
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:
@@ -0,0 +1,180 @@
1
+ ---
2
+ name: field-service-data-capture-form-deployer-configure
3
+ description: "Assemble a Data Capture Flow from a JSON spec and deploy it to a connected Field Service org via the Tooling Flow sObject (JSON Metadata, no XML). Use when given a data-capture spec JSON and asked to build or deploy a DataCaptureFlow."
4
+ user-invocable: false
5
+ metadata:
6
+ version: "1.0"
7
+ domains: ["Field Service"]
8
+ ---
9
+
10
+ # Build a Data Capture Form (Field Service Mobile)
11
+
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
+
14
+ > **Runtime contract:** every org interaction in this skill is a REST call
15
+ > dispatched through the Codey runtime (`execute_api` locally / the hosted
16
+ > Headless 360 MCP in shared surfaces). This skill has **no dependency on the
17
+ > execution environment** — no `sf` CLI, no shell scripts, no local Python, no
18
+ > temp files. Auth probes, record reads, and record writes are single REST
19
+ > calls; the Flow XML is authored by the agent inline from the reference docs.
20
+ > Do not shell out.
21
+
22
+ ## Input contract
23
+
24
+ A JSON file matching the schema in [reference/field-types.md](reference/field-types.md) and (optionally) [reference/post-screen-automation.md](reference/post-screen-automation.md). Canonical examples:
25
+
26
+ - [examples/sample-spec.json](examples/sample-spec.json) — minimal screens-only flow.
27
+ - [examples/inventory-transfer-spec.json](examples/inventory-transfer-spec.json) — full example with Repeater, Radio, visibility, decision, lookups, loop, and record-create.
28
+
29
+ Required top-level keys: `formTitle`, `formType`, `screens`. Optional: `postScreen`.
30
+
31
+ ## Output
32
+
33
+ A Data Capture Flow created in the org via a single Tooling API call — `POST /services/data/vXX.0/tooling/sobjects/Flow` with a JSON `Metadata` body (no `.flow-meta.xml`, no zip, no SFDX project). The flow is created in `Draft` status and the user activates it themselves in Flow Builder.
34
+
35
+ ## Workflow
36
+
37
+ ### 1. Verify org auth
38
+
39
+ Confirm the connected org is reachable with a cheap auth probe — dispatch `SELECT Id FROM Organization LIMIT 1` (`GET /services/data/vXX.0/query`):
40
+
41
+ - 2xx with `totalSize=1` → the session token is live; continue.
42
+ - 401/403 → the org needs re-authentication. Surface that to the user and **stop**; do not deploy. (The Codey runtime resolves and refreshes the connected org — this skill does not manage org aliases.)
43
+
44
+ ### 2. Pick a Flow API name
45
+
46
+ If the design skill already supplied `<FlowApiName>`, use it. Otherwise derive from `formTitle`: PascalCase, no spaces, must match `^[A-Z][A-Za-z0-9_]*$`. If the title can't be coerced, ask the user.
47
+
48
+ ### 3. Build the Flow Metadata JSON
49
+
50
+ Assemble the flow's `Metadata` object **inline** from the spec — the Tooling `Flow` sObject takes a JSON `Metadata` blob, so there is no XML to compile and no converter to run. Follow the JSON shape and field mappings in [reference/flow-metadata-json.md](reference/flow-metadata-json.md) and [reference/field-types.md](reference/field-types.md).
51
+
52
+ Notes:
53
+ - The JSON `Metadata` is the exact same shape the Tooling `Flow` GET returns (`GET /tooling/sobjects/Flow/{id}` → `Metadata`), so you can retrieve a known-good sibling flow as a live reference before composing.
54
+ - **Dedupe choices** across the entire flow — two fields with `["Good","Fair","Poor"]` share the same three entries in the top-level `choices` array.
55
+ - Repeater children are nested as `fields` entries inside the parent Repeater field.
56
+ - `Signature`, `UploadFile`, `UploadImage`, and `Images` auto-wire `parentRecordId` / `recordId` to the standard DataCaptureFlow input variables. They deploy as functional components, no Flow Builder cleanup required.
57
+ - `Lookup` requires a `lookupObject` spec key; without it, emit the labeled `dcTextInput` placeholder. `FileView` requires a `fileName`; same fallback. Collect any such fallbacks and surface them in step 5.
58
+ - Self-check before deploying: `processType` is `DataCaptureFlow`, `environments` includes `Offline`, and the three input variables (`parentObjectType`, `parentRecordId`, `recordId`) are present.
59
+
60
+ ### 4. Deploy to org
61
+
62
+ Create the flow with a single Tooling API call — dispatch `POST /services/data/vXX.0/tooling/sobjects/Flow` with body:
63
+
64
+ ```json
65
+ {
66
+ "FullName": "<FlowApiName>",
67
+ "Metadata": { "processType": "DataCaptureFlow", "environments": ["Offline"], "label": "...", "screens": [ ... ], "choices": [ ... ], "variables": [ ... ], "status": "Draft" }
68
+ }
69
+ ```
70
+
71
+ - `FullName` is the Flow API name; `Metadata` is the object you assembled in step 3.
72
+ - A 201 with `success: true` returns the new Flow version id. `Status` stays `Draft` (set `Metadata.status: "Active"` only if the user asked to activate on create — the default is Draft so the user reviews in Flow Builder first).
73
+ - On a 400, the response body's `message` carries the Flow validation error — diagnose against step 5's failure table.
74
+
75
+ ### 5. Report back
76
+
77
+ On success:
78
+ - Look up the FlowDefinition Id with a Tooling API query — dispatch `GET /services/data/vXX.0/tooling/query` with `SELECT Id, ActiveVersionId FROM FlowDefinition WHERE DeveloperName = '<FlowApiName>'`.
79
+ - Print a clickable Flow Builder URL: `<instanceUrl>/builder_platform_interaction/flowBuilder.app?flowId=<id>`.
80
+ - List screens, total field count, and any fallback fields (Lookup with no `lookupObject`, FileView with no `fileName`) the user needs to wire up in Flow Builder.
81
+ - Print the direct flow-launch URL: `<instanceUrl>/flow/<FlowApiName>` — the fastest validation path that bypasses QuickActions, layouts, and the Forms tab.
82
+
83
+ On failure (the `POST` returned a 400 — read the error from the response body's `message`):
84
+ - If `Cannot find component 'runtime_service_fieldservice:dcXxx'` → org doesn't have Field Service enabled (or the component name is wrong). Surface the exact error and stop.
85
+ - For Flow validation errors, the cause is usually a pattern listed in the prohibited-patterns table at [fs-data-capture-reference/SKILL.md](../fs-data-capture-reference/SKILL.md). Read that file before retrying. Common diagnoses: schema-grouping violations, `.AllItems` vs `.AddedItems` accessor mismatch, CUD ordering, missing `nextOrFinishButtonLabel`, `IsLlmTargetable` boolean-vs-string.
86
+ - Don't loop more than twice without showing the user.
87
+ - Common gotcha: if you emit an implicit `Sec_General` section for any screen whose first field appears before an explicit `{ "section": "..." }` header, two such screens collide with `Duplicate developer name: Sec_General`. Give each such screen an explicit leading section in the JSON, then re-assemble and re-POST.
88
+
89
+ ### 6. Make the form visible (optional but usually wanted)
90
+
91
+ Deploying the flow does NOT make it appear in the "Forms" related list on a Service Appointment, Work Order, or other parent. To make a deployed flow show up as a pending form a tech can pick up:
92
+
93
+ 1. **Attach a `DynamicDataCapture` record to the parent.** This is the SDO's canonical "pending form" pattern — see how shipped SDO forms (Job Safety, Vehicle Inspection, Job Completion) are wired. Create the record with a single sObject insert — dispatch `POST /services/data/vXX.0/sobjects/DynamicDataCapture` with this body:
94
+
95
+ ```json
96
+ {
97
+ "Name": "<Display Name>",
98
+ "ParentRecordId": "<ParentRecordId>",
99
+ "ActionDefinition": "<FlowApiName>",
100
+ "ActionType": "Flow",
101
+ "ProcessType": "DataCaptureFlow",
102
+ "StatusCategory": "New",
103
+ "IsRequired": true,
104
+ "ExecutionOrder": 1
105
+ }
106
+ ```
107
+
108
+ `Name` defaults to `<FlowApiName>` with underscores → spaces if the caller gives no display name; `IsRequired` is a real boolean (`true`/`false`), not a string. A 201 with `success: true` returns the new DDC id. `ParentRecordId` is polymorphic — accepted parent types are `ServiceAppointment`, `ServiceResource`, `TimeSheet`, `Visit`, `WorkOrder`, `WorkOrderLineItem`.
109
+
110
+ 2. **For FSL Mobile / Service Appointment context, attach to the parent Work Order, not the SA itself.** FSL Mobile's Forms tab on a Service Appointment typically aggregates `DynamicDataCapture` records from the SA's parent Work Order (via `ServiceAppointment.ParentRecordId`). Attaching directly to the SA may not surface in mobile.
111
+
112
+ Resolve the SA's parent Work Order Id first — dispatch `GET /services/data/vXX.0/query` with `SELECT ParentRecordId FROM ServiceAppointment WHERE Id = '<SA_Id>'`, then attach (sub-step 1) to that Work Order Id.
113
+
114
+ 3. **Verify the parent's page layout has the Forms (DynamicDataCapture) related list.** Different SDOs use different layouts per profile. Query with the Tooling API — dispatch `GET /services/data/vXX.0/tooling/query` with `SELECT Layout.Name, Profile.Name FROM ProfileLayout WHERE TableEnumOrId = 'WorkOrder'`.
115
+
116
+ `Layout` is itself a Tooling sObject with a JSON `Metadata` field, so the splice is a REST read-modify-write — no XML file, no deploy. Read the layout with `GET /services/data/vXX.0/tooling/sobjects/Layout/{layoutId}` (resolve `{layoutId}` from the `ProfileLayout.LayoutId` in the query above), check `Metadata.relatedLists` for a `DynamicDataCapture` entry, and if absent append this entry and `PATCH /services/data/vXX.0/tooling/sobjects/Layout/{layoutId}` with the updated `Metadata`:
117
+
118
+ ```json
119
+ {
120
+ "relatedList": "DynamicDataCapture",
121
+ "fields": ["Name", "StatusCategory", "IsRequired"]
122
+ }
123
+ ```
124
+
125
+ 4. **Caveats:**
126
+ - Attached form must have `StatusCategory='New'` to appear as pending. `Completed` records show as historical.
127
+ - `ActionDefinition` must exactly match the deployed flow's API name (case-sensitive).
128
+ - `ProcessType='DataCaptureFlow'` is required — the SDO sometimes also uses `DiscoveryFrameworkFlow`.
129
+ - If the parent profile's layout lacks the Forms list, attaching the DDC succeeds at the data layer but the form is invisible in the UI.
130
+
131
+ 5. **Each profile has its own page layout.** Real SDOs commonly route different profiles to different Work Order layouts (e.g. `System Administrator` → `SDO SFS Work Order Layout`, `Standard User` → `Work Order Layout`, `SDO-Service` → `FSL Work Order Layout`). Patching one layout doesn't help users on the others. Run the ProfileLayout query above for every profile that needs to see the form, then patch the union of layouts.
132
+
133
+ 6. **DDC + WorkPlan OWD must be Public Read/Write for FSL Mobile.** The Forms tab on FSL Mobile uses the UI API (`/ui-api/related-list-records/<woId>/DynamicDataCaptures`), which enforces sharing. If `DynamicDataCapture` or `WorkPlan` OWD is **Private** (the platform default), the technician got the WO via AssignedResource sharing and has zero row access to the DDCs themselves — UI API returns `INSUFFICIENT_ACCESS` and the Forms tab silently shows "No forms available. We couldn't find any forms to display." with a "Try Again" button. Desktop SOQL as admin doesn't catch this because admins bypass sharing.
134
+
135
+ **Fix:**
136
+ - Set the org-wide default for `DynamicDataCapture` and `WorkPlan` to **Public Read/Write**. Org-wide sharing defaults are a Setup-only surface — surface the deeplink `<instanceUrl>/lightning/setup/SecuritySharing/home` and have the admin set both objects' Default Internal Access to Public Read/Write. (This is a click-through, not a shell step.)
137
+ - Set `doesShareSaParentWoWithAr` and `doesShareSaWithAr` to `true` on `FieldServiceSettings` via a Tooling PATCH — `GET /services/data/vXX.0/tooling/query` `SELECT Id, Metadata FROM FieldServiceSettings` to read the singleton, then `PATCH /services/data/vXX.0/tooling/sobjects/FieldServiceSettings/{id}` with `{"Metadata": {"doesShareSaParentWoWithAr": true, "doesShareSaWithAr": true}}` (merge — include the existing Metadata keys).
138
+ - Re-save existing `AssignedResource` records to trigger sharing recalc — a no-op `PATCH /services/data/vXX.0/sobjects/AssignedResource/{id}` per record re-fires the sharing rules.
139
+ - User must **sign out + back in to FSL Mobile** — the sharing snapshot is cached at login.
140
+
141
+ Verify with a Tooling API query — dispatch `GET /services/data/vXX.0/tooling/query` with `SELECT QualifiedApiName, InternalSharingModel FROM EntityDefinition WHERE QualifiedApiName IN ('DynamicDataCapture','WorkPlan')`. Both should return `ReadWrite`. If either returns `Private`, the Forms tab will fail for the tech even though the DDC row exists.
142
+
143
+ 7. **After attaching, mobile may need a refresh.** Even with OWD correct, the FSL Mobile Forms tab caches the related list. To pick up a newly-attached DDC: **pull-to-refresh on the Forms tab** on the Work Order. Force-quit + reopen the app if pull-to-refresh doesn't surface it. Sign out + back in is only needed when sharing changes (#6).
144
+
145
+ 8. **UI API version note (testing only).** UI API v60 returns `INSUFFICIENT_ACCESS` even after sharing is correct; v62+ works. The iOS FSL Mobile app hardcodes v67, so this isn't a production issue — but it matters when reproducing the call via curl.
146
+
147
+ ## Scope
148
+
149
+ **Generated automatically:**
150
+ - Field labels, types, and required state from the spec.
151
+ - Native types: `ShortText`, `LongText`, `Name`, `Email`, `Phone`, `Numeric`, `Counter` (with `min`/`max`/`value`), `Date`, `DateTime`, `Checkbox`, `Toggle`, `Picklist`, `Radio` (`dcRbGroup`), `CheckboxGroup`, `DisplayText`, `Repeater`.
152
+ - Specialized Field Service components: `Signature` (auto-wires `parentRecordId` + `recordId`), `UploadFile`, `UploadImage`, `Images` (auto-wire `recordId` → `parentRecordId`), `Address` (compound), `Matrix` (column choices + `questions` row labels), `Lookup` (with `lookupObject`/`lookupSearchFields`/`lookupMulti` spec keys), `FileView` (with `fileName` spec key).
153
+ - Conditional visibility on individual fields.
154
+ - Optional `postScreen` automation: a decision, multiple `recordLookups`, one `loop`, multiple `recordCreates`, and extra non-input variables.
155
+
156
+ **Falls back to deploy-safe placeholders** (admin replaces in Flow Builder):
157
+ - `Lookup` *with no `lookupObject`* → `dcTextInput` with `[Lookup — set objectApiName in Flow Builder]` prefix.
158
+ - `FileView` *with no `fileName`* → `dcTextInput` with `[FileView — set fileName in Flow Builder]` prefix.
159
+
160
+ See [reference/field-types.md](reference/field-types.md) for the full mapping.
161
+
162
+ **Out of scope:**
163
+ - Multiple decisions / multiple loops / nested loops in `postScreen`.
164
+ - Subflows, formulas, text templates, assignments.
165
+ - Visual polish HTML (banners, progress bars, callouts) — those are hand-authored. See `fs-data-capture-reference` skill for patterns.
166
+
167
+ ## Files in this skill
168
+
169
+ This skill has **no executable scripts**. Auth checks, record reads, the `DynamicDataCapture` attach, and the flow create/deploy are all single REST calls dispatched through the Codey runtime (steps 1, 4, 5, 6). The Flow Metadata JSON is assembled by the agent inline (step 3) from the reference docs below.
170
+
171
+ - `reference/flow-metadata-json.md` — the Tooling `Flow.Metadata` JSON shape (screens, choices, decisions, variables, post-screen chain) and the deploy/activate calls. Read this when composing the flow.
172
+ - `reference/field-types.md` — input contract: spec `fieldType` → runtime component + JSON attributes.
173
+ - `reference/post-screen-automation.md` — input contract for the optional `postScreen` block.
174
+ - `examples/sample-spec.json`, `examples/inventory-transfer-spec.json` — canonical specs.
175
+
176
+ ## Related skills
177
+
178
+ - **`fs-data-capture-reference`** (sibling library skill) — reference manual for hand-authoring patterns, prohibited patterns + exact deploy errors, visual polish HTML, supporting CustomObject/PermissionSet/CustomTab deploy. Read this when diagnosing a deploy error or extending the JSON field mappings.
179
+ - **`fs-data-capture-form-designer`** — produces the spec this skill consumes (from prose or an image/PDF).
180
+ - **`fs-data-capture-form-editor`** — patches an already-deployed flow in the org. Uses the same Tooling `Flow` JSON round-trip (GET Metadata → edit → PATCH) this skill uses to create.