@schandlergarcia/sf-web-components 1.0.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 (195) hide show
  1. package/.a4drules/skills/building-data-visualization/SKILL.md +72 -0
  2. package/.a4drules/skills/building-data-visualization/implementation/bar-line-chart.md +316 -0
  3. package/.a4drules/skills/building-data-visualization/implementation/dashboard-layout.md +189 -0
  4. package/.a4drules/skills/building-data-visualization/implementation/donut-chart.md +181 -0
  5. package/.a4drules/skills/building-data-visualization/implementation/stat-card.md +150 -0
  6. package/.a4drules/skills/building-react-components/SKILL.md +96 -0
  7. package/.a4drules/skills/building-react-components/implementation/component.md +78 -0
  8. package/.a4drules/skills/building-react-components/implementation/header-footer.md +132 -0
  9. package/.a4drules/skills/building-react-components/implementation/page.md +93 -0
  10. package/.a4drules/skills/configuring-csp-trusted-sites/SKILL.md +90 -0
  11. package/.a4drules/skills/configuring-csp-trusted-sites/implementation/metadata-format.md +281 -0
  12. package/.a4drules/skills/configuring-webapp-metadata/SKILL.md +158 -0
  13. package/.a4drules/skills/creating-webapp/SKILL.md +140 -0
  14. package/.a4drules/skills/deploying-to-salesforce/SKILL.md +226 -0
  15. package/.a4drules/skills/implementing-file-upload/SKILL.md +396 -0
  16. package/.a4drules/skills/installing-webapp-features/SKILL.md +210 -0
  17. package/.a4drules/skills/managing-agentforce-conversation-client/SKILL.md +186 -0
  18. package/.a4drules/skills/managing-agentforce-conversation-client/references/constraints.md +134 -0
  19. package/.a4drules/skills/managing-agentforce-conversation-client/references/examples.md +132 -0
  20. package/.a4drules/skills/managing-agentforce-conversation-client/references/style-tokens.md +101 -0
  21. package/.a4drules/skills/managing-agentforce-conversation-client/references/troubleshooting.md +57 -0
  22. package/.a4drules/skills/using-salesforce-data/SKILL.md +363 -0
  23. package/.a4drules/skills/using-salesforce-data/graphql-search.sh +139 -0
  24. package/.a4drules/webapp-data.md +353 -0
  25. package/.a4drules/webapp-ui.md +16 -0
  26. package/README.md +124 -0
  27. package/dist/components/library/cards/ActionList.js +27 -0
  28. package/dist/components/library/cards/ActionList.js.map +1 -0
  29. package/dist/components/library/cards/ActivityCard.js +40 -0
  30. package/dist/components/library/cards/ActivityCard.js.map +1 -0
  31. package/dist/components/library/cards/BaseCard.js +89 -0
  32. package/dist/components/library/cards/BaseCard.js.map +1 -0
  33. package/dist/components/library/cards/CalloutCard.js +28 -0
  34. package/dist/components/library/cards/CalloutCard.js.map +1 -0
  35. package/dist/components/library/cards/ChartCard.js +79 -0
  36. package/dist/components/library/cards/ChartCard.js.map +1 -0
  37. package/dist/components/library/cards/FeedPanel.js +38 -0
  38. package/dist/components/library/cards/FeedPanel.js.map +1 -0
  39. package/dist/components/library/cards/ListCard.js +112 -0
  40. package/dist/components/library/cards/ListCard.js.map +1 -0
  41. package/dist/components/library/cards/MetricCard.js +86 -0
  42. package/dist/components/library/cards/MetricCard.js.map +1 -0
  43. package/dist/components/library/cards/MetricsStrip.js +60 -0
  44. package/dist/components/library/cards/MetricsStrip.js.map +1 -0
  45. package/dist/components/library/cards/SectionCard.js +59 -0
  46. package/dist/components/library/cards/SectionCard.js.map +1 -0
  47. package/dist/components/library/cards/StatusCard.js +137 -0
  48. package/dist/components/library/cards/StatusCard.js.map +1 -0
  49. package/dist/components/library/cards/TableCard.js +244 -0
  50. package/dist/components/library/cards/TableCard.js.map +1 -0
  51. package/dist/components/library/cards/WidgetCard.js +60 -0
  52. package/dist/components/library/cards/WidgetCard.js.map +1 -0
  53. package/dist/components/library/charts/D3Chart.js +74 -0
  54. package/dist/components/library/charts/D3Chart.js.map +1 -0
  55. package/dist/components/library/charts/D3ChartTemplates.js +44 -0
  56. package/dist/components/library/charts/D3ChartTemplates.js.map +1 -0
  57. package/dist/components/library/charts/GeoMap.js +229 -0
  58. package/dist/components/library/charts/GeoMap.js.map +1 -0
  59. package/dist/components/library/chat/ChatBar.js +194 -0
  60. package/dist/components/library/chat/ChatBar.js.map +1 -0
  61. package/dist/components/library/chat/ChatInput.js +67 -0
  62. package/dist/components/library/chat/ChatInput.js.map +1 -0
  63. package/dist/components/library/chat/ChatMessage.js +112 -0
  64. package/dist/components/library/chat/ChatMessage.js.map +1 -0
  65. package/dist/components/library/chat/ChatMessageList.js +50 -0
  66. package/dist/components/library/chat/ChatMessageList.js.map +1 -0
  67. package/dist/components/library/chat/ChatPanel.js +77 -0
  68. package/dist/components/library/chat/ChatPanel.js.map +1 -0
  69. package/dist/components/library/chat/ChatSuggestions.js +22 -0
  70. package/dist/components/library/chat/ChatSuggestions.js.map +1 -0
  71. package/dist/components/library/chat/ChatToolCall.js +87 -0
  72. package/dist/components/library/chat/ChatToolCall.js.map +1 -0
  73. package/dist/components/library/chat/ChatTypingIndicator.js +20 -0
  74. package/dist/components/library/chat/ChatTypingIndicator.js.map +1 -0
  75. package/dist/components/library/chat/ChatWelcome.js +24 -0
  76. package/dist/components/library/chat/ChatWelcome.js.map +1 -0
  77. package/dist/components/library/chat/useChatState.js +77 -0
  78. package/dist/components/library/chat/useChatState.js.map +1 -0
  79. package/dist/components/library/data/DataModeProvider.js +47 -0
  80. package/dist/components/library/data/DataModeProvider.js.map +1 -0
  81. package/dist/components/library/data/DataModeToggle.js +28 -0
  82. package/dist/components/library/data/DataModeToggle.js.map +1 -0
  83. package/dist/components/library/data/filterUtils.js +71 -0
  84. package/dist/components/library/data/filterUtils.js.map +1 -0
  85. package/dist/components/library/data/useDataSource.js +13 -0
  86. package/dist/components/library/data/useDataSource.js.map +1 -0
  87. package/dist/components/library/data/usePageFilters.js +46 -0
  88. package/dist/components/library/data/usePageFilters.js.map +1 -0
  89. package/dist/components/library/filters/FilterBar.js +89 -0
  90. package/dist/components/library/filters/FilterBar.js.map +1 -0
  91. package/dist/components/library/filters/SearchFilter.js +44 -0
  92. package/dist/components/library/filters/SearchFilter.js.map +1 -0
  93. package/dist/components/library/filters/SelectFilter.js +44 -0
  94. package/dist/components/library/filters/SelectFilter.js.map +1 -0
  95. package/dist/components/library/filters/ToggleFilter.js +48 -0
  96. package/dist/components/library/filters/ToggleFilter.js.map +1 -0
  97. package/dist/components/library/forms/FormField.js +256 -0
  98. package/dist/components/library/forms/FormField.js.map +1 -0
  99. package/dist/components/library/forms/FormModal.js +161 -0
  100. package/dist/components/library/forms/FormModal.js.map +1 -0
  101. package/dist/components/library/forms/FormRenderer.js +32 -0
  102. package/dist/components/library/forms/FormRenderer.js.map +1 -0
  103. package/dist/components/library/forms/FormSection.js +49 -0
  104. package/dist/components/library/forms/FormSection.js.map +1 -0
  105. package/dist/components/library/forms/useFormState.js +85 -0
  106. package/dist/components/library/forms/useFormState.js.map +1 -0
  107. package/dist/components/library/heroui/Accordion.js +12 -0
  108. package/dist/components/library/heroui/Accordion.js.map +1 -0
  109. package/dist/components/library/heroui/Alert.js +12 -0
  110. package/dist/components/library/heroui/Alert.js.map +1 -0
  111. package/dist/components/library/heroui/Badge.js +12 -0
  112. package/dist/components/library/heroui/Badge.js.map +1 -0
  113. package/dist/components/library/heroui/Breadcrumbs.js +12 -0
  114. package/dist/components/library/heroui/Breadcrumbs.js.map +1 -0
  115. package/dist/components/library/heroui/Button.js +18 -0
  116. package/dist/components/library/heroui/Button.js.map +1 -0
  117. package/dist/components/library/heroui/Card.js +12 -0
  118. package/dist/components/library/heroui/Card.js.map +1 -0
  119. package/dist/components/library/heroui/Drawer.js +12 -0
  120. package/dist/components/library/heroui/Drawer.js.map +1 -0
  121. package/dist/components/library/heroui/Dropdown.js +12 -0
  122. package/dist/components/library/heroui/Dropdown.js.map +1 -0
  123. package/dist/components/library/heroui/Input.js +10 -0
  124. package/dist/components/library/heroui/Input.js.map +1 -0
  125. package/dist/components/library/heroui/Kbd.js +12 -0
  126. package/dist/components/library/heroui/Kbd.js.map +1 -0
  127. package/dist/components/library/heroui/Meter.js +12 -0
  128. package/dist/components/library/heroui/Meter.js.map +1 -0
  129. package/dist/components/library/heroui/Modal.js +12 -0
  130. package/dist/components/library/heroui/Modal.js.map +1 -0
  131. package/dist/components/library/heroui/Pagination.js +12 -0
  132. package/dist/components/library/heroui/Pagination.js.map +1 -0
  133. package/dist/components/library/heroui/ProgressBar.js +12 -0
  134. package/dist/components/library/heroui/ProgressBar.js.map +1 -0
  135. package/dist/components/library/heroui/ProgressCircle.js +12 -0
  136. package/dist/components/library/heroui/ProgressCircle.js.map +1 -0
  137. package/dist/components/library/heroui/ScrollShadow.js +12 -0
  138. package/dist/components/library/heroui/ScrollShadow.js.map +1 -0
  139. package/dist/components/library/heroui/Select.js +12 -0
  140. package/dist/components/library/heroui/Select.js.map +1 -0
  141. package/dist/components/library/heroui/Separator.js +12 -0
  142. package/dist/components/library/heroui/Separator.js.map +1 -0
  143. package/dist/components/library/heroui/Skeleton.js +12 -0
  144. package/dist/components/library/heroui/Skeleton.js.map +1 -0
  145. package/dist/components/library/heroui/Tabs.js +12 -0
  146. package/dist/components/library/heroui/Tabs.js.map +1 -0
  147. package/dist/components/library/heroui/Toast.js +13 -0
  148. package/dist/components/library/heroui/Toast.js.map +1 -0
  149. package/dist/components/library/heroui/Toggle.js +12 -0
  150. package/dist/components/library/heroui/Toggle.js.map +1 -0
  151. package/dist/components/library/heroui/Tooltip.js +12 -0
  152. package/dist/components/library/heroui/Tooltip.js.map +1 -0
  153. package/dist/components/library/layout/PageContainer.js +9 -0
  154. package/dist/components/library/layout/PageContainer.js.map +1 -0
  155. package/dist/components/library/skeletons/CardSkeleton.js +29 -0
  156. package/dist/components/library/skeletons/CardSkeleton.js.map +1 -0
  157. package/dist/components/library/theme/AppThemeProvider.js +55 -0
  158. package/dist/components/library/theme/AppThemeProvider.js.map +1 -0
  159. package/dist/components/library/theme/tokens.js +55 -0
  160. package/dist/components/library/theme/tokens.js.map +1 -0
  161. package/dist/components/library/ui/Avatar.js +33 -0
  162. package/dist/components/library/ui/Avatar.js.map +1 -0
  163. package/dist/components/library/ui/Button.js +50 -0
  164. package/dist/components/library/ui/Button.js.map +1 -0
  165. package/dist/components/library/ui/Card.js +21 -0
  166. package/dist/components/library/ui/Card.js.map +1 -0
  167. package/dist/components/library/ui/Chip.js +31 -0
  168. package/dist/components/library/ui/Chip.js.map +1 -0
  169. package/dist/components/library/ui/Container.js +42 -0
  170. package/dist/components/library/ui/Container.js.map +1 -0
  171. package/dist/components/library/ui/EmptyState.js +27 -0
  172. package/dist/components/library/ui/EmptyState.js.map +1 -0
  173. package/dist/components/library/ui/Input.js +22 -0
  174. package/dist/components/library/ui/Input.js.map +1 -0
  175. package/dist/components/library/ui/Spinner.js +64 -0
  176. package/dist/components/library/ui/Spinner.js.map +1 -0
  177. package/dist/components/library/ui/Text.js +43 -0
  178. package/dist/components/library/ui/Text.js.map +1 -0
  179. package/dist/components/library/ui/Toggle.js +47 -0
  180. package/dist/components/library/ui/Toggle.js.map +1 -0
  181. package/dist/components/workspace/ComponentRegistry.js +231 -0
  182. package/dist/components/workspace/ComponentRegistry.js.map +1 -0
  183. package/dist/index.js +185 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/lib/utils.js +9 -0
  186. package/dist/lib/utils.js.map +1 -0
  187. package/dist/node_modules/clsx/dist/clsx.js +17 -0
  188. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  189. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +2925 -0
  190. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  191. package/package.json +81 -0
  192. package/scripts/get-graphql-schema.mjs +68 -0
  193. package/scripts/reset-command-center.sh +358 -0
  194. package/scripts/rewrite-e2e-assets.mjs +23 -0
  195. package/scripts/validate-dashboard.sh +290 -0
@@ -0,0 +1,93 @@
1
+ # Implementation — Page
2
+
3
+ ### Rules
4
+
5
+ 1. **Edit the component that owns the UI, never output raw HTML** — When editing the home page or any page content, modify the actual `.tsx` file that renders the target. If the target is inside a child component (e.g. `<GlobalSearchInput />` in `Home.tsx`), edit the child's file (e.g. `GlobalSearchInput.tsx`), not the parent. Do not wrap the component with extra elements in the parent; go into the component and change its JSX. Do not paste or generate raw HTML.
6
+ 2. **`routes.tsx` is the only route registry** — never add routes in `app.tsx` or inside page files.
7
+ 3. **All pages are children of the AppLayout route** — do not create top-level routes that bypass the layout shell.
8
+ 4. **Default export per page** — each page file has exactly one default-export component.
9
+ 5. **Path aliases in all imports** — use `@/pages/...`, `@/components/...`; no deep relative paths.
10
+ 6. **No inline styles** — Tailwind utility classes and design tokens only.
11
+ 7. **Catch-all last** — `path: '*'` (NotFound) must always remain the last child in the layout route.
12
+ 8. **Never modify `appLayout.tsx`** when adding a page — layout changes are a separate concern.
13
+
14
+ ### Step 1 — Create the page file
15
+
16
+ Create `src/pages/MyPage.tsx` with a **default export** and the standard page container:
17
+
18
+ ```tsx
19
+ export default function MyPage() {
20
+ return (
21
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
22
+ <h1 className="text-3xl font-bold text-foreground">My Page</h1>
23
+ <p className="mt-4 text-muted-foreground">Page content goes here.</p>
24
+ </div>
25
+ );
26
+ }
27
+ ```
28
+
29
+ Use shadcn components from `@/components/ui` for UI elements. All styling via Tailwind — no inline `style={{}}`.
30
+
31
+ ### Step 2 — Register the route in `routes.tsx`
32
+
33
+ Open `src/routes.tsx`. Import the page and add it inside the layout route's `children` array:
34
+
35
+ ```tsx
36
+ import MyPage from "@/pages/MyPage";
37
+
38
+ // Inside the layout route's children array (before the catch-all):
39
+ {
40
+ path: "my-page",
41
+ element: <MyPage />,
42
+ handle: { showInNavigation: true, label: "My Page" },
43
+ },
44
+ ```
45
+
46
+ - `path` is a **relative segment** (e.g., `"contacts"`), not an absolute path.
47
+ - Include `handle: { showInNavigation: true, label: "Label" }` only if the page should appear in the navigation menu.
48
+ - The catch-all `path: '*'` must stay **last**.
49
+
50
+ ### Step 3 — Apply an auth guard (if needed)
51
+
52
+ | Access type | Guard | Behavior |
53
+ | ---------------------------------- | ----------------------- | --------------------------------------- |
54
+ | Public | None | Direct child of layout |
55
+ | Authenticated only | `<PrivateRoute>` | Redirects to login if not authenticated |
56
+ | Unauthenticated only (e.g., login) | `<AuthenticationRoute>` | Redirects away if already authenticated |
57
+
58
+ Example — private page:
59
+
60
+ ```tsx
61
+ import { PrivateRoute } from "@/components/auth/private-route";
62
+
63
+ {
64
+ path: "settings",
65
+ element: <PrivateRoute><SettingsPage /></PrivateRoute>,
66
+ handle: { showInNavigation: true, label: "Settings" },
67
+ },
68
+ ```
69
+
70
+ Use `ROUTES.*` constants from `@/utils/authenticationConfig` for auth-related paths — do not hardcode `/login`, `/profile`, etc.
71
+
72
+ ### File Conventions — Page
73
+
74
+ | Concern | Location |
75
+ | ----------------- | ------------------------------------------------------ |
76
+ | Page component | `src/pages/<PageName>.tsx` (default export) |
77
+ | Route definition | `src/routes.tsx` only |
78
+ | Layout shell | `src/appLayout.tsx` — do not modify for page additions |
79
+ | Auth config paths | `ROUTES.*` from `@/utils/authenticationConfig` |
80
+
81
+ ### State and Data
82
+
83
+ - **Local state:** `useState`, `useReducer`, `useRef` inside the page component
84
+ - **Shared or complex state:** extract to `src/hooks/` with a `use` prefix (e.g., `useContacts`)
85
+ - **Data fetching:** prefer GraphQL (`executeGraphQL`) or REST utilities in `src/api/`; place shared data logic in `src/hooks/`
86
+ - **Auth context:** `useAuth()` from `@/context/AuthContext` when current user is needed — only valid under `AuthProvider`
87
+
88
+ ### Confirm — Page
89
+
90
+ - The page renders inside the app shell (header/nav visible)
91
+ - If `showInNavigation: true`, the link appears in the navigation menu
92
+ - No TypeScript errors; no broken imports; no missing exports
93
+ - Imports use path aliases (`@/`, not deep relative paths)
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: configuring-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) |
@@ -0,0 +1,158 @@
1
+ ---
2
+ name: configuring-webapp-metadata
3
+ description: Use this skill when configuring web application metadata structure, webapplication.json, or bundle organization. Covers WebApplication bundle layout, meta XML, build output directory, and webapplication.json settings.
4
+ ---
5
+
6
+ # WebApplication Requirements
7
+
8
+ ## Bundle Rules
9
+ - A WebApplication bundle must live under `webapplications/<AppName>/`
10
+ - The bundle must contain `<AppName>.webapplication-meta.xml`
11
+ - The metadata filename must exactly match the folder name
12
+ - A build output directory must exist and contain at least one file
13
+ - Default build output directory: `dist/`
14
+ - If `webapplication.json.outputDir` is set, it overrides `dist/`
15
+
16
+ Valid example:
17
+ ```text
18
+ webapplications/
19
+ MyApp/
20
+ MyApp.webapplication-meta.xml
21
+ webapplication.json
22
+ dist/
23
+ index.html
24
+ ```
25
+
26
+ ## Metadata XML
27
+ Required fields:
28
+ - `masterLabel`
29
+ - `version` (max 20 chars)
30
+ - `isActive` (boolean)
31
+
32
+ Optional fields:
33
+ - `description` (max 255 chars)
34
+
35
+ ## webapplication.json
36
+ `webapplication.json` is optional.
37
+
38
+ Allowed top-level keys only:
39
+ - `outputDir`
40
+ - `routing`
41
+ - `headers`
42
+
43
+ ### File Constraints
44
+ - Must be valid UTF-8 JSON
45
+ - Max size: 100 KB
46
+ - Root must be a non-empty object
47
+ - Never allow `{}`, arrays, or primitives as the root
48
+
49
+ ### Path Safety
50
+ Applies to:
51
+ - `outputDir`
52
+ - `routing.fallback`
53
+
54
+ Reject:
55
+ - backslashes
56
+ - leading `/` or `\`
57
+ - `..` segments
58
+ - null or control characters
59
+ - globs: `*`, `?`, `**`
60
+ - `%`
61
+
62
+ All resolved paths must stay within the application bundle.
63
+
64
+ ### outputDir
65
+ - Must be a non-empty string
66
+ - Must reference a subdirectory only
67
+ - Reject `.` and `./`
68
+ - The directory must exist in the bundle
69
+ - The directory must contain at least one file
70
+
71
+ ### routing
72
+ - If present, must be a non-empty object
73
+ - Allowed keys only:
74
+ - `rewrites`
75
+ - `redirects`
76
+ - `fallback`
77
+ - `trailingSlash`
78
+ - `fileBasedRouting`
79
+
80
+ #### routing.trailingSlash
81
+ - Must be one of: `"always"`, `"never"`, `"auto"`
82
+
83
+ #### routing.fileBasedRouting
84
+ - Must be a boolean
85
+
86
+ #### routing.fallback
87
+ - Must be a non-empty string
88
+ - Must satisfy Path Safety rules
89
+ - Target file must exist
90
+
91
+ #### routing.rewrites
92
+ - Must be a non-empty array
93
+ - Each item must be a non-empty object
94
+ - Allowed keys: `route`, `rewrite`
95
+ - `rewrite` must be a non-empty string
96
+ - `route`, if present, must be a non-empty string
97
+
98
+ Example:
99
+ ```json
100
+ {
101
+ "routing": {
102
+ "rewrites": [
103
+ { "route": "/app/:path*", "rewrite": "/index.html" }
104
+ ]
105
+ }
106
+ }
107
+ ```
108
+
109
+ #### routing.redirects
110
+ - Must be a non-empty array
111
+ - Each item must be a non-empty object
112
+ - Allowed keys: `route`, `redirect`, `statusCode`
113
+ - `redirect` must be a non-empty string
114
+ - `route`, if present, must be a non-empty string
115
+ - `statusCode`, if present, must be one of: `301`, `302`, `307`, `308`
116
+
117
+ Example:
118
+ ```json
119
+ {
120
+ "routing": {
121
+ "redirects": [
122
+ { "route": "/old-page", "redirect": "/new-page", "statusCode": 301 }
123
+ ]
124
+ }
125
+ }
126
+ ```
127
+
128
+ ### headers
129
+ - If present, must be a non-empty array
130
+ - Each item must be a non-empty object
131
+ - Allowed keys: `source`, `headers`
132
+ - `headers` must be a non-empty array
133
+
134
+ Each header entry must contain:
135
+ - `key`: non-empty string
136
+ - `value`: non-empty string
137
+
138
+ Example:
139
+ ```json
140
+ {
141
+ "headers": [
142
+ {
143
+ "source": "/assets/**",
144
+ "headers": [
145
+ { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
146
+ ]
147
+ }
148
+ ]
149
+ }
150
+ ```
151
+
152
+ ## Never Suggest
153
+ - `{}` as the JSON root
154
+ - `"routing": {}`
155
+ - empty arrays
156
+ - empty array items such as `[{}]`
157
+ - `"outputDir": "."`
158
+ - `"outputDir": "./"`