@salesforce/webapp-template-app-react-template-b2e-experimental 1.82.0 → 1.84.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 (51) hide show
  1. package/dist/.a4drules/skills/webapp-csp-trusted-sites/SKILL.md +90 -0
  2. package/dist/.a4drules/skills/webapp-csp-trusted-sites/implementation/metadata-format.md +281 -0
  3. package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/SKILL.md +1 -1
  4. package/dist/.a4drules/skills/webapp-react-data-visualization/SKILL.md +72 -0
  5. package/dist/.a4drules/skills/webapp-react-data-visualization/implementation/dashboard-layout.md +189 -0
  6. package/dist/.a4drules/skills/webapp-react-data-visualization/implementation/donut-chart.md +181 -0
  7. package/dist/.a4drules/skills/webapp-react-data-visualization/implementation/stat-card.md +150 -0
  8. package/dist/.a4drules/skills/webapp-react-interactive-map/SKILL.md +92 -0
  9. package/dist/.a4drules/skills/webapp-react-interactive-map/implementation/geocoding.md +245 -0
  10. package/dist/.a4drules/skills/webapp-react-interactive-map/implementation/leaflet-map.md +279 -0
  11. package/dist/.a4drules/skills/webapp-react-weather-widget/SKILL.md +65 -0
  12. package/dist/.a4drules/skills/webapp-react-weather-widget/implementation/weather-hook.md +258 -0
  13. package/dist/.a4drules/skills/webapp-react-weather-widget/implementation/weather-ui.md +216 -0
  14. package/dist/.a4drules/skills/webapp-ui-ux/SKILL.md +268 -0
  15. package/dist/.a4drules/skills/webapp-ui-ux/data/charts.csv +26 -0
  16. package/dist/.a4drules/skills/webapp-ui-ux/data/colors.csv +97 -0
  17. package/dist/.a4drules/skills/webapp-ui-ux/data/icons.csv +101 -0
  18. package/dist/.a4drules/skills/webapp-ui-ux/data/landing.csv +31 -0
  19. package/dist/.a4drules/skills/webapp-ui-ux/data/products.csv +97 -0
  20. package/dist/.a4drules/skills/webapp-ui-ux/data/react-performance.csv +45 -0
  21. package/dist/.a4drules/skills/webapp-ui-ux/data/stacks/html-tailwind.csv +56 -0
  22. package/dist/.a4drules/skills/webapp-ui-ux/data/stacks/react.csv +54 -0
  23. package/dist/.a4drules/skills/webapp-ui-ux/data/stacks/shadcn.csv +61 -0
  24. package/dist/.a4drules/skills/webapp-ui-ux/data/styles.csv +68 -0
  25. package/dist/.a4drules/skills/webapp-ui-ux/data/typography.csv +58 -0
  26. package/dist/.a4drules/skills/webapp-ui-ux/data/ui-reasoning.csv +101 -0
  27. package/dist/.a4drules/skills/webapp-ui-ux/data/ux-guidelines.csv +100 -0
  28. package/dist/.a4drules/skills/webapp-ui-ux/data/web-interface.csv +31 -0
  29. package/dist/.a4drules/skills/webapp-ui-ux/scripts/core.js +255 -0
  30. package/dist/.a4drules/skills/webapp-ui-ux/scripts/design_system.js +861 -0
  31. package/dist/.a4drules/skills/webapp-ui-ux/scripts/search.js +98 -0
  32. package/dist/.a4drules/skills/webapp-unsplash-images/SKILL.md +71 -0
  33. package/dist/.a4drules/skills/webapp-unsplash-images/implementation/usage.md +159 -0
  34. package/dist/.a4drules/webapp-no-node-e.md +54 -15
  35. package/dist/.a4drules/webapp-react.md +9 -10
  36. package/dist/.a4drules/webapp-skills-first.md +26 -0
  37. package/dist/.a4drules/webapp.md +8 -0
  38. package/dist/AGENT.md +2 -0
  39. package/dist/CHANGELOG.md +22 -0
  40. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/package.json +4 -4
  41. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/webapplication.json +1 -1
  42. package/dist/package.json +3 -2
  43. package/dist/scripts/prepare-import-unique-fields.js +108 -0
  44. package/dist/scripts/setup-cli.mjs +282 -0
  45. package/package.json +1 -1
  46. package/dist/.a4drules/webapp-images.md +0 -15
  47. /package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/implementation/component.md +0 -0
  48. /package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/implementation/header-footer.md +0 -0
  49. /package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/implementation/page.md +0 -0
  50. /package/dist/.a4drules/{webapp-code-quality.md → webapp-react-code-quality.md} +0 -0
  51. /package/dist/.a4drules/{webapp-typescript.md → webapp-react-typescript.md} +0 -0
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: webapp-csp-trusted-sites
3
+ description: Creates Salesforce CSP Trusted Site metadata when adding external domains. Use when the user adds an external API, CDN, image host, font provider, map tile server, or any third-party URL that the web application needs to load resources from — or when a browser console shows a CSP violation error.
4
+ ---
5
+
6
+ # CSP Trusted Sites
7
+
8
+ ## When to Use
9
+
10
+ Use this skill whenever the application references a new external domain that is not already registered as a CSP Trusted Site. This includes:
11
+
12
+ - Adding images from a new CDN (Unsplash, Pexels, Cloudinary, etc.)
13
+ - Loading fonts from an external provider (Google Fonts, Adobe Fonts)
14
+ - Calling a third-party API (Open-Meteo, Nominatim, Mapbox, etc.)
15
+ - Loading map tiles from a tile server (OpenStreetMap, Mapbox)
16
+ - Embedding iframes from external services (YouTube, Vimeo)
17
+ - Loading external stylesheets or scripts
18
+
19
+ Salesforce enforces Content Security Policy (CSP) headers on all web applications. Any external domain not registered as a CSP Trusted Site will be blocked by the browser, causing images to not load, API calls to fail, or fonts to be missing.
20
+
21
+ **Reference:** [Salesforce CspTrustedSite Object Reference](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_csptrustedsite.htm)
22
+
23
+ ---
24
+
25
+ ## Step 1 — Identify external domains
26
+
27
+ Scan the code for any URLs pointing to external domains. Common patterns:
28
+
29
+ - `fetch("https://api.example.com/...")` — API calls
30
+ - `<img src="https://images.example.com/..." />` — images
31
+ - `<link href="https://fonts.example.com/..." />` — stylesheets
32
+ - `url="https://tiles.example.com/{z}/{x}/{y}.png"` — map tiles
33
+ - `@import url("https://cdn.example.com/...")` — CSS imports
34
+
35
+ Extract the **origin** (scheme + host) from each URL. For example:
36
+ - `https://api.open-meteo.com/v1/forecast?lat=...` → `https://api.open-meteo.com`
37
+ - `https://images.unsplash.com/photo-123?w=800` → `https://images.unsplash.com`
38
+
39
+ ---
40
+
41
+ ## Step 2 — Check existing CSP Trusted Sites
42
+
43
+ Before creating a new file, check if the domain already has a CSP Trusted Site:
44
+
45
+ ```bash
46
+ ls force-app/main/default/cspTrustedSites/
47
+ ```
48
+
49
+ If the domain is already registered, no action is needed.
50
+
51
+ ---
52
+
53
+ ## Step 3 — Determine the CSP directive(s)
54
+
55
+ Map the resource type to the correct CSP `isApplicableTo*Src` fields. Read `implementation/metadata-format.md` for the full reference.
56
+
57
+ Quick reference:
58
+
59
+ | Resource type | CSP directive field(s) to set `true` |
60
+ |--------------|--------------------------------------|
61
+ | Images (img, background-image) | `isApplicableToImgSrc` |
62
+ | API calls (fetch, XMLHttpRequest) | `isApplicableToConnectSrc` |
63
+ | Fonts (.woff, .woff2, .ttf) | `isApplicableToFontSrc` |
64
+ | Stylesheets (CSS) | `isApplicableToStyleSrc` |
65
+ | Video / audio | `isApplicableToMediaSrc` |
66
+ | Iframes | `isApplicableToFrameSrc` |
67
+
68
+ **Always also set `isApplicableToConnectSrc` to `true`** — most resources also require connect-src for preflight/redirect handling.
69
+
70
+ ---
71
+
72
+ ## Step 4 — Create the metadata file
73
+
74
+ Read `implementation/metadata-format.md` and follow the instructions to create the `.cspTrustedSite-meta.xml` file.
75
+
76
+ ---
77
+
78
+ ## Step 5 — Verify
79
+
80
+ 1. Confirm the file is valid XML and matches the expected schema.
81
+ 2. Confirm the file is placed in `force-app/main/default/cspTrustedSites/`.
82
+ 3. Confirm only the necessary `isApplicableTo*Src` fields are set to `true`.
83
+ 4. Run from the web app directory:
84
+
85
+ ```bash
86
+ cd force-app/main/default/webapplications/<appName> && npm run lint && npm run build
87
+ ```
88
+
89
+ - **Lint:** MUST result in 0 errors.
90
+ - **Build:** MUST succeed.
@@ -0,0 +1,281 @@
1
+ # CSP Trusted Site Metadata — Implementation Guide
2
+
3
+ ## File location
4
+
5
+ ```
6
+ force-app/main/default/cspTrustedSites/{Name}.cspTrustedSite-meta.xml
7
+ ```
8
+
9
+ The `cspTrustedSites/` directory must be a direct child of `force-app/main/default/`. Create it if it does not exist.
10
+
11
+ ---
12
+
13
+ ## File naming convention
14
+
15
+ The file name must match the `<fullName>` value inside the XML, with `.cspTrustedSite-meta.xml` appended.
16
+
17
+ | Domain | fullName | File name |
18
+ |--------|----------|-----------|
19
+ | `https://images.unsplash.com` | `Unsplash_Images` | `Unsplash_Images.cspTrustedSite-meta.xml` |
20
+ | `https://api.open-meteo.com` | `Open_Meteo_API` | `Open_Meteo_API.cspTrustedSite-meta.xml` |
21
+ | `https://tile.openstreetmap.org` | `OpenStreetMap_Tiles` | `OpenStreetMap_Tiles.cspTrustedSite-meta.xml` |
22
+
23
+ **Naming rules:**
24
+ - Use PascalCase with underscores separating words (e.g. `Google_Fonts_Static`)
25
+ - Name should describe the provider and resource type (e.g. `Pexels_Videos`, not just `Pexels`)
26
+ - Must be unique across the org
27
+ - Maximum 80 characters
28
+
29
+ ---
30
+
31
+ ## XML template
32
+
33
+ ```xml
34
+ <?xml version="1.0" encoding="UTF-8" ?>
35
+ <CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
36
+ <fullName>{UNIQUE_NAME}</fullName>
37
+ <description>{DESCRIPTION}</description>
38
+ <endpointUrl>{HTTPS_ORIGIN}</endpointUrl>
39
+ <isActive>true</isActive>
40
+ <context>All</context>
41
+ <isApplicableToConnectSrc>{true|false}</isApplicableToConnectSrc>
42
+ <isApplicableToFontSrc>{true|false}</isApplicableToFontSrc>
43
+ <isApplicableToFrameSrc>{true|false}</isApplicableToFrameSrc>
44
+ <isApplicableToImgSrc>{true|false}</isApplicableToImgSrc>
45
+ <isApplicableToMediaSrc>{true|false}</isApplicableToMediaSrc>
46
+ <isApplicableToStyleSrc>{true|false}</isApplicableToStyleSrc>
47
+ </CspTrustedSite>
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Field reference
53
+
54
+ | Field | Required | Description |
55
+ |-------|----------|-------------|
56
+ | `fullName` | Yes | Unique API name. Must match the file name (before `.cspTrustedSite-meta.xml`). |
57
+ | `description` | Yes | Human-readable purpose. Start with "Allow access to..." |
58
+ | `endpointUrl` | Yes | The external origin (scheme + host). Must start with `https://`. No trailing slash. No path. |
59
+ | `isActive` | Yes | Always `true` for new entries. Set `false` to disable without deleting. |
60
+ | `context` | Yes | `All` (applies to all contexts). Other values: `LEX` (Lightning Experience only), `Communities` (Experience Cloud only), `VisualForce`. Use `All` unless there is a specific reason to restrict. |
61
+ | `isApplicableToConnectSrc` | Yes | `true` if the domain is called via `fetch()`, `XMLHttpRequest`, or WebSocket. |
62
+ | `isApplicableToFontSrc` | Yes | `true` if the domain serves font files (`.woff`, `.woff2`, `.ttf`, `.otf`). |
63
+ | `isApplicableToFrameSrc` | Yes | `true` if the domain is loaded in an `<iframe>` or `<object>`. |
64
+ | `isApplicableToImgSrc` | Yes | `true` if the domain serves images (`<img>`, CSS `background-image`, `<svg>`). |
65
+ | `isApplicableToMediaSrc` | Yes | `true` if the domain serves audio or video (`<audio>`, `<video>`). |
66
+ | `isApplicableToStyleSrc` | Yes | `true` if the domain serves CSS stylesheets (`<link rel="stylesheet">`). |
67
+
68
+ **Reference:** [CspTrustedSite — Salesforce Object Reference](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_csptrustedsite.htm)
69
+
70
+ ---
71
+
72
+ ## CSP directive mapping
73
+
74
+ | CSP header directive | Metadata field | What it allows |
75
+ |---------------------|----------------|----------------|
76
+ | `connect-src` | `isApplicableToConnectSrc` | `fetch()`, `XMLHttpRequest`, WebSocket, `EventSource` |
77
+ | `font-src` | `isApplicableToFontSrc` | `@font-face` sources |
78
+ | `frame-src` | `isApplicableToFrameSrc` | `<iframe>`, `<frame>`, `<object>`, `<embed>` |
79
+ | `img-src` | `isApplicableToImgSrc` | `<img>`, `background-image`, `favicon`, `<picture>` |
80
+ | `media-src` | `isApplicableToMediaSrc` | `<audio>`, `<video>`, `<source>`, `<track>` |
81
+ | `style-src` | `isApplicableToStyleSrc` | `<link rel="stylesheet">`, `@import` in CSS |
82
+
83
+ ---
84
+
85
+ ## Common external domains and their directives
86
+
87
+ Use this table as a quick reference when adding new domains:
88
+
89
+ | Domain | connect-src | font-src | frame-src | img-src | media-src | style-src |
90
+ |--------|:-----------:|:--------:|:---------:|:-------:|:---------:|:---------:|
91
+ | `https://images.unsplash.com` | true | false | false | true | false | false |
92
+ | `https://images.pexels.com` | true | false | false | true | false | false |
93
+ | `https://videos.pexels.com` | true | false | false | false | true | false |
94
+ | `https://fonts.googleapis.com` | true | false | false | false | false | true |
95
+ | `https://fonts.gstatic.com` | true | true | false | false | false | false |
96
+ | `https://avatars.githubusercontent.com` | true | false | false | true | false | false |
97
+ | `https://api.open-meteo.com` | true | false | false | false | false | false |
98
+ | `https://nominatim.openstreetmap.org` | true | false | false | false | false | false |
99
+ | `https://tile.openstreetmap.org` | true | false | false | true | false | false |
100
+ | `https://api.mapbox.com` | true | false | false | true | false | false |
101
+ | `https://cdn.jsdelivr.net` | true | false | false | false | false | true |
102
+ | `https://www.youtube.com` | false | false | true | true | false | false |
103
+ | `https://player.vimeo.com` | false | false | true | false | false | false |
104
+ | `https://res.cloudinary.com` | true | false | false | true | false | false |
105
+
106
+ ---
107
+
108
+ ## Complete examples
109
+
110
+ ### Image CDN (Unsplash)
111
+
112
+ ```xml
113
+ <?xml version="1.0" encoding="UTF-8" ?>
114
+ <CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
115
+ <fullName>Unsplash_Images</fullName>
116
+ <description>Allow access to Unsplash image content for static app media</description>
117
+ <endpointUrl>https://images.unsplash.com</endpointUrl>
118
+ <isActive>true</isActive>
119
+ <context>All</context>
120
+ <isApplicableToConnectSrc>true</isApplicableToConnectSrc>
121
+ <isApplicableToFontSrc>false</isApplicableToFontSrc>
122
+ <isApplicableToFrameSrc>false</isApplicableToFrameSrc>
123
+ <isApplicableToImgSrc>true</isApplicableToImgSrc>
124
+ <isApplicableToMediaSrc>false</isApplicableToMediaSrc>
125
+ <isApplicableToStyleSrc>false</isApplicableToStyleSrc>
126
+ </CspTrustedSite>
127
+ ```
128
+
129
+ ### REST API (Open-Meteo weather)
130
+
131
+ ```xml
132
+ <?xml version="1.0" encoding="UTF-8" ?>
133
+ <CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
134
+ <fullName>Open_Meteo_API</fullName>
135
+ <description>Allow access to Open-Meteo weather forecast API</description>
136
+ <endpointUrl>https://api.open-meteo.com</endpointUrl>
137
+ <isActive>true</isActive>
138
+ <context>All</context>
139
+ <isApplicableToConnectSrc>true</isApplicableToConnectSrc>
140
+ <isApplicableToFontSrc>false</isApplicableToFontSrc>
141
+ <isApplicableToFrameSrc>false</isApplicableToFrameSrc>
142
+ <isApplicableToImgSrc>false</isApplicableToImgSrc>
143
+ <isApplicableToMediaSrc>false</isApplicableToMediaSrc>
144
+ <isApplicableToStyleSrc>false</isApplicableToStyleSrc>
145
+ </CspTrustedSite>
146
+ ```
147
+
148
+ ### Font provider (Google Fonts — requires two entries)
149
+
150
+ Google Fonts needs two CSP entries because CSS is served from `fonts.googleapis.com` and font files from `fonts.gstatic.com`:
151
+
152
+ **Entry 1: Stylesheets**
153
+ ```xml
154
+ <?xml version="1.0" encoding="UTF-8" ?>
155
+ <CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
156
+ <fullName>Google_Fonts</fullName>
157
+ <description>Allow access to Google Fonts stylesheets for custom typography</description>
158
+ <endpointUrl>https://fonts.googleapis.com</endpointUrl>
159
+ <isActive>true</isActive>
160
+ <context>All</context>
161
+ <isApplicableToConnectSrc>true</isApplicableToConnectSrc>
162
+ <isApplicableToFontSrc>false</isApplicableToFontSrc>
163
+ <isApplicableToFrameSrc>false</isApplicableToFrameSrc>
164
+ <isApplicableToImgSrc>false</isApplicableToImgSrc>
165
+ <isApplicableToMediaSrc>false</isApplicableToMediaSrc>
166
+ <isApplicableToStyleSrc>true</isApplicableToStyleSrc>
167
+ </CspTrustedSite>
168
+ ```
169
+
170
+ **Entry 2: Font files**
171
+ ```xml
172
+ <?xml version="1.0" encoding="UTF-8" ?>
173
+ <CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
174
+ <fullName>Google_Fonts_Static</fullName>
175
+ <description>Allow access to Google Fonts static files for font loading</description>
176
+ <endpointUrl>https://fonts.gstatic.com</endpointUrl>
177
+ <isActive>true</isActive>
178
+ <context>All</context>
179
+ <isApplicableToConnectSrc>true</isApplicableToConnectSrc>
180
+ <isApplicableToFontSrc>true</isApplicableToFontSrc>
181
+ <isApplicableToFrameSrc>false</isApplicableToFrameSrc>
182
+ <isApplicableToImgSrc>false</isApplicableToImgSrc>
183
+ <isApplicableToMediaSrc>false</isApplicableToMediaSrc>
184
+ <isApplicableToStyleSrc>false</isApplicableToStyleSrc>
185
+ </CspTrustedSite>
186
+ ```
187
+
188
+ ### Map tiles (OpenStreetMap)
189
+
190
+ ```xml
191
+ <?xml version="1.0" encoding="UTF-8" ?>
192
+ <CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
193
+ <fullName>OpenStreetMap_Tiles</fullName>
194
+ <description>Allow access to OpenStreetMap tile images for map rendering</description>
195
+ <endpointUrl>https://tile.openstreetmap.org</endpointUrl>
196
+ <isActive>true</isActive>
197
+ <context>All</context>
198
+ <isApplicableToConnectSrc>true</isApplicableToConnectSrc>
199
+ <isApplicableToFontSrc>false</isApplicableToFontSrc>
200
+ <isApplicableToFrameSrc>false</isApplicableToFrameSrc>
201
+ <isApplicableToImgSrc>true</isApplicableToImgSrc>
202
+ <isApplicableToMediaSrc>false</isApplicableToMediaSrc>
203
+ <isApplicableToStyleSrc>false</isApplicableToStyleSrc>
204
+ </CspTrustedSite>
205
+ ```
206
+
207
+ ### Geocoding API (Nominatim)
208
+
209
+ ```xml
210
+ <?xml version="1.0" encoding="UTF-8" ?>
211
+ <CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
212
+ <fullName>OpenStreetMap_Nominatim</fullName>
213
+ <description>Allow access to OpenStreetMap Nominatim geocoding API</description>
214
+ <endpointUrl>https://nominatim.openstreetmap.org</endpointUrl>
215
+ <isActive>true</isActive>
216
+ <context>All</context>
217
+ <isApplicableToConnectSrc>true</isApplicableToConnectSrc>
218
+ <isApplicableToFontSrc>false</isApplicableToFontSrc>
219
+ <isApplicableToFrameSrc>false</isApplicableToFrameSrc>
220
+ <isApplicableToImgSrc>false</isApplicableToImgSrc>
221
+ <isApplicableToMediaSrc>false</isApplicableToMediaSrc>
222
+ <isApplicableToStyleSrc>false</isApplicableToStyleSrc>
223
+ </CspTrustedSite>
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Endpoint URL rules
229
+
230
+ | Rule | Correct | Incorrect |
231
+ |------|---------|-----------|
232
+ | Must be HTTPS | `https://api.example.com` | `http://api.example.com` |
233
+ | No trailing slash | `https://api.example.com` | `https://api.example.com/` |
234
+ | No path | `https://api.example.com` | `https://api.example.com/v1/forecast` |
235
+ | No port (unless non-standard) | `https://api.example.com` | `https://api.example.com:443` |
236
+ | No wildcards | `https://api.example.com` | `https://*.example.com` |
237
+
238
+ Each subdomain needs its own entry. For example, `fonts.googleapis.com` and `fonts.gstatic.com` are separate entries.
239
+
240
+ ---
241
+
242
+ ## When a service requires multiple domains
243
+
244
+ Some services split resources across multiple subdomains. Create one CSP Trusted Site per domain:
245
+
246
+ | Service | Domains needed |
247
+ |---------|---------------|
248
+ | Google Fonts | `fonts.googleapis.com` (CSS) + `fonts.gstatic.com` (font files) |
249
+ | Mapbox | `api.mapbox.com` (tiles/API) + `events.mapbox.com` (telemetry) |
250
+ | YouTube embed | `www.youtube.com` (iframe) + `i.ytimg.com` (thumbnails) |
251
+ | Cloudflare CDN | `cdnjs.cloudflare.com` (scripts/CSS) |
252
+
253
+ ---
254
+
255
+ ## Troubleshooting CSP violations
256
+
257
+ If the browser console shows a CSP error like:
258
+
259
+ ```
260
+ Refused to load the image 'https://example.com/image.png' because it violates
261
+ the following Content Security Policy directive: "img-src 'self' ..."
262
+ ```
263
+
264
+ 1. Extract the **blocked origin** from the URL (e.g. `https://example.com`).
265
+ 2. Identify the **directive** from the error message (e.g. `img-src` → `isApplicableToImgSrc`).
266
+ 3. Check if a CSP Trusted Site already exists for that origin.
267
+ 4. If not, create one using this skill.
268
+ 5. Deploy the metadata and refresh the page.
269
+
270
+ ---
271
+
272
+ ## Common mistakes
273
+
274
+ | Mistake | Fix |
275
+ |---------|-----|
276
+ | Including a path in `endpointUrl` | Use only the origin: `https://api.example.com` |
277
+ | Adding trailing slash | Remove it: `https://api.example.com` not `https://api.example.com/` |
278
+ | Using HTTP instead of HTTPS | Salesforce requires HTTPS. If the service only supports HTTP, it cannot be added. |
279
+ | Forgetting `isApplicableToConnectSrc` | Most resources also need connect-src for redirects/preflight. Set to `true` by default. |
280
+ | One entry for multiple subdomains | Each subdomain needs its own file (e.g. `api.example.com` and `cdn.example.com` are separate) |
281
+ | File name doesn't match `fullName` | They must be identical (excluding the `.cspTrustedSite-meta.xml` extension) |
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: webapp-add-react-component
2
+ name: webapp-react-add-component
3
3
  description: Creates React components, pages, headers, and footers using shadcn UI and Tailwind CSS. Use when the user asks to create a component, add a widget, build a UI element, add a page, create a new page, add a section (e.g. "add a contacts page"), add a header, add a footer, add a navigation bar, or add anything to the web application.
4
4
  ---
5
5
 
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: webapp-react-data-visualization
3
+ description: Adds data visualization components (charts, stat cards, KPI metrics) to React pages using Recharts. Use when the user asks to add a chart, graph, donut chart, pie chart, bar chart, stat card, KPI metric, dashboard visualization, or analytics component to the web application.
4
+ ---
5
+
6
+ # Data Visualization
7
+
8
+ ## When to Use
9
+
10
+ Use this skill when:
11
+ - Adding charts (donut, pie, bar, line, area) to a dashboard or analytics page
12
+ - Displaying KPI/metric stat cards with trend indicators
13
+ - Building a dashboard layout with mixed chart types and summary cards
14
+
15
+ ---
16
+
17
+ ## Step 1 — Determine the visualization type
18
+
19
+ Identify what the user needs:
20
+
21
+ - **Donut / pie chart** — categorical breakdown (e.g. issue types, status distribution)
22
+ - **Bar chart** — comparison across categories or time periods
23
+ - **Line / area chart** — trends over time
24
+ - **Stat card** — single KPI metric with optional trend indicator
25
+ - **Combined dashboard** — stat cards + one or more charts
26
+
27
+ If unclear, ask:
28
+
29
+ > "What data should the chart display, and would a donut chart, bar chart, line chart, or stat cards work best?"
30
+
31
+ ---
32
+
33
+ ## Step 2 — Install dependencies
34
+
35
+ All chart types in this skill use **recharts**. Install once from the web app directory:
36
+
37
+ ```bash
38
+ npm install recharts
39
+ ```
40
+
41
+ Recharts is built on D3 and provides declarative React components. No additional CSS is needed.
42
+
43
+ ---
44
+
45
+ ## Step 3 — Choose implementation path
46
+
47
+ Read the corresponding guide:
48
+
49
+ - **Bar chart** — use the **`analytics-charts`** skill in `feature-react-chart` (AnalyticsChart component for categorical data).
50
+ - **Line / area chart** — use the **`analytics-charts`** skill in `feature-react-chart` (AnalyticsChart component for time-series data).
51
+ - **Donut / pie chart** — read `implementation/donut-chart.md`
52
+ - **Stat card with trend** — read `implementation/stat-card.md`
53
+ - **Dashboard layout** — read `implementation/dashboard-layout.md`
54
+
55
+ ---
56
+
57
+ ## Verification
58
+
59
+ Before completing:
60
+
61
+ 1. Chart renders with correct data and colors.
62
+ 2. Chart is responsive (resizes with container).
63
+ 3. Legend labels match the data categories.
64
+ 4. Stat card trends display correct positive/negative indicators.
65
+ 5. Run from the web app directory:
66
+
67
+ ```bash
68
+ cd force-app/main/default/webapplications/<appName> && npm run lint && npm run build
69
+ ```
70
+
71
+ - **Lint:** MUST result in 0 errors.
72
+ - **Build:** MUST succeed.
@@ -0,0 +1,189 @@
1
+ # Dashboard Layout — Implementation Guide
2
+
3
+ ## Anatomy of a dashboard page
4
+
5
+ A typical dashboard combines stat cards, charts, and data tables:
6
+
7
+ ```
8
+ ┌────────────────────────────────────────────────────┐
9
+ │ Search / global action bar │
10
+ ├──────────┬──────────┬──────────────────────────────┤
11
+ │ Stat 1 │ Stat 2 │ Stat 3 │
12
+ ├──────────┴──────────┴──────┬───────────────────────┤
13
+ │ │ │
14
+ │ Data table / list │ Donut chart │
15
+ │ (70% width) │ (30% width) │
16
+ │ │ │
17
+ └────────────────────────────┴───────────────────────┘
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Layout implementation
23
+
24
+ ```tsx
25
+ import { PageContainer } from "@/components/layout/PageContainer";
26
+ import { StatCard } from "@/components/StatCard";
27
+ import { DonutChart } from "@/components/DonutChart";
28
+
29
+ export default function Dashboard() {
30
+ return (
31
+ <PageContainer>
32
+ <div className="max-w-7xl mx-auto space-y-6">
33
+ {/* Search bar */}
34
+ <div>{/* global search component */}</div>
35
+
36
+ {/* Main content: 70/30 split */}
37
+ <div className="grid grid-cols-1 lg:grid-cols-[70%_30%] gap-6">
38
+ <div className="space-y-6">
39
+ {/* Stat cards row */}
40
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
41
+ <StatCard title="Metric A" value={42} />
42
+ <StatCard title="Metric B" value={18} />
43
+ <StatCard title="Metric C" value={7} />
44
+ </div>
45
+
46
+ {/* Data table */}
47
+ <div>{/* table component */}</div>
48
+ </div>
49
+
50
+ {/* Sidebar chart */}
51
+ <div>
52
+ <DonutChart title="Distribution" data={chartData} />
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </PageContainer>
57
+ );
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Responsive behavior
64
+
65
+ | Breakpoint | Layout |
66
+ |------------|--------|
67
+ | Mobile (`< 768px`) | Single column, everything stacked |
68
+ | Tablet (`md`) | Stat cards in 3-col grid, rest stacked |
69
+ | Desktop (`lg`) | 70/30 split for table + chart |
70
+
71
+ Key Tailwind classes:
72
+
73
+ ```
74
+ grid grid-cols-1 lg:grid-cols-[70%_30%] gap-6
75
+ grid grid-cols-1 md:grid-cols-3 gap-6
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Loading state
81
+
82
+ Show a full-page loading state while dashboard data is being fetched:
83
+
84
+ ```tsx
85
+ if (loading) {
86
+ return (
87
+ <PageContainer>
88
+ <div className="flex items-center justify-center min-h-[400px]">
89
+ <p className="text-muted-foreground">Loading dashboard…</p>
90
+ </div>
91
+ </PageContainer>
92
+ );
93
+ }
94
+ ```
95
+
96
+ Or use a skeleton layout:
97
+
98
+ ```tsx
99
+ if (loading) {
100
+ return (
101
+ <PageContainer>
102
+ <div className="max-w-7xl mx-auto space-y-6">
103
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
104
+ {[1, 2, 3].map((i) => (
105
+ <div key={i} className="h-28 animate-pulse rounded-xl bg-muted" />
106
+ ))}
107
+ </div>
108
+ <div className="grid grid-cols-1 lg:grid-cols-[70%_30%] gap-6">
109
+ <div className="h-64 animate-pulse rounded-xl bg-muted" />
110
+ <div className="h-64 animate-pulse rounded-xl bg-muted" />
111
+ </div>
112
+ </div>
113
+ </PageContainer>
114
+ );
115
+ }
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Data fetching pattern
121
+
122
+ Use `useEffect` with cancellation for dashboard metrics:
123
+
124
+ ```ts
125
+ const [metrics, setMetrics] = useState<Metrics | null>(null);
126
+ const [loading, setLoading] = useState(true);
127
+
128
+ useEffect(() => {
129
+ let cancelled = false;
130
+ (async () => {
131
+ try {
132
+ setLoading(true);
133
+ const data = await fetchDashboardMetrics();
134
+ if (!cancelled) setMetrics(data);
135
+ } catch (error) {
136
+ if (!cancelled) console.error("Error loading metrics:", error);
137
+ } finally {
138
+ if (!cancelled) setLoading(false);
139
+ }
140
+ })();
141
+ return () => { cancelled = true; };
142
+ }, []);
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Combining multiple data sources
148
+
149
+ Dashboards often aggregate data from several APIs. Load them in parallel:
150
+
151
+ ```ts
152
+ const [metrics, setMetrics] = useState<Metrics | null>(null);
153
+ const [requests, setRequests] = useState<Request[]>([]);
154
+ const [loading, setLoading] = useState(true);
155
+
156
+ useEffect(() => {
157
+ let cancelled = false;
158
+ Promise.all([fetchMetrics(), fetchRecentRequests()])
159
+ .then(([metricsData, requestsData]) => {
160
+ if (!cancelled) {
161
+ setMetrics(metricsData);
162
+ setRequests(requestsData);
163
+ }
164
+ })
165
+ .catch((err) => {
166
+ if (!cancelled) console.error(err);
167
+ })
168
+ .finally(() => {
169
+ if (!cancelled) setLoading(false);
170
+ });
171
+ return () => { cancelled = true; };
172
+ }, []);
173
+ ```
174
+
175
+ ---
176
+
177
+ ## PageContainer wrapper
178
+
179
+ A simple wrapper for consistent page padding:
180
+
181
+ ```tsx
182
+ interface PageContainerProps {
183
+ children: React.ReactNode;
184
+ }
185
+
186
+ export function PageContainer({ children }: PageContainerProps) {
187
+ return <div className="p-6">{children}</div>;
188
+ }
189
+ ```