@salesforce/webapp-template-feature-react-global-search-experimental 1.107.2 → 1.107.4

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 (56) hide show
  1. package/dist/.a4drules/features/feature-react-agentforce-conversation-client-embedded-agent-rule.md +30 -6
  2. package/dist/.a4drules/skills/creating-webapp/SKILL.md +20 -0
  3. package/dist/.a4drules/skills/deploying-to-salesforce/SKILL.md +229 -0
  4. package/dist/.a4drules/skills/exploring-graphql-schema/SKILL.md +7 -18
  5. package/dist/.a4drules/skills/managing-agentforce-conversation-client/SKILL.md +186 -0
  6. package/dist/.a4drules/skills/managing-agentforce-conversation-client/references/constraints.md +134 -0
  7. package/dist/.a4drules/skills/managing-agentforce-conversation-client/references/examples.md +132 -0
  8. package/dist/.a4drules/skills/managing-agentforce-conversation-client/references/style-tokens.md +101 -0
  9. package/dist/.a4drules/skills/{integrating-agentforce-conversation-client/docs → managing-agentforce-conversation-client/references}/troubleshooting.md +9 -12
  10. package/dist/.a4drules/skills/using-graphql/SKILL.md +2 -1
  11. package/dist/.a4drules/webapp-code-quality.md +5 -2
  12. package/dist/.a4drules/webapp-data-access.md +25 -0
  13. package/dist/.a4drules/webapp-deployment.md +32 -0
  14. package/dist/.a4drules/webapp-react-typescript.md +4 -12
  15. package/dist/.a4drules/webapp-react.md +7 -13
  16. package/dist/AGENT.md +3 -0
  17. package/dist/CHANGELOG.md +19 -0
  18. package/dist/eslint.config.js +7 -0
  19. package/dist/force-app/main/default/webapplications/feature-react-global-search/eslint.config.js +2 -0
  20. package/dist/force-app/main/default/webapplications/feature-react-global-search/package.json +3 -9
  21. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/app.tsx +4 -1
  22. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/components/alerts/status-alert.tsx +1 -1
  23. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/recordListGraphQLService.ts +2 -2
  24. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/components/detail/DetailForm.tsx +2 -2
  25. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/components/filters/FiltersPanel.tsx +1 -1
  26. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/components/search/SearchResultCard.tsx +29 -27
  27. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/form.tsx +1 -1
  28. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectInfoBatch.ts +3 -3
  29. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectSearchData.ts +3 -3
  30. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useRecordDetailLayout.ts +1 -1
  31. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useRecordListGraphQL.ts +1 -1
  32. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/pages/GlobalSearch.tsx +16 -10
  33. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/types/filters/filters.ts +2 -2
  34. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/debounce.ts +1 -0
  35. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/sanitizationUtils.ts +1 -0
  36. package/dist/force-app/main/default/webapplications/feature-react-global-search/tsconfig.json +7 -1
  37. package/dist/package-lock.json +9995 -0
  38. package/dist/package.json +7 -7
  39. package/package.json +1 -1
  40. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/recordListGraphQLService.ts +2 -2
  41. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/components/detail/DetailForm.tsx +2 -2
  42. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/components/filters/FiltersPanel.tsx +1 -1
  43. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/components/search/SearchResultCard.tsx +29 -27
  44. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/form.tsx +1 -1
  45. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectInfoBatch.ts +3 -3
  46. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectSearchData.ts +3 -3
  47. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useRecordDetailLayout.ts +1 -1
  48. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useRecordListGraphQL.ts +1 -1
  49. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/pages/GlobalSearch.tsx +16 -10
  50. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/types/filters/filters.ts +2 -2
  51. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/debounce.ts +1 -0
  52. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/sanitizationUtils.ts +1 -0
  53. package/dist/.a4drules/skills/generating-micro-frontend-lwc/SKILL.md +0 -137
  54. package/dist/.a4drules/skills/integrating-agentforce-conversation-client/SKILL.md +0 -92
  55. package/dist/.a4drules/skills/integrating-agentforce-conversation-client/docs/embed-examples.md +0 -116
  56. package/dist/force-app/main/default/webapplications/feature-react-global-search/tsconfig.tsbuildinfo +0 -1
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
3
- "version": "1.107.2",
3
+ "version": "1.107.4",
4
4
  "description": "Base SFDX project template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "publishConfig": {
@@ -10,7 +10,7 @@
10
10
  "sf-project-setup": "node scripts/sf-project-setup.mjs",
11
11
  "build": "echo 'No build required for base-sfdx-project'",
12
12
  "clean": "echo 'No clean required for base-sfdx-project'",
13
- "lint": "eslint **/{aura,lwc}/**/*.js",
13
+ "lint": "eslint --no-error-on-unmatched-pattern **/{aura,lwc}/**/*.js",
14
14
  "test": "npm run test:unit",
15
15
  "test:coverage": "npm run test",
16
16
  "test:unit": "sfdx-lwc-jest -- --passWithNoTests",
@@ -23,13 +23,13 @@
23
23
  "setup": "node scripts/setup-cli.mjs"
24
24
  },
25
25
  "devDependencies": {
26
- "@lwc/eslint-plugin-lwc": "^2.0.0",
26
+ "@lwc/eslint-plugin-lwc": "^3.3.0",
27
27
  "@prettier/plugin-xml": "^3.2.2",
28
- "@salesforce/eslint-config-lwc": "^3.2.3",
29
- "@salesforce/eslint-plugin-aura": "^2.0.0",
30
- "@salesforce/eslint-plugin-lightning": "^1.0.0",
28
+ "@salesforce/eslint-config-lwc": "^4.1.0",
29
+ "@salesforce/eslint-plugin-aura": "^3.0.0",
30
+ "@salesforce/eslint-plugin-lightning": "^2.0.0",
31
31
  "@salesforce/sfdx-lwc-jest": "^7.0.1",
32
- "eslint": "8.57.1",
32
+ "eslint": "^9.39.0",
33
33
  "eslint-plugin-import": "^2.25.4",
34
34
  "eslint-plugin-jest": "^28.8.1",
35
35
  "husky": "^9.1.5",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-feature-react-global-search-experimental",
3
- "version": "1.107.2",
3
+ "version": "1.107.4",
4
4
  "description": "Global search feature for Salesforce objects with filtering and pagination",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",
@@ -104,10 +104,10 @@ function serializeSelectionTree(tree: SelectionTree, indent: string): string {
104
104
  if (key === "Id") {
105
105
  lines.push(`${indent}Id`);
106
106
  } else {
107
- lines.push(`${indent}${key} { value }`);
107
+ lines.push(`${indent}${key} @optional { value }`);
108
108
  }
109
109
  } else {
110
- lines.push(`${indent}${key} {`);
110
+ lines.push(`${indent}${key} @optional {`);
111
111
  lines.push(serializeSelectionTree(val, childIndent));
112
112
  lines.push(`${indent}}`);
113
113
  }
@@ -36,6 +36,8 @@ function FieldCell({
36
36
  record: GraphQLRecordNode;
37
37
  metadata?: ObjectInfoMetadata | null;
38
38
  }) {
39
+ const labelId = useId();
40
+ const valueId = useId();
39
41
  if (!item.isField || item.apiName == null) return null;
40
42
  const label = item.label ?? item.apiName;
41
43
  const hasComponents = item.layoutComponentApiNames && item.layoutComponentApiNames.length > 0;
@@ -51,8 +53,6 @@ function FieldCell({
51
53
  : getDisplayValueForDetailFieldFromNode(record, item.apiName, metadata);
52
54
  const dataType =
53
55
  (hasComponents ? layoutResult?.dataType : undefined) ?? item.dataType ?? undefined;
54
- const labelId = useId();
55
- const valueId = useId();
56
56
  return (
57
57
  <div
58
58
  className="flex flex-col gap-1"
@@ -207,7 +207,7 @@ export default function FiltersPanel({
207
207
  }
208
208
 
209
209
  previousLoadingRef.current = loading;
210
- }, [loading, defaultValues]);
210
+ }, [loading, defaultValues, form]);
211
211
 
212
212
  const handleSuccessDismiss = useCallback(() => {
213
213
  setSubmitSuccess(null);
@@ -23,6 +23,7 @@
23
23
  * />
24
24
  * ```
25
25
  */
26
+ import React from "react";
26
27
  import { useNavigate } from "react-router";
27
28
  import { useMemo, useCallback } from "react";
28
29
  import {
@@ -49,26 +50,24 @@ export default function SearchResultCard({
49
50
  }: SearchResultCardProps) {
50
51
  const navigate = useNavigate();
51
52
 
52
- if (!record || !record.id) {
53
- return null;
54
- }
55
-
56
- if (!columns || !Array.isArray(columns) || columns.length === 0) {
57
- return null;
58
- }
59
-
60
- if (!record.fields || typeof record.fields !== "object") {
61
- return null;
62
- }
53
+ const validColumns = useMemo(
54
+ () => (columns && Array.isArray(columns) && columns.length > 0 ? columns : []),
55
+ [columns],
56
+ );
57
+ const validRecord =
58
+ record?.id && record?.fields && typeof record.fields === "object" ? record : null;
63
59
 
64
60
  const detailPath = useMemo(
65
- () => `/object/${objectApiName?.trim() || OBJECT_API_NAMES[0]}/${record.id}`,
66
- [record.id, objectApiName],
61
+ () =>
62
+ validRecord
63
+ ? `/object/${objectApiName?.trim() || OBJECT_API_NAMES[0]}/${validRecord.id}`
64
+ : "",
65
+ [validRecord, objectApiName],
67
66
  );
68
67
 
69
68
  const handleClick = useCallback(() => {
70
- if (record.id) navigate(detailPath);
71
- }, [record.id, detailPath, navigate]);
69
+ if (validRecord?.id) navigate(detailPath);
70
+ }, [validRecord?.id, detailPath, navigate]);
72
71
 
73
72
  const handleKeyDown = useCallback(
74
73
  (e: React.KeyboardEvent) => {
@@ -82,29 +81,32 @@ export default function SearchResultCard({
82
81
 
83
82
  const primaryField = useMemo(() => {
84
83
  return (
85
- columns.find(
84
+ validColumns.find(
86
85
  (col) =>
87
86
  col &&
88
87
  col.fieldApiName &&
89
88
  (col.fieldApiName.toLowerCase() === "name" ||
90
89
  col.fieldApiName.toLowerCase().includes("name")),
91
90
  ) ||
92
- columns[0] ||
91
+ validColumns[0] ||
93
92
  null
94
93
  );
95
- }, [columns]);
94
+ }, [validColumns]);
96
95
 
97
96
  const primaryValue = useMemo(() => {
98
- return primaryField && primaryField.fieldApiName
99
- ? getNestedFieldValue(record.fields, primaryField.fieldApiName) || "Untitled"
97
+ return primaryField && primaryField.fieldApiName && validRecord?.fields
98
+ ? getNestedFieldValue(validRecord.fields, primaryField.fieldApiName) || "Untitled"
100
99
  : "Untitled";
101
- }, [primaryField, record.fields]);
100
+ }, [primaryField, validRecord]);
102
101
 
103
102
  const secondaryColumns = useMemo(() => {
104
- return columns.filter(
103
+ return validColumns.filter(
105
104
  (col) => col && col.fieldApiName && col.fieldApiName !== primaryField?.fieldApiName,
106
105
  );
107
- }, [columns, primaryField]);
106
+ }, [validColumns, primaryField]);
107
+
108
+ if (!validRecord) return null;
109
+ if (validColumns.length === 0) return null;
108
110
 
109
111
  return (
110
112
  <Card
@@ -114,19 +116,19 @@ export default function SearchResultCard({
114
116
  role="button"
115
117
  tabIndex={0}
116
118
  aria-label={`View details for ${primaryValue}`}
117
- aria-describedby={`result-${record.id}-description`}
119
+ aria-describedby={`result-${validRecord.id}-description`}
118
120
  >
119
121
  <CardHeader>
120
- <CardTitle className="text-lg" id={`result-${record.id}-title`}>
122
+ <CardTitle className="text-lg" id={`result-${validRecord.id}-title`}>
121
123
  {primaryValue}
122
124
  </CardTitle>
123
125
  </CardHeader>
124
126
  <CardContent>
125
- <div id={`result-${record.id}-description`} className="sr-only">
127
+ <div id={`result-${validRecord.id}-description`} className="sr-only">
126
128
  Search result: {primaryValue}
127
129
  </div>
128
130
  <ResultCardFields
129
- record={record}
131
+ record={validRecord}
130
132
  columns={secondaryColumns}
131
133
  excludeFieldApiName={primaryField?.fieldApiName}
132
134
  />
@@ -1,4 +1,4 @@
1
- import { useId } from "react";
1
+ import React, { useId } from "react";
2
2
  import { createFormHookContexts, createFormHook } from "@tanstack/react-form";
3
3
  import {
4
4
  Field,
@@ -35,10 +35,10 @@ export function useObjectInfoBatch(objectApiNames: string[]): UseObjectInfoBatch
35
35
  isCancelled.current = false;
36
36
  const names = objectApiNames.filter(Boolean);
37
37
  if (names.length === 0) {
38
- setState({ objectInfos: [], loading: false, error: null });
38
+ queueMicrotask(() => setState({ objectInfos: [], loading: false, error: null }));
39
39
  return;
40
40
  }
41
- setState((s) => ({ ...s, loading: true, error: null }));
41
+ queueMicrotask(() => setState((s) => ({ ...s, loading: true, error: null })));
42
42
  objectInfoService
43
43
  .getObjectInfoBatch(names.join(","))
44
44
  .then((res) => {
@@ -59,7 +59,7 @@ export function useObjectInfoBatch(objectApiNames: string[]): UseObjectInfoBatch
59
59
  return () => {
60
60
  isCancelled.current = true;
61
61
  };
62
- }, [objectApiNames.join(",")]);
62
+ }, [objectApiNames]);
63
63
 
64
64
  return state;
65
65
  }
@@ -85,14 +85,14 @@ export function useObjectListMetadata(objectApiName: string | null): ObjectListM
85
85
 
86
86
  useEffect(() => {
87
87
  if (!objectApiName) {
88
- setState((s) => ({ ...s, loading: false, error: "Invalid object" }));
88
+ queueMicrotask(() => setState((s) => ({ ...s, loading: false, error: "Invalid object" })));
89
89
  return;
90
90
  }
91
91
 
92
92
  let isCancelled = false;
93
93
 
94
94
  const run = async () => {
95
- setState((s) => ({ ...s, loading: true, error: null }));
95
+ queueMicrotask(() => setState((s) => ({ ...s, loading: true, error: null })));
96
96
  try {
97
97
  const filters = await getSharedFilters(objectApiName!);
98
98
  if (isCancelled) return;
@@ -119,7 +119,7 @@ export function useObjectListMetadata(objectApiName: string | null): ObjectListM
119
119
  loading: false,
120
120
  error: null,
121
121
  });
122
- } catch (err) {
122
+ } catch {
123
123
  if (isCancelled) return;
124
124
  setState((s) => ({
125
125
  ...s,
@@ -110,7 +110,7 @@ export function useRecordDetailLayout({
110
110
  setLayout(layoutData);
111
111
  setRecord(recordData);
112
112
  setObjectMetadata(objectMetadataData);
113
- } catch (err) {
113
+ } catch {
114
114
  if (isCancelled) return;
115
115
  setError("Failed to load record details");
116
116
  } finally {
@@ -115,7 +115,7 @@ export function useRecordListGraphQL(
115
115
  useEffect(() => {
116
116
  if (!objectApiName || columnsLoading || columnsError) return;
117
117
  if (columns.length === 0 && !columnsLoading) return;
118
- fetchRecords();
118
+ queueMicrotask(() => fetchRecords());
119
119
  }, [objectApiName, columns, columnsLoading, columnsError, fetchRecords]);
120
120
 
121
121
  const objectData = data?.uiapi?.query?.[objectApiName];
@@ -46,7 +46,7 @@ export default function GlobalSearch() {
46
46
  if (!query) return "";
47
47
  try {
48
48
  return decodeURIComponent(query);
49
- } catch (e) {
49
+ } catch {
50
50
  return query;
51
51
  }
52
52
  }, [query]);
@@ -56,9 +56,11 @@ export default function GlobalSearch() {
56
56
 
57
57
  // Reset pagination when the URL search query changes so we don't use an old cursor with a new result set
58
58
  useEffect(() => {
59
- setAfterCursor(null);
60
- setPageIndex(0);
61
- setCursorStack([null]);
59
+ queueMicrotask(() => {
60
+ setAfterCursor(null);
61
+ setPageIndex(0);
62
+ setCursorStack([null]);
63
+ });
62
64
  }, [query]);
63
65
 
64
66
  const listMeta = useObjectListMetadata(objectApiName);
@@ -87,10 +89,12 @@ export default function GlobalSearch() {
87
89
  if (resultsLoading) return;
88
90
  const cursor = pageInfo?.endCursor ?? null;
89
91
  if (cursor == null) return;
90
- setCursorStack((prev) => {
91
- const next = [...prev];
92
- next[pageIndex + 1] = cursor;
93
- return next;
92
+ queueMicrotask(() => {
93
+ setCursorStack((prev) => {
94
+ const next = [...prev];
95
+ next[pageIndex + 1] = cursor;
96
+ return next;
97
+ });
94
98
  });
95
99
  }, [resultsLoading, pageInfo?.endCursor, pageIndex]);
96
100
 
@@ -116,8 +120,10 @@ export default function GlobalSearch() {
116
120
 
117
121
  const cursorStackRef = useRef(cursorStack);
118
122
  const pageIndexRef = useRef(pageIndex);
119
- cursorStackRef.current = cursorStack;
120
- pageIndexRef.current = pageIndex;
123
+ useEffect(() => {
124
+ cursorStackRef.current = cursorStack;
125
+ pageIndexRef.current = pageIndex;
126
+ }, [cursorStack, pageIndex]);
121
127
 
122
128
  const canRenderFilters =
123
129
  !listMeta.loading && listMeta.filters !== undefined && listMeta.picklistValues !== undefined;
@@ -108,8 +108,8 @@ export type FilterCriteria = z.infer<typeof FilterCriteriaSchema>;
108
108
  // Export schema for validation
109
109
  export const FilterCriteriaArraySchema = z.array(FilterCriteriaSchema);
110
110
 
111
- // Zod Schema for Filters Response
112
- const FiltersResponseSchema = z.record(z.string(), z.unknown()).and(
111
+ // Zod Schema for Filters Response (used for type inference via z.infer)
112
+ export const FiltersResponseSchema = z.record(z.string(), z.unknown()).and(
113
113
  z.object({
114
114
  filters: FilterArraySchema.optional(),
115
115
  }),
@@ -57,6 +57,7 @@ export function debounce<T extends (...args: any[]) => any>(
57
57
  let lastArgs: Parameters<T> | null = null;
58
58
  function debounced(this: ThisParameterType<T>, ...args: Parameters<T>) {
59
59
  // 2. Context Safety: Capture 'this' to support class methods
60
+ // eslint-disable-next-line @typescript-eslint/no-this-alias -- required for debouncing class methods
60
61
  lastContext = this;
61
62
  lastArgs = args;
62
63
  if (timeoutId) {
@@ -43,6 +43,7 @@ export function sanitizeFilterValue(value: string, maxLength: number = 1000): st
43
43
  sanitized = sanitized.substring(0, maxLength);
44
44
  }
45
45
 
46
+ // eslint-disable-next-line no-control-regex -- intentionally matching control chars for sanitization
46
47
  sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
47
48
 
48
49
  return sanitized;
@@ -1,137 +0,0 @@
1
- ---
2
- name: generating-micro-frontend-lwc
3
- description: Generate a Micro Frontend LWC component for a Web Application.
4
- license: Proprietary. LICENSE.txt has complete terms
5
- metadata:
6
- author: salesforce-experience-platform-emu/lwc-admins
7
- ---
8
-
9
- # Micro Frontend generation (workflow)
10
-
11
- When the user wants a Micro Frontend for a Web Application, follow this workflow.
12
-
13
- ## 1. Install the dependency
14
-
15
- Micro Frontends are generated using the `generate-micro-frontend` CLI command from the `@salesforce/micro-frontends-experimental` package.
16
-
17
- ```bash
18
- npm install @salesforce/micro-frontends-experimental
19
- ```
20
-
21
- The dependency should be added to the project's `package.json` dependencies.
22
-
23
- ## 2. Identify the Web Application
24
-
25
- - Verify the Web Application exists in `force-app/main/default/webapplications/<web-app-name>/`.
26
- - Confirm the Web Application has a `lightningOut` target in its `webapplication-meta.xml` file.
27
- - If no Web Application exists, the user must create one first using the Web Apps template system.
28
-
29
- ## 3. Generate the Micro Frontend component
30
-
31
- Run the `generate-micro-frontend` command with the Web Application name from the root of an SFDX project:
32
-
33
- ```bash
34
- npx generate-micro-frontend <web-app-name>
35
- ```
36
-
37
- This creates:
38
-
39
- - A custom wrapper LWC component in `force-app/main/default/lwc/<webAppName>/`. This is the "Micro Frontend component".
40
- - The static `microFrontendShell` component that handles iframe communication.
41
-
42
- Notes:
43
-
44
- - The command may be added to the project's `package.json` scripts for convenience.
45
- - The Micro Frontend component uses the Web Application name (e.g. `my-web-app/`) in camelCase for its folder and file names (e.g. `myWebApp/myWebApp.js`, `myWebApp/myWebApp.html`).
46
-
47
- ## 4. Customize the Micro Frontend component metadata
48
-
49
- Edit the `<webAppName>.js-meta.xml` file to:
50
-
51
- - Set appropriate `targets` (e.g. `lightning__HomePage`, `lightning__AppPage`, `lightning__RecordPage`, `lightningCommunity__Page`)
52
- - Add `targetConfigs` for page-specific properties
53
- - Optionally update the `masterLabel` and `description`
54
-
55
- Example:
56
-
57
- ```xml
58
- <targetConfigs>
59
- <targetConfig targets="lightning__HomePage">
60
- <property name="height" type="Integer" min="0" max="600" default="300" />
61
- </targetConfig>
62
- </targetConfigs>
63
- ```
64
-
65
- ## 5. Pass properties to the Micro Frontend component
66
-
67
- Edit the `<webAppName>.js` file to customize the `properties` getter:
68
-
69
- ```javascript
70
- @api height;
71
-
72
- @api get properties() {
73
- return {
74
- height: this.height,
75
- // Add any other data your Web Application needs
76
- };
77
- }
78
- ```
79
-
80
- All properties are passed to the embedded Web Application via `postMessage` and can be accessed in the app's code.
81
-
82
- ## 6. Deploy and test
83
-
84
- Deploy the Micro Frontend component using standard SF CLI commands:
85
-
86
- ```bash
87
- sf project deploy start --source-dir force-app/main/default
88
- ```
89
-
90
- Add the component to a page using Lightning App Builder or Experience Builder and verify it loads correctly.
91
-
92
- # Micro Frontend component customization examples
93
-
94
- ## Record page example
95
-
96
- Command to generate a Micro Frontend component for the `my-site` Web Application:
97
-
98
- ```bash
99
- npx generate-micro-frontend my-site
100
- ```
101
-
102
- `mySite.js-meta.xml` file with a `lightning__RecordPage` target:
103
-
104
- ```xml
105
- <?xml version="1.0" encoding="UTF-8"?>
106
- <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
107
- <apiVersion>66.0</apiVersion>
108
- <isExposed>true</isExposed>
109
- <masterLabel>Micro Frontend for "My Site"</masterLabel>
110
- <targets>
111
- <target>lightning__RecordPage</target>
112
- </targets>
113
- <targetConfigs>
114
- <targetConfig targets="lightning__RecordPage">
115
- <property name="mode" type="String" default="dark" />
116
- </targetConfig>
117
- </targetConfigs>
118
- </LightningComponentBundle>
119
- ```
120
-
121
- `mySite.js` file with public properties and `properties` getter:
122
-
123
- ```js
124
- // Micro Frontend component
125
- export default class mySite extends LightningElement {
126
- @api recordId;
127
- @api mode;
128
-
129
- @api get properties() {
130
- // This data is passed to the Micro Frontend
131
- return {
132
- recordId: this.recordId, // automatically populated for lightning__RecordPage target
133
- mode: this.mode, // matches the mode targetConfig
134
- };
135
- }
136
- }
137
- ```
@@ -1,92 +0,0 @@
1
- ---
2
- name: integrating-agentforce-conversation-client
3
- description: Embed an Agentforce conversation client (chat UI) into a React web application using the AgentforceConversationClient component. Use when the user wants to add or integrate a chat widget, chatbot, conversation client, agent chat, or conversational interface in a React app, or when they mention Agentforce chat, Agentforce widget, employee agent, travel agent, HR agent, or embedding a Salesforce agent. ALWAYS use this skill instead of building a chat UI from scratch. NEVER generate custom chat components, use third-party chat libraries, or implement chat with WebSockets or REST APIs. Do NOT use for Lightning Web Components (LWC), non-React frameworks.
4
- ---
5
-
6
- # Embedded Agentforce chat (flat-prop API)
7
-
8
- Use this workflow whenever the user wants add or update Agentforce chat in React.
9
-
10
- ## 1) Get agent id first
11
-
12
- Ask for the Salesforce agent id (18-char id starting with `0Xx`). Do not proceed without it.
13
-
14
- Placeholder convention for all examples in this file:
15
-
16
- `<AgentforceConversationClient agentId="<USER_AGENT_ID_18_CHAR_0Xx...>" />`
17
-
18
- ## 2) Install package
19
-
20
- ```bash
21
- npm install @salesforce/webapp-template-feature-react-agentforce-conversation-client-experimental
22
- ```
23
-
24
- ## 3) Use component in app layout
25
-
26
- Render a single instance in the shared layout (alongside `<Outlet />`).
27
-
28
- ```tsx
29
- import { Outlet } from "react-router";
30
- import { AgentforceConversationClient } from "@salesforce/webapp-template-feature-react-agentforce-conversation-client-experimental";
31
-
32
- export default function AppLayout() {
33
- return (
34
- <>
35
- <Outlet />
36
- <AgentforceConversationClient agentId="<USER_AGENT_ID_18_CHAR_0Xx...>" />
37
- </>
38
- );
39
- }
40
- ```
41
-
42
- ## 4) Flat props only
43
-
44
- This package uses a flat prop API. Use these props directly on the component:
45
-
46
- - `agentId` (required in practice)
47
- - `inline` (`true` = inline, omitted/false = floating)
48
- - `headerEnabled` (defaults to true for floating; actual use case for inline mode)
49
- - `width`, `height` (actual work is when inline mode is true)
50
- - `styleTokens`
51
- - `salesforceOrigin`, `frontdoorUrl`
52
-
53
- ## 5) Inline mode example
54
-
55
- ```tsx
56
- <AgentforceConversationClient
57
- agentId="<USER_AGENT_ID_18_CHAR_0Xx...>"
58
- inline
59
- width={420}
60
- height={600}
61
- />
62
- ```
63
-
64
- ## 6) Theming example
65
-
66
- ```tsx
67
- <AgentforceConversationClient
68
- agentId="<USER_AGENT_ID_18_CHAR_0Xx...>"
69
- styleTokens={{
70
- headerBlockBackground: "#0176d3",
71
- headerBlockTextColor: "#ffffff",
72
- }}
73
- />
74
- ```
75
-
76
- ## 7) Do not do this
77
-
78
- - Do not create custom chat UIs.
79
- - Do not use third-party chat libraries.
80
- - Do not call `embedAgentforceClient` directly from @salesforce/agentforce-conversation-client.
81
-
82
- ## 8) Prerequisites
83
-
84
- Ensure org setup is valid:
85
-
86
- 1. Agent is active and deployed to the correct channel.
87
- 2. `localhost:<PORT>` is trusted for inline frames in local dev.
88
- 3. First-party Salesforce cookie restriction is disabled when required for embedding.
89
-
90
- ## Troubleshooting
91
-
92
- If the chat widget does not appear, fails to authenticate, or behaves unexpectedly, see [troubleshooting.md](docs/troubleshooting.md).