@salesforce/webapp-template-feature-react-authentication-experimental 1.105.0 → 1.106.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 (62) hide show
  1. package/dist/.a4drules/skills/{feature-react-chart-analytics-charts → building-analytics-charts}/SKILL.md +1 -1
  2. package/dist/.a4drules/skills/{webapp-react-data-visualization → building-data-visualization}/SKILL.md +3 -3
  3. package/dist/.a4drules/skills/{webapp-react-data-visualization → building-data-visualization}/implementation/donut-chart.md +1 -1
  4. package/dist/.a4drules/skills/{webapp-react-interactive-map → building-interactive-map}/SKILL.md +1 -1
  5. package/dist/.a4drules/skills/{webapp-react → building-react-components}/SKILL.md +1 -1
  6. package/dist/.a4drules/skills/{webapp-react-weather-widget → building-weather-widget}/SKILL.md +2 -2
  7. package/dist/.a4drules/skills/{webapp-csp-trusted-sites → configuring-csp-trusted-sites}/SKILL.md +1 -1
  8. package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/SKILL.md +14 -14
  9. package/dist/.a4drules/skills/{feature-micro-frontend-micro-frontend → generating-micro-frontend-lwc}/SKILL.md +1 -1
  10. package/dist/.a4drules/skills/{feature-react-file-upload-file-upload → implementing-file-upload}/SKILL.md +1 -1
  11. package/dist/.a4drules/skills/{webapp-features → installing-webapp-features}/SKILL.md +1 -1
  12. package/dist/.a4drules/skills/{feature-react-agentforce-conversation-client-embedded-agent → integrating-agentforce-conversation-client}/SKILL.md +1 -1
  13. package/dist/.a4drules/skills/{webapp-unsplash-images → integrating-unsplash-images}/SKILL.md +1 -1
  14. package/dist/.a4drules/skills/salesforce-data-access/SKILL.md +165 -0
  15. package/dist/.a4drules/skills/salesforce-graphql/SKILL.md +323 -0
  16. package/dist/.a4drules/skills/salesforce-graphql-explore-schema/SKILL.md +160 -0
  17. package/dist/.a4drules/skills/{feature-graphql-graphql-data-access/docs/generate-mutation-query.md → salesforce-graphql-mutation-query/SKILL.md} +72 -42
  18. package/dist/.a4drules/skills/salesforce-graphql-read-query/SKILL.md +253 -0
  19. package/dist/.a4drules/skills/salesforce-rest-api-fetch/SKILL.md +167 -0
  20. package/dist/.a4drules/webapp-react.md +2 -50
  21. package/dist/AGENT.md +5 -0
  22. package/dist/CHANGELOG.md +19 -0
  23. package/dist/force-app/main/default/webapplications/feature-react-authentication/eslint.config.js +42 -27
  24. package/dist/force-app/main/default/webapplications/feature-react-authentication/vite.config.ts +17 -13
  25. package/dist/package.json +1 -1
  26. package/package.json +2 -2
  27. package/dist/.a4drules/features/feature-graphql-graphql-data-access-rule.md +0 -470
  28. package/dist/.a4drules/skills/feature-graphql-graphql-data-access/SKILL.md +0 -155
  29. package/dist/.a4drules/skills/feature-graphql-graphql-data-access/docs/explore-schema.md +0 -256
  30. package/dist/.a4drules/skills/feature-graphql-graphql-data-access/docs/generate-read-query.md +0 -202
  31. /package/dist/.a4drules/skills/{feature-react-chart-analytics-charts → building-analytics-charts}/docs/schema-mapping.md +0 -0
  32. /package/dist/.a4drules/skills/{webapp-react-data-visualization → building-data-visualization}/implementation/dashboard-layout.md +0 -0
  33. /package/dist/.a4drules/skills/{webapp-react-data-visualization → building-data-visualization}/implementation/stat-card.md +0 -0
  34. /package/dist/.a4drules/skills/{webapp-react-interactive-map → building-interactive-map}/implementation/geocoding.md +0 -0
  35. /package/dist/.a4drules/skills/{webapp-react-interactive-map → building-interactive-map}/implementation/leaflet-map.md +0 -0
  36. /package/dist/.a4drules/skills/{webapp-react → building-react-components}/implementation/component.md +0 -0
  37. /package/dist/.a4drules/skills/{webapp-react → building-react-components}/implementation/header-footer.md +0 -0
  38. /package/dist/.a4drules/skills/{webapp-react → building-react-components}/implementation/page.md +0 -0
  39. /package/dist/.a4drules/skills/{webapp-react-weather-widget → building-weather-widget}/implementation/weather-hook.md +0 -0
  40. /package/dist/.a4drules/skills/{webapp-react-weather-widget → building-weather-widget}/implementation/weather-ui.md +0 -0
  41. /package/dist/.a4drules/skills/{webapp-csp-trusted-sites → configuring-csp-trusted-sites}/implementation/metadata-format.md +0 -0
  42. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/charts.csv +0 -0
  43. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/colors.csv +0 -0
  44. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/icons.csv +0 -0
  45. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/landing.csv +0 -0
  46. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/products.csv +0 -0
  47. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/react-performance.csv +0 -0
  48. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/stacks/html-tailwind.csv +0 -0
  49. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/stacks/react.csv +0 -0
  50. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/stacks/shadcn.csv +0 -0
  51. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/styles.csv +0 -0
  52. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/typography.csv +0 -0
  53. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/ui-reasoning.csv +0 -0
  54. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/ux-guidelines.csv +0 -0
  55. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/data/web-interface.csv +0 -0
  56. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/scripts/core.js +0 -0
  57. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/scripts/design_system.js +0 -0
  58. /package/dist/.a4drules/skills/{webapp-ui-ux → designing-webapp-ui-ux}/scripts/search.js +0 -0
  59. /package/dist/.a4drules/skills/{feature-react-agentforce-conversation-client-embedded-agent → integrating-agentforce-conversation-client}/docs/embed-examples.md +0 -0
  60. /package/dist/.a4drules/skills/{feature-react-agentforce-conversation-client-embedded-agent → integrating-agentforce-conversation-client}/docs/troubleshooting.md +0 -0
  61. /package/dist/.a4drules/skills/{webapp-unsplash-images → integrating-unsplash-images}/implementation/usage.md +0 -0
  62. /package/dist/.a4drules/skills/{feature-graphql-graphql-data-access/docs → salesforce-graphql}/shared-schema.graphqls +0 -0
@@ -0,0 +1,253 @@
1
+ ---
2
+ name: salesforce-graphql-read-query
3
+ description: Generate Salesforce GraphQL read queries. Use when the query to generate is a read query. Schema exploration must complete first — invoke salesforce-graphql-explore-schema first.
4
+ paths:
5
+ - "**/*.ts"
6
+ - "**/*.tsx"
7
+ - "**/*.graphql"
8
+ ---
9
+
10
+ # Salesforce GraphQL Read Query Generation
11
+
12
+ **Triggering conditions**
13
+
14
+ 1. Only if the schema exploration phase completed successfully (invoke `salesforce-graphql-explore-schema` first)
15
+ 2. Only if the query to generate is a read query
16
+
17
+ ## Schema Access Policy
18
+
19
+ > ⚠️ **GREP ONLY** — During query generation you may need to verify field names, types, or relationships. All schema lookups **MUST** use the grep-only commands defined in the `salesforce-graphql-explore-schema` skill. Do NOT open, read, stream, or parse `./schema.graphql` with any tool other than grep.
20
+
21
+ ## Field-Level Security and @optional
22
+
23
+ Field-level security (FLS) restricts which fields different users can see. Use the `@optional` directive on Salesforce record fields when possible. The server omits the field when the user lacks access, allowing the query to succeed instead of failing. Apply `@optional` to scalar fields, value-type fields (e.g. `Name { value }`), parent relationships, and child relationships. Available in API v65.0+.
24
+
25
+ **Consuming code must defend against missing fields.** When a field is omitted due to FLS, it will be `undefined` (or absent) in the response. Use optional chaining (`?.`), nullish coalescing (`??`), and explicit null/undefined checks when reading query results. Never assume an optional field is present — otherwise the app may crash or behave incorrectly for users without field access.
26
+
27
+ ```ts
28
+ // ✅ Defend against missing fields
29
+ const name = node.Name?.value ?? '';
30
+ const relatedName = node.RelationshipName?.Name?.value ?? 'N/A';
31
+
32
+ // ❌ Unsafe — will throw if field omitted due to FLS
33
+ const name = node.Name.value;
34
+ ```
35
+
36
+ ## Your Role
37
+
38
+ You are a GraphQL expert. Generate Salesforce-compatible read queries. Schema exploration must complete first. If the schema exploration has not been executed yet, you **MUST** run the full exploration workflow from the `salesforce-graphql-explore-schema` skill first, then return here for read query generation.
39
+
40
+ ## Read Query Generation Workflow
41
+
42
+ Strictly follow the rules below when generating the GraphQL read query:
43
+
44
+ 1. **No Proliferation** - Only generate for the explicitly requested fields, nothing else. Do NOT add fields the user did not ask for.
45
+ 2. **Unique Query** - Leverage child relationships to query entities in one single query
46
+ 3. **Navigate Entities** - Always use `relationshipName` to access reference fields and child entities
47
+ 1. **Exception** - if the `relationshipName` field is null, you can't navigate the related entity, and will have to return the `Id` itself
48
+ 4. **Leverage Fragments** - Generate one fragment per possible type on polymorphic fields (field with `dataType="REFERENCE"` and more than one entry in `referenceToInfos` introspection attribute)
49
+ 5. **Type Consistency** - Make sure variables used as query arguments and their related fields share the same GraphQL type. Verify types against grep output from the schema — do not assume types
50
+ 6. **Type Enforcement** - Make sure to leverage field type information from introspection and GraphQL schema to generate field access
51
+ 7. **Field Name Validation** - Every field name in the generated query **MUST** match a field confirmed via grep lookup in the schema. Do NOT guess or assume field names exist
52
+ 8. **@optional for FLS** - Apply `@optional` on all Salesforce record fields when possible (see [Field-Level Security and @optional](#field-level-security-and-optional)). This lets the query succeed when the user lacks field-level access; the server omits inaccessible fields instead of failing
53
+ 9. **Consuming code defense** - When generating or modifying code that consumes read query results, defend against missing fields (see [Field-Level Security and @optional](#field-level-security-and-optional)). Use optional chaining, nullish coalescing, and null/undefined checks — never assume optional fields are present
54
+ 10. **Semi and anti joins** - Use the semi-join or anti-join templates to filter an entity with conditions on child entities
55
+ 11. **Query Generation** - Use the [template](#read-query-template) to generate the query
56
+ 12. **Output Format** - Use the [standalone](#read-standalone-default-output-format---clean-code-only)
57
+ 13. **Lint Validation** - After writing the query to a file, run `npx eslint <file>` from the webapp dir to validate it against the schema. Fix any reported errors before proceeding. See [Lint Validation](#lint-validation) for details
58
+ 14. **Test the Query** - Use the [Generated Read Query Testing](#generated-read-query-testing) workflow to test the generated query
59
+ 1. **Report First** - Always output the generated query in the proper output format BEFORE initiating any test
60
+
61
+ ## Read Query Template
62
+
63
+ ```graphql
64
+ query QueryName {
65
+ uiapi {
66
+ query {
67
+ EntityName(
68
+ # conditions here
69
+ ) {
70
+ edges {
71
+ node {
72
+ # Direct fields — use @optional for FLS resilience
73
+ FieldName @optional { value }
74
+
75
+ # Non-polymorphic reference (single type)
76
+ RelationshipName @optional {
77
+ Id
78
+ Name { value }
79
+ }
80
+
81
+ # Polymorphic reference (multiple types)
82
+ PolymorphicRelationshipName @optional {
83
+ ...TypeAInfo
84
+ ...TypeBInfo
85
+ }
86
+
87
+ # Child relationship (subquery)
88
+ RelationshipName @optional (
89
+ # conditions here
90
+ ) {
91
+ edges {
92
+ node {
93
+ # fields
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ fragment TypeAInfo on TypeA {
105
+ Id
106
+ SpecificFieldA @optional { value }
107
+ }
108
+
109
+ fragment TypeBInfo on TypeB {
110
+ Id
111
+ SpecificFieldB @optional { value }
112
+ }
113
+ ```
114
+
115
+ ## Semi-Join and Anti-Join Condition Template
116
+
117
+ Semi-joins (resp. anti-joins) condition leverage parent-child relationships and allow filtering the parent entity using a condition on child entities.
118
+ This is a standard `where` condition, on the parent entity's `Id`, expressed using the `inq` (resp. `ninq`, i.e. not `inq`) operator. This operator accepts two attributes:
119
+
120
+ - The child entity camelcase name to apply the condition on, with a value expressing the condition
121
+ - The field name on the child entity containing the parent entity `Id`, which is the `fieldName` from the `childRelationships` information for the child entity
122
+ - If the only condition is related child entity existence, you can use an `Id: { ne: null }` condition
123
+
124
+ ### Semi-Join Example - ParentEntity with at least one Matching ChildEntity
125
+
126
+ ```graphql
127
+ query testSemiJoin {
128
+ uiapi {
129
+ query {
130
+ ParentEntity(
131
+ where: {
132
+ Id: {
133
+ inq: {
134
+ ChildEntity: {
135
+ # standard conditions here
136
+ Name: { like: "test%" }
137
+ Type: { eq: "some value" }
138
+ }
139
+ ApiName: "parentIdFieldInChild"
140
+ }
141
+ }
142
+ }
143
+ ) {
144
+ edges {
145
+ node {
146
+ Id
147
+ Name @optional {
148
+ value
149
+ }
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ ### Anti-Join Example - ParentEntity with no Matching ChildEntity
159
+
160
+ Same example as the [Semi-Join Example](#semi-join-example---parententity-with-at-least-one-matching-childentity), but replacing the `inq` operator by the `ninq` one.
161
+
162
+ ## Read Standalone (Default) Output Format - CLEAN CODE ONLY
163
+
164
+ ```javascript
165
+ const QUERY_NAME = `
166
+ query GetData {
167
+ # query here
168
+ }
169
+ `;
170
+
171
+ const QUERY_VARIABLES = {
172
+ // variables here
173
+ };
174
+ ```
175
+
176
+ **❌ FORBIDDEN — Do NOT include any of the following:**
177
+
178
+ - Explanatory comments about the query (inline or surrounding)
179
+ - Field descriptions or annotations
180
+ - Additional text about what the query does
181
+ - Workflow step descriptions or summaries
182
+ - Comments like `// fetches...`, `// returns...`, `/* ... */`
183
+
184
+ **✅ ONLY output:**
185
+
186
+ - The raw query string constant
187
+ - The variables object constant
188
+ - Nothing else — no imports, no exports, no wrapper functions
189
+
190
+ ## Lint Validation
191
+
192
+ After writing the generated query into a source file, validate it against the schema using the project's GraphQL ESLint setup:
193
+
194
+ ```bash
195
+ # Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
196
+ npx eslint <path-to-file-containing-query>
197
+ ```
198
+
199
+ **How it works:** The ESLint config uses `@graphql-eslint/eslint-plugin` with its `processor`, which extracts GraphQL operations from `gql` template literals in `.ts`/`.tsx` files and validates the extracted `.graphql` virtual files against `schema.graphql`.
200
+
201
+ **Rules enforced:** `no-anonymous-operations`, `no-duplicate-fields`, `known-fragment-names`, `no-undefined-variables`, `no-unused-variables`
202
+
203
+ **On failure:** Fix the reported issues, re-run `npx eslint <file>` until clean, then proceed to testing.
204
+
205
+ > ⚠️ **Prerequisites**: The `schema.graphql` file must exist (invoke `salesforce-graphql-explore-schema` first) and project dependencies must be installed (`npm install`).
206
+
207
+ ## Generated Read Query Testing
208
+
209
+ **Triggering conditions** — **ALL conditions must be true:**
210
+
211
+ 1. The [Read Query Generation Workflow](#read-query-generation-workflow) completed with status `SUCCESS` and you have a generated query
212
+ 2. The query is a read query
213
+ 3. A non-manual method was used during schema exploration to retrieve introspection data
214
+
215
+ **Workflow**
216
+
217
+ 1. **Report Step** - State the exact method you will use to test (e.g., `sf api request graphql` from the **project root**, Connect API, etc.) — this **MUST** match the method used during schema exploration
218
+ 2. **Interactive Step** - Ask the user whether they want you to test the query using the proposed method
219
+ 1. **STOP and WAIT** for the user's answer. Do NOT proceed until the user responds. Do NOT assume consent.
220
+ 3. **Test Query** - Only if the user explicitly agrees:
221
+ 1. Use `sf api request rest` to POST the query to the GraphQL endpoint:
222
+ ```bash
223
+ sf api request rest /services/data/v65.0/graphql \
224
+ --method POST \
225
+ --body '{"query":"query GetData { uiapi { query { EntityName { edges { node { Id } } } } } }"}'
226
+ ```
227
+ 2. Replace `v65.0` with the API version of the target org
228
+ 3. Replace the `query` value with the generated read query string
229
+ 4. If the query uses variables, include them in the JSON body as a `variables` key
230
+ 5. Report the result as `SUCCESS` if the query executed without error, or `FAILED` if errors were returned
231
+ 6. An empty result set with no errors is `SUCCESS` — the query is valid, the org simply has no matching data
232
+ 4. **Remediation Step** - If status is `FAILED`, use the [`FAILED` status handling workflows](#failed-status-handling-workflow)
233
+
234
+ ### `FAILED` Status Handling Workflow
235
+
236
+ The query is invalid:
237
+
238
+ 1. **Error Analysis** - Parse and categorize the specific error messages
239
+ 2. **Root Cause Identification** - Use error message to identify the root cause:
240
+ - **Syntax** - Error contains `invalid syntax`
241
+ - **Validation** - Error contains `validation error`
242
+ - **Type** - Error contains `VariableTypeMismatch` or `UnknownType`
243
+ 3. **Targeted Resolution** - Depending on the root cause categorization
244
+ - **Syntax** - Update the query using the error message information to fix the syntax errors
245
+ - **Validation** - The field name is most probably invalid. Re-run the relevant grep command from the `salesforce-graphql-explore-schema` skill to verify the correct field name. If still unclear, ask the user for clarification and **STOP and WAIT** for their answer
246
+ - **Type** - Use the error details and re-verify the type via grep lookup in the schema. Correct the argument type and adjust variables accordingly
247
+ 4. **Test Again** - Resume the [query testing workflow](#generated-read-query-testing) with the updated query (increment and track attempt counter)
248
+ 5. **Escalation Path** - If targeted resolution fails after 2 attempts, ask for additional details and restart the entire GraphQL workflow from the `salesforce-graphql-explore-schema` skill
249
+
250
+ ## Related Skills
251
+
252
+ - Schema exploration: `salesforce-graphql-explore-schema` (must complete first)
253
+ - Mutation generation: `salesforce-graphql-mutation-query`
@@ -0,0 +1,167 @@
1
+ ---
2
+ name: salesforce-rest-api-fetch
3
+ description: REST API usage via the Data SDK fetch method. Use when implementing Chatter, Connect REST, Apex REST, UI API REST, or Einstein LLM calls — only when GraphQL is not sufficient.
4
+ paths:
5
+ - "**/*.ts"
6
+ - "**/*.tsx"
7
+ - "**/*.graphql"
8
+ ---
9
+
10
+ # Salesforce REST API via Data SDK Fetch
11
+
12
+ Use `sdk.fetch` from the Data SDK when GraphQL is not sufficient. The SDK applies authentication, CSRF handling, and base URL resolution. **Always use optional chaining** (`sdk.fetch?.()`) and handle the case where `fetch` is not available.
13
+
14
+ Invoke this skill when you need to call Chatter, Connect REST, Apex REST, UI API REST, or Einstein LLM endpoints.
15
+
16
+ ## API Version
17
+
18
+ Use the project's API version. It is typically injected as `__SF_API_VERSION__`; fallback to `"65.0"`:
19
+
20
+ ```typescript
21
+ declare const __SF_API_VERSION__: string;
22
+ const API_VERSION = typeof __SF_API_VERSION__ !== "undefined" ? __SF_API_VERSION__ : "65.0";
23
+ ```
24
+
25
+ ## Base Path
26
+
27
+ URLs are relative to the Salesforce API base. The SDK prepends the correct base path. Use paths starting with `/services/...`.
28
+
29
+ ---
30
+
31
+ ## Chatter API
32
+
33
+ User and collaboration data. No GraphQL equivalent.
34
+
35
+ | Endpoint | Method | Purpose |
36
+ | -------- | ------ | ------- |
37
+ | `/services/data/v{version}/chatter/users/me` | GET | Current user (id, name, email, username) |
38
+
39
+ ```typescript
40
+ const sdk = await createDataSDK();
41
+ const response = await sdk.fetch?.(`/services/data/v${API_VERSION}/chatter/users/me`);
42
+
43
+ if (!response?.ok) throw new Error(`HTTP ${response?.status}`);
44
+ const data = await response.json();
45
+ return { id: data.id, name: data.name };
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Connect REST API
51
+
52
+ File and content operations.
53
+
54
+ | Endpoint | Method | Purpose |
55
+ | -------- | ------ | ------- |
56
+ | `/services/data/v{version}/connect/file/upload/config` | GET | Upload config (token, uploadUrl) for file uploads |
57
+
58
+ ```typescript
59
+ const sdk = await createDataSDK();
60
+ const configRes = await sdk.fetch?.(`/services/data/v${API_VERSION}/connect/file/upload/config`, {
61
+ method: "GET",
62
+ });
63
+
64
+ if (!configRes?.ok) throw new Error(`Failed to get upload config: ${configRes?.status}`);
65
+ const config = await configRes.json();
66
+ const { token, uploadUrl } = config;
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Apex REST
72
+
73
+ Custom Apex REST resources. Requires corresponding Apex classes in the org. CSRF protection is applied automatically for `services/apexrest` URLs.
74
+
75
+ | Endpoint | Method | Purpose |
76
+ | -------- | ------ | ------- |
77
+ | `/services/apexrest/auth/login` | POST | User login |
78
+ | `/services/apexrest/auth/register` | POST | User registration |
79
+ | `/services/apexrest/auth/forgot-password` | POST | Request password reset |
80
+ | `/services/apexrest/auth/reset-password` | POST | Reset password with token |
81
+ | `/services/apexrest/auth/change-password` | POST | Change password (authenticated) |
82
+ | `/services/apexrest/{resource}` | GET/POST | Custom Apex REST resources |
83
+
84
+ **Example (login):**
85
+
86
+ ```typescript
87
+ const sdk = await createDataSDK();
88
+ const response = await sdk.fetch?.("/services/apexrest/auth/login", {
89
+ method: "POST",
90
+ body: JSON.stringify({ email, password, startUrl: "/" }),
91
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
92
+ });
93
+ ```
94
+
95
+ Apex REST paths do not include the API version.
96
+
97
+ ---
98
+
99
+ ## UI API (REST)
100
+
101
+ When GraphQL cannot cover the use case. **Prefer GraphQL** when possible.
102
+
103
+ | Endpoint | Method | Purpose |
104
+ | -------- | ------ | ------- |
105
+ | `/services/data/v{version}/ui-api/records/{recordId}` | GET | Fetch a single record |
106
+
107
+ ```typescript
108
+ const sdk = await createDataSDK();
109
+ const response = await sdk.fetch?.(`/services/data/v${API_VERSION}/ui-api/records/${recordId}`);
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Einstein LLM Gateway
115
+
116
+ AI features. Requires Einstein API setup.
117
+
118
+ | Endpoint | Method | Purpose |
119
+ | -------- | ------ | ------- |
120
+ | `/services/data/v{version}/einstein/llm/prompt/generations` | POST | Generate text from Einstein LLM |
121
+
122
+ ```typescript
123
+ const sdk = await createDataSDK();
124
+ const response = await sdk.fetch?.(`/services/data/v${API_VERSION}/einstein/llm/prompt/generations`, {
125
+ method: "POST",
126
+ headers: { "Content-Type": "application/json" },
127
+ body: JSON.stringify({
128
+ additionalConfig: { applicationName: "PromptTemplateGenerationsInvocable" },
129
+ promptTextorId: prompt,
130
+ }),
131
+ });
132
+
133
+ if (!response?.ok) throw new Error(`Einstein LLM failed (${response?.status})`);
134
+ const data = await response.json();
135
+ return data?.generations?.[0]?.text ?? "";
136
+ ```
137
+
138
+ ---
139
+
140
+ ## General Pattern
141
+
142
+ ```typescript
143
+ import { createDataSDK } from "@salesforce/sdk-data";
144
+
145
+ const sdk = await createDataSDK();
146
+
147
+ if (!sdk.fetch) {
148
+ throw new Error("Data SDK fetch is not available in this context");
149
+ }
150
+
151
+ const response = await sdk.fetch(url, {
152
+ method: "GET", // or POST, PUT, PATCH, DELETE
153
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
154
+ body: method !== "GET" ? JSON.stringify(payload) : undefined,
155
+ });
156
+
157
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
158
+ const data = await response.json();
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Reference
164
+
165
+ - Parent: `salesforce-data-access` — enforces Data SDK usage for all Salesforce data fetches
166
+ - GraphQL: `salesforce-graphql` — use for record queries and mutations when possible
167
+ - `createRecord` from `@salesforce/webapp-experimental/api` for UI API record creation (uses SDK internally)
@@ -46,7 +46,7 @@ import { Input } from '@/components/ui/input';
46
46
  - **No emojis ever:** Do not use emojis anywhere in the app: no emojis in UI labels, buttons, headings, empty states, tooltips, or any user-facing text. Use words and lucide-react icons instead.
47
47
  - ✅ "Settings" with `<Settings />` icon
48
48
  - ❌ "⚙️ Settings" or "Settings 🔧"
49
- - For icon usage patterns and naming, see the **webapp-ui-ux** skill (`.a4drules/skills/webapp-ui-ux/`, especially `data/icons.csv`).
49
+ - For icon usage patterns and naming, see the **designing-webapp-ui-ux** skill (`.a4drules/skills/designing-webapp-ui-ux/`, especially `data/icons.csv`).
50
50
 
51
51
  ## Editing UI — Source Files Only (CRITICAL)
52
52
 
@@ -90,55 +90,7 @@ Use standard web APIs and npm packages only.
90
90
 
91
91
  ## Data Access (CRITICAL)
92
92
 
93
- All Salesforce API calls **must** go through the DataSDK (`@salesforce/sdk-data`). Do NOT use `axios` or raw `fetch` the SDK handles authentication and CSRF.
94
-
95
- ### GraphQL (Preferred)
96
-
97
- For queries and mutations, follow the **`graphql-data-access`** skill in `feature-graphql`. It covers schema exploration, query patterns, codegen, type generation, and guardrails.
98
-
99
- ### UI API (Fallback)
100
-
101
- When GraphQL cannot cover the use case, use `sdk.fetch!()` for UI API endpoints:
102
-
103
- ```typescript
104
- const sdk = await getDataSDK();
105
- const resp = await sdk.fetch!('/services/data/v62.0/ui-api/records/{recordId}');
106
- ```
107
-
108
- ### Apex REST is NOT Available
109
-
110
- Apex REST cannot be called from React applications. If Apex seems required:
111
- 1. Evaluate if GraphQL can accomplish the task
112
- 2. If not, inform the user the feature is not supported in React
113
-
114
- ### MCP Tool Integration
115
-
116
- Before implementing data access:
117
- 1. Check if `orchestrate_lds_data_requirements` tool is available
118
- 2. Use it to get guidance on the appropriate pattern (GraphQL or UI API)
119
- 3. If it recommends Apex REST, ignore and use GraphQL instead
120
-
121
- ## Einstein LLM Gateway (AI Features)
122
-
123
- ```typescript
124
- import { getDataSDK } from '@salesforce/sdk-data';
125
-
126
- async function callEinsteinGenerations(prompt: string): Promise<string> {
127
- const sdk = await getDataSDK();
128
- const resp = await sdk.fetch!('/services/data/v62.0/einstein/llm/prompt/generations', {
129
- method: 'POST',
130
- headers: { 'Content-Type': 'application/json' },
131
- body: JSON.stringify({
132
- additionalConfig: { applicationName: 'PromptTemplateGenerationsInvocable' },
133
- promptTextorId: prompt,
134
- }),
135
- });
136
-
137
- if (!resp.ok) throw new Error(`Einstein LLM failed (${resp.status})`);
138
- const data = await resp.json();
139
- return data?.generations?.[0]?.text || '';
140
- }
141
- ```
93
+ For all Salesforce data access (GraphQL, REST, Chatter, Connect, Apex REST, UI API, Einstein LLM), invoke the **`salesforce-data-access`** skill (`.a4drules/skills/salesforce-data-access/`). It enforces Data SDK usage, GraphQL-first preference, optional chaining, and documents when to use `sdk.fetch` via the `salesforce-rest-api-fetch` skill.
142
94
 
143
95
  ## Error Handling
144
96
 
package/dist/AGENT.md CHANGED
@@ -58,6 +58,10 @@ cd <sfdx-source>/webapplications/<appName>
58
58
 
59
59
  This project includes **.a4drules/** at the project root. Follow them when generating or editing code.
60
60
 
61
+ - **Salesforce Data Access** (`.a4drules/skills/salesforce-data-access/`): Use for all Salesforce data fetches. Enforces Data SDK usage (`createDataSDK()` + `sdk.graphql` or `sdk.fetch`); GraphQL preferred, fetch when GraphQL is not sufficient.
62
+ - **Salesforce REST API Fetch** (`.a4drules/skills/salesforce-rest-api-fetch/`): Use when implementing Chatter, Connect REST, Apex REST, UI API REST, or Einstein LLM calls via `sdk.fetch`.
63
+ - **Salesforce GraphQL** (`.a4drules/skills/salesforce-graphql/`): Use when implementing Salesforce GraphQL queries or mutations. Sub-skills: `salesforce-graphql-explore-schema`, `salesforce-graphql-read-query`, `salesforce-graphql-mutation-query`.
64
+
61
65
  When rules refer to "web app directory" or `<sfdx-source>/webapplications/<appName>/`, resolve `<sfdx-source>` from `sfdx-project.json` and use the **actual app folder name** for this project.
62
66
 
63
67
  ## Deploying
@@ -79,3 +83,4 @@ sf project deploy start --source-dir <packageDir> --target-org <alias>
79
83
 
80
84
  - **UI**: shadcn/ui + Tailwind. Import from `@/components/ui/...`.
81
85
  - **Entry**: Keep `App.tsx` and routes in `src/`; add features as new routes or sections, don't replace the app shell but you may modify it to match the requested design.
86
+ - **Data (Salesforce)**: Invoke the `salesforce-data-access` skill for all Salesforce data fetches. The skill enforces use of the Data SDK (`createDataSDK()` + `sdk.graphql` or `sdk.fetch`) — never use `fetch` or `axios` directly. GraphQL is preferred; use `sdk.fetch` when GraphQL is not sufficient (e.g., Chatter, Connect REST). For GraphQL implementation, invoke the `salesforce-graphql` skill.
package/dist/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [1.106.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.105.1...v1.106.0) (2026-03-17)
7
+
8
+
9
+ ### Features
10
+
11
+ * **data-skills:** Restructure data knowledge into composable skills ([#296](https://github.com/salesforce-experience-platform-emu/webapps/issues/296)) ([35e0223](https://github.com/salesforce-experience-platform-emu/webapps/commit/35e0223ac8e14c451f204fd206d5ca29fb5e684a))
12
+
13
+
14
+
15
+
16
+
17
+ ## [1.105.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.105.0...v1.105.1) (2026-03-17)
18
+
19
+ **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
20
+
21
+
22
+
23
+
24
+
6
25
  # [1.105.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.104.1...v1.105.0) (2026-03-17)
7
26
 
8
27
  **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
@@ -1,3 +1,7 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname } from 'node:path';
1
5
  import js from '@eslint/js';
2
6
  import tseslint from '@typescript-eslint/eslint-plugin';
3
7
  import tsparser from '@typescript-eslint/parser';
@@ -7,7 +11,11 @@ import reactRefresh from 'eslint-plugin-react-refresh';
7
11
  import globals from 'globals';
8
12
  import graphqlPlugin from '@graphql-eslint/eslint-plugin';
9
13
 
10
- export default [
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const schemaPath = resolve(__dirname, '../../../../../schema.graphql');
16
+ const schemaExists = existsSync(schemaPath);
17
+
18
+ const config = [
11
19
  // Global ignores
12
20
  {
13
21
  ignores: ['build/**/*', 'dist/**/*', 'coverage/**/*'],
@@ -111,31 +119,38 @@ export default [
111
119
  '@typescript-eslint/no-explicit-any': 'off',
112
120
  },
113
121
  },
114
- // GraphQL processor - extracts GraphQL from gql template literals in TS/TSX files
115
- {
116
- files: ['**/*.{ts,tsx}'],
117
- processor: graphqlPlugin.processor,
118
- },
119
- // GraphQL linting configuration for .graphql files
120
- {
121
- files: ['**/*.graphql'],
122
- languageOptions: {
123
- parser: graphqlPlugin.parser,
124
- parserOptions: {
125
- graphQLConfig: {
126
- schema: '../../../../../schema.graphql',
122
+ ];
123
+
124
+ // Only add GraphQL rules when schema exists (e.g. after graphql:schema).
125
+ // In CI or when schema is not checked in, skip so lint succeeds.
126
+ if (schemaExists) {
127
+ config.push(
128
+ {
129
+ files: ['**/*.{ts,tsx}'],
130
+ processor: graphqlPlugin.processor,
131
+ },
132
+ {
133
+ files: ['**/*.graphql'],
134
+ languageOptions: {
135
+ parser: graphqlPlugin.parser,
136
+ parserOptions: {
137
+ graphQLConfig: {
138
+ schema: '../../../../../schema.graphql',
139
+ },
127
140
  },
128
141
  },
129
- },
130
- plugins: {
131
- '@graphql-eslint': graphqlPlugin,
132
- },
133
- rules: {
134
- '@graphql-eslint/no-anonymous-operations': 'error',
135
- '@graphql-eslint/no-duplicate-fields': 'error',
136
- '@graphql-eslint/known-fragment-names': 'error',
137
- '@graphql-eslint/no-undefined-variables': 'error',
138
- '@graphql-eslint/no-unused-variables': 'error',
139
- },
140
- },
141
- ];
142
+ plugins: {
143
+ '@graphql-eslint': graphqlPlugin,
144
+ },
145
+ rules: {
146
+ '@graphql-eslint/no-anonymous-operations': 'error',
147
+ '@graphql-eslint/no-duplicate-fields': 'error',
148
+ '@graphql-eslint/known-fragment-names': 'error',
149
+ '@graphql-eslint/no-undefined-variables': 'error',
150
+ '@graphql-eslint/no-unused-variables': 'error',
151
+ },
152
+ }
153
+ );
154
+ }
155
+
156
+ export default config;
@@ -1,3 +1,4 @@
1
+ import { existsSync } from 'node:fs';
1
2
  import { defineConfig } from 'vite';
2
3
  import react from '@vitejs/plugin-react';
3
4
  import path from 'path';
@@ -6,26 +7,29 @@ import tailwindcss from '@tailwindcss/vite';
6
7
  import salesforce from '@salesforce/vite-plugin-webapp-experimental';
7
8
  import codegen from 'vite-plugin-graphql-codegen';
8
9
 
10
+ const schemaPath = resolve(__dirname, '../../../../../schema.graphql');
11
+ const schemaExists = existsSync(schemaPath);
12
+
9
13
  export default defineConfig(({ mode }) => {
10
14
  return {
11
15
  base: './',
12
- // Type assertion avoids Plugin type mismatch when dist has its own node_modules (vite/rollup)
13
16
  plugins: [
14
17
  tailwindcss(),
15
18
  react(),
16
19
  salesforce(),
17
- codegen({
18
- // Path to the codegen config file
19
- configFilePathOverride: resolve(__dirname, 'codegen.yml'),
20
- // Run codegen on dev server start
21
- runOnStart: true,
22
- // Don't run codegen on build for now
23
- runOnBuild: false,
24
- // Enable file watcher during development
25
- enableWatcher: true,
26
- // Fail build if codegen errors
27
- throwOnBuild: true,
28
- }),
20
+ // Only add codegen when schema exists (e.g. after `npm run graphql:schema`).
21
+ // In CI or when schema is not checked in, skip codegen so build succeeds.
22
+ ...(schemaExists
23
+ ? [
24
+ codegen({
25
+ configFilePathOverride: resolve(__dirname, 'codegen.yml'),
26
+ runOnStart: true,
27
+ runOnBuild: true,
28
+ enableWatcher: true,
29
+ throwOnBuild: true,
30
+ }),
31
+ ]
32
+ : []),
29
33
  ] as import('vite').PluginOption[],
30
34
 
31
35
  // Build configuration for MPA
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
3
- "version": "1.105.0",
3
+ "version": "1.106.0",
4
4
  "description": "Base SFDX project template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "publishConfig": {