@salesforce/webapp-template-feature-react-global-search-experimental 1.75.1 → 1.76.1

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 (23) hide show
  1. package/README.md +169 -322
  2. package/dist/.a4drules/skills/webapp-add-react-component/SKILL.md +78 -0
  3. package/dist/.a4drules/skills/webapp-add-react-component/implementation/component.md +78 -0
  4. package/dist/.a4drules/skills/webapp-add-react-component/implementation/header-footer.md +124 -0
  5. package/dist/.a4drules/skills/webapp-add-react-component/implementation/page.md +92 -0
  6. package/dist/CHANGELOG.md +19 -0
  7. package/dist/force-app/main/default/webapplications/feature-react-global-search/package.json +3 -3
  8. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectDetailService.ts +3 -26
  9. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoGraphQLService.ts +0 -9
  10. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoService.ts +5 -104
  11. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectSearchData.ts +7 -228
  12. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useRecordDetailLayout.ts +1 -20
  13. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/apiUtils.ts +3 -69
  14. package/dist/force-app/main/default/webapplications/feature-react-global-search/src/index.ts +125 -0
  15. package/dist/package.json +1 -1
  16. package/package.json +3 -3
  17. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectDetailService.ts +3 -26
  18. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoGraphQLService.ts +0 -9
  19. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoService.ts +5 -104
  20. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectSearchData.ts +7 -228
  21. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useRecordDetailLayout.ts +1 -20
  22. package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/apiUtils.ts +3 -69
  23. package/src/force-app/main/default/webapplications/feature-react-global-search/src/index.ts +125 -0
package/README.md CHANGED
@@ -1,403 +1,250 @@
1
- # Feature: Global Search
1
+ # Feature: React Global Search (Single Object)
2
2
 
3
- This feature provides API services and utilities for implementing global search functionality in Salesforce Web Applications. It includes:
3
+ This package is a **reference implementation** for adding global search (keyword search, filters, results list, and record detail) to a Salesforce web app. It provides **API services, hooks, types/schemas, and utilities** so you can **build your own UI** and import the data layer from the package—or optionally **copy** the reference UI from the package and adapt it.
4
4
 
5
- ## Consuming as a dependency
5
+ **Intended use (vibing model):**
6
6
 
7
- Apps (e.g. `appreactsampleb2e`) can depend on this package and import **API, hooks, types, and utilities** (no UI). The package exposes a built ESM bundle.
7
+ - **Do not copy the feature source** (api, hooks, types, utils).
8
+ - **Build your own UI** (recommended), or **copy** the reference pages/components from the package and adapt.
9
+ - **Import** data-layer pieces from the package: services, hooks, types, utilities.
8
10
 
9
- 1. Add the dependency: `"@salesforce/webapp-template-feature-react-global-search-experimental": "^1.71.3"`.
10
- 2. **Before building or running the app**, build the library bundle from this package:
11
- ```bash
12
- cd packages/template/feature/feature-react-global-search && npm run build:lib
13
- ```
14
- This produces `dist/index.js`, which is the package entry point.
15
- 3. Import what you need (e.g. `useObjectListMetadata`, `parseFilterValue`, `sanitizeFilterValue`, `FilterCriteria`, `objectInfoService`). Use your own UI for filters and list pages.
11
+ UI components and feature constants are deliberately **not exported** from the public API.
16
12
 
17
- - **API Service Layer** - Type-safe API client for Salesforce search endpoints
18
- - **Type Definitions** - Complete TypeScript types with Zod schema validation
19
- - **Utility Functions** - Helper functions for debouncing and data extraction
20
- - **Custom Hooks** - React hooks for managing search data and API calls
21
-
22
- ## Features
23
-
24
- - **Multi-Object Search API** - Search across multiple Salesforce objects simultaneously
25
- - **Object Metadata API** - Fetch object information, column definitions, and filter definitions
26
- - **Picklist Values API** - Retrieve picklist values for dynamic filter options
27
- - **Type Safety** - Full TypeScript support with Zod schema validation
28
- - **Race Condition Protection** - AbortSignal support to cancel stale requests
29
- - **Error Handling** - Consistent error handling across all API calls
30
-
31
- ## Architecture
32
-
33
- ### Data Layer (GraphQL vs REST)
34
-
35
- - **GraphQL (uiapi)**
36
- - **Object metadata** – `getObjectInfoBatch` and `getPicklistValues` are implemented via `getObjectInfosGraphQL` (uiapi.objectInfos). Responses are adapted to the existing REST-shaped types.
37
- - **Records** – A single query path is used for both list and detail: `getRecordsGraphQL` with optional `recordId` for single-record fetch. `getRecordByIdGraphQL` is a thin wrapper that calls `getRecordsGraphQL` with `recordId` and `first: 1`.
38
-
39
- - **REST (uiApiClient)**
40
- - **Layout** – Detail view layout comes from `GET /layout/{object}` (used by `objectDetailService.getRecordDetail`).
41
- - **List/search** – Columns are derived from `getObjectListFilters` (e.g. `filters.map(f => ({ fieldApiName: f.targetFieldPath, label: f.label }))`); filters and results from `getObjectListFilters` / `searchResults` (keyword search) or from `getRecordsGraphQL` for list views.
42
-
43
- The detail page uses `useRecordDetailLayout` → `objectDetailService.getRecordDetail` (layout + object metadata + single record by Id). List/search uses `useRecordListGraphQL` (columns derived from Filters API, records from GraphQL) or `useObjectSearchData` (REST search).
44
-
45
- ### API Service Layer
46
-
47
- The feature includes a state-agnostic service layer (`objectInfoService`, `objectDetailService`, record GraphQL helpers) that provides pure functions for API interactions. All API calls:
48
-
49
- - Use `fetchAndValidate` utility for consistent error handling
50
- - Support `AbortSignal` for request cancellation
51
- - Validate responses with Zod schemas
52
- - Provide type-safe return values
53
-
54
- ### API Flow Diagram
55
-
56
- The following flowchart explains the API architecture and how each API is consumed:
57
-
58
- ```mermaid
59
- flowchart TD
60
- Start([Application Initialization]) --> BatchAPI[getObjectInfoBatch API]
61
- BatchAPI -->|Comma-separated Object Names| ObjectMetadata[Object Metadata]
62
- ObjectMetadata --> LoadObjectData[Load Data for Each Object]
13
+ ---
63
14
 
64
- %% Parallel API Calls for Each Object
65
- LoadObjectData --> ParallelAPIs{Parallel API Calls per Object}
15
+ ## What you get (public API)
16
+
17
+ Import from the package root:
18
+
19
+ ```ts
20
+ import {
21
+ objectInfoService,
22
+ objectDetailService,
23
+ useObjectListMetadata,
24
+ useRecordListGraphQL,
25
+ useRecordDetailLayout,
26
+ useObjectColumns,
27
+ useObjectFilters,
28
+ useObjectInfoBatch,
29
+ type FilterCriteria,
30
+ parseFilterValue,
31
+ sanitizeFilterValue,
32
+ } from "@salesforce/webapp-template-feature-react-global-search-experimental";
33
+ ```
66
34
 
67
- %% Filters API (columns derived from filters via targetFieldPath)
68
- ParallelAPIs --> FiltersAPI[getObjectListFilters API]
69
- FiltersAPI -->|Object Name| Filters[Filter Definitions]
70
- Filters --> Columns[Column Definitions]
35
+ ### API services
71
36
 
72
- %% Search Results API
73
- ParallelAPIs --> SearchAPI[searchResults API]
74
- SearchAPI -->|Query + Object Name + Pagination| SearchRecords[Search Records]
37
+ - **`objectInfoService`**
38
+ - `getObjectInfoBatch(objectApiNames)` (GraphQL uiapi.objectInfos)
39
+ - `getPicklistValues(objectApiName, fieldName, recordTypeId?)` (GraphQL uiapi.objectInfos)
40
+ - `getObjectListFilters(objectApiName)` (REST search-info filters)
41
+ - **`objectDetailService`**
42
+ - `getLayout(objectApiName, recordTypeId?)` (REST layout)
43
+ - `getRecordDetail(objectApiName, recordId, recordTypeId?)` (layout + object info + record)
44
+ - **GraphQL record helpers**
45
+ - `getRecordsGraphQL(...)` (list query, `first + after`)
46
+ - `getRecordByIdGraphQL(...)` (single record by Id)
47
+ - `buildWhereFromCriteria(...)`, `buildOrderByFromSort(...)`, `buildGetRecordsQuery(...)`
75
48
 
76
- %% Picklist Values Flow
77
- Filters --> PicklistCheck{Has Picklist Fields?}
78
- PicklistCheck -->|Yes| PicklistAPI[getPicklistValues API]
79
- PicklistAPI -->|Object Name + Field Name| PicklistValues[Picklist Options]
80
- PicklistCheck -->|No| SkipPicklist[Skip Picklist Fetch]
49
+ ### Hooks
81
50
 
82
- %% User Interactions - API Calls
83
- UserQuery([User Search Query]) --> Debounce[Debounce 500ms]
84
- Debounce --> SearchAPI
51
+ - **List metadata**: `useObjectListMetadata(objectApiName)` filters, columns (derived from filters), picklistValues.
52
+ - **List columns (legacy)**: `useObjectColumns(objectApiName)` columns, columnsLoading, columnsError.
53
+ - **List filters**: `useObjectFilters(objectApiName)` — filtersData (filters + picklist values per object).
54
+ - **List records**: `useRecordListGraphQL({ objectApiName, first, after, filters, sortBy, searchQuery, columns? })` — GraphQL list with cursor pagination.
55
+ - **Detail**: `useRecordDetailLayout({ objectApiName, recordId, recordTypeId?, initialData? })` — layout (REST) + object metadata + record (GraphQL).
56
+ - **Batch object info**: `useObjectInfoBatch(objectApiNames)` — objectInfos, loading, error.
85
57
 
86
- FilterChange([Filter Change]) --> SearchAPI
87
- PageChange([Pagination Change]) --> SearchAPI
88
- PageSizeChange([Page Size Change]) --> SearchAPI
58
+ ### Types, schemas, utils
89
59
 
90
- %% Error Handling
91
- BatchAPI -.->|Error| ErrorHandler[Error Handler]
92
- FiltersAPI -.->|Error| ErrorHandler
93
- SearchAPI -.->|Error| ErrorHandler
94
- PicklistAPI -.->|Error| ErrorHandler
95
- ErrorHandler --> HandleError[Handle Error Gracefully]
60
+ - **Types**: `Filter`, `FilterCriteria`, `Column`, `SearchResultRecord`, `LayoutResponse`, `ObjectInfoResult`, …
61
+ - **Zod schemas**: `LayoutResponseSchema`, `ObjectInfoResultSchema`, `SearchResultsResponseSchema`, …
62
+ - **Utilities**: `parseFilterValue`, `sanitizeFilterValue`, `createFiltersKey`, `getGraphQLNodeValue`, `graphQLNodeToSearchResultRecordData`, `fetchAndValidate`, `safeEncodePath`, `getNestedFieldValue`, `getRecordDisplayName`, layout transform and form helpers, etc.
96
63
 
97
- %% Race Condition Protection
98
- SearchAPI -.->|AbortSignal| CancelOld[Cancel Old Request]
99
- CancelOld --> PreventStale[Prevent Stale Data]
64
+ The definitive type surface is in `index.d.ts` in this package.
100
65
 
101
- style BatchAPI fill:#e1f5ff
102
- style FiltersAPI fill:#e1f5ff
103
- style SearchAPI fill:#e1f5ff
104
- style PicklistAPI fill:#e1f5ff
105
- style ErrorHandler fill:#ffe1e1
106
- style CancelOld fill:#fff4e1
107
- ```
66
+ ---
108
67
 
109
- ## API Reference
68
+ ## Features
110
69
 
111
- ### 1. `getObjectInfoBatch(objectApiNames, signal?)`
70
+ - **Single object browse & search**: keyword search and “browse all” mode (`browse__all`).
71
+ - **Filterable list**: filter definitions + picklist values, criteria → GraphQL where clause.
72
+ - **Sortable list**: build orderBy from sort selection.
73
+ - **Cursor pagination (forward-only)** with **previous-page simulation via cursor stack**.
74
+ - **Record detail**: layout-driven field selection (layout REST + record GraphQL).
75
+ - **Type safety**: Zod validation + exported TypeScript types.
112
76
 
113
- **Purpose**: Fetches object metadata for multiple Salesforce objects in a single request.
77
+ ---
114
78
 
115
- **When to Use**:
79
+ ## Architecture
116
80
 
117
- - Initial page load to get object labels and basic metadata
118
- - Needed to create tabs for each object type
119
- - Provides object plural labels for tab display
81
+ ### Data layer (GraphQL vs REST)
120
82
 
121
- **Parameters**:
83
+ - **GraphQL (uiapi)**
84
+ Object metadata via `getObjectInfoBatch` and `getPicklistValues`. Records via `getRecordsGraphQL` and `getRecordByIdGraphQL`.
85
+ - **REST (uiApiClient)**
86
+ Layout from `GET /layout/{object}`. Search filters from `GET /search-info/{object}/filters`. List/search results are **GraphQL-only** via `useRecordListGraphQL` (no REST keyword search in the public API).
122
87
 
123
- - `objectApiNames`: Comma-separated list (e.g., `"Account,Property__c"`)
124
- - `signal?`: Optional AbortSignal for cancellation
88
+ In the reference implementation:
125
89
 
126
- **Returns**: `ObjectInfoBatchResponse` containing metadata for all requested objects
90
+ - **Detail page**: `useRecordDetailLayout` `objectDetailService.getRecordDetail` (layout REST + record GraphQL).
91
+ - **List page**: `useObjectListMetadata` (filters + columns + picklists) + `useRecordListGraphQL` (records).
127
92
 
128
- **Example**:
93
+ ### API flow (conceptual)
129
94
 
130
- ```typescript
131
- const objectInfo = await getObjectInfoBatch("Account,Property__c");
132
- // Returns: { objects: [{ apiName: 'Account', label: 'Accounts', ... }, ...] }
133
- ```
95
+ - **Initialization**: `getObjectInfoBatch` for object metadata; per object: `getObjectListFilters` (columns derived from filters), then `getPicklistValues` for picklist filters.
96
+ - **List/search**: `useRecordListGraphQL` (GraphQL UI API with cursor pagination; use a cursor stack for “Previous”).
97
+ - **Detail**: `useRecordDetailLayout` / `objectDetailService.getRecordDetail`.
134
98
 
135
- **API Endpoint**: `GET /object-info/batch/{objectApiNames}`
99
+ All API calls use `fetchAndValidate` and validate with Zod.
136
100
 
137
101
  ---
138
102
 
139
- ### 2. `getObjectListFilters(objectApiName, signal?)`
103
+ ## Install
140
104
 
141
- **Purpose**: Fetches filter definitions for a specific object.
142
-
143
- **When to Use**:
105
+ ```bash
106
+ npm install @salesforce/webapp-template-feature-react-global-search-experimental
107
+ ```
144
108
 
145
- - When loading data for an object to determine available filters
146
- - Provides filter metadata (field types, operators, default values, affordances)
147
- - Used to understand what filters can be applied to search results
109
+ ### Required peer environment
148
110
 
149
- **Parameters**:
111
+ - `@salesforce/webapp-experimental` (API proxy, auth, etc.)
112
+ - React and React Router
150
113
 
151
- - `objectApiName`: The object API name (e.g., `"Account"`)
152
- - `signal?`: Optional AbortSignal for cancellation
114
+ If you are not using that stack, you can still reuse **pure** utilities and types; the API services and hooks need an equivalent UI API transport.
153
115
 
154
- **Returns**: Array of `Filter` objects with filter definitions
116
+ ---
155
117
 
156
- **Example**:
118
+ ## Configuration (constants)
157
119
 
158
- ```typescript
159
- const filters = await getObjectListFilters("Account");
160
- // Returns: [{ targetFieldPath: 'Name', operator: 'contains', ... }, ...]
161
- ```
120
+ Constants are **not exported**. When you build your app (or copy reference UI), define them in the app:
162
121
 
163
- **API Endpoint**: `GET /search-info/{objectApiName}/filters`
122
+ - **OBJECT_API_NAMES** – Array of object API names to search. First element is the primary object (e.g. `OBJECT_API_NAMES[0]`). Default to `["Account"]` if no object is specified.
123
+ - **DEFAULT_PAGE_SIZE** – e.g. `20`.
124
+ - **DEFAULT_DETAIL_PAGE_TITLE** – e.g. `"Untitled"`.
164
125
 
165
- Column definitions for list/result UI are derived from the filters response: `filters.map(f => ({ fieldApiName: f.targetFieldPath, label: f.label, searchable: true, sortable: true }))`. The `useObjectColumns` hook calls `getObjectListFilters` and derives columns internally.
126
+ **Determining the search object:** Decide from the user’s request or app context. If the user does not specify an object, use **Account**. If your repo provides a script (e.g. `scripts/parse-feature-install-context.cjs`), you can use it to parse the user prompt and get an object API name or label hint; otherwise set OBJECT_API_NAMES in the app’s constants accordingly.
166
127
 
167
128
  ---
168
129
 
169
- ### 3. `getPicklistValues(objectApiName, fieldName, recordTypeId?, signal?)`
170
-
171
- **Purpose**: Fetches available picklist values for a specific field.
172
-
173
- **When to Use**:
130
+ ## Vibing integration (recommended: build your own UI)
174
131
 
175
- - When a filter has `affordance: 'select'` (indicating a picklist field)
176
- - Needed to get the list of valid values for a picklist filter
177
- - Called for each picklist field in the filters array returned by `getObjectListFilters`
132
+ 1. **Routes**
133
+ Search results: e.g. `/global-search/:query` (use `browse__all` for “show all”). Record detail: e.g. `/object/:objectApiName/:recordId`.
178
134
 
179
- **Parameters**:
135
+ 2. **Search entry**
136
+ On submit, navigate to `/global-search/${encodeURIComponent(query)}` or `/global-search/browse__all`.
180
137
 
181
- - `objectApiName`: The object API name (e.g., `"Account"`)
182
- - `fieldName`: The field API name (e.g., `"Type"`)
183
- - `recordTypeId?`: Optional record type ID (defaults to master record type)
184
- - `signal?`: Optional AbortSignal for cancellation
138
+ 3. **Results page (list)**
139
+ Use `useObjectListMetadata(objectApiName)` for filters/columns/picklists and `useRecordListGraphQL({ objectApiName, first, after, filters, sortBy, searchQuery, columns })` for records. Implement a **cursor stack** for “Previous” (see reference `GlobalSearch` page).
185
140
 
186
- **Returns**: Array of `PicklistValue` objects with label and value
141
+ 4. **Detail page**
142
+ Use `useRecordDetailLayout({ objectApiName, recordId })` for `layout`, `record`, `objectMetadata`; render with `layoutTransformUtils` and `graphQLNodeFieldUtils` (or equivalent).
187
143
 
188
- **Example**:
189
-
190
- ```typescript
191
- const picklistValues = await getPicklistValues("Account", "Type");
192
- // Returns: [{ label: 'Customer', value: 'Customer' }, { label: 'Partner', value: 'Partner' }, ...]
193
- ```
194
-
195
- **API Endpoint**: `GET /object-info/{objectApiName}/picklist-values/{recordTypeId}/{fieldName}`
144
+ 5. **Search input placement**
145
+ Put the search entry on the **Home page** (e.g. as a card) or in the app **Header**—**not** in a root layout that wraps every page.
196
146
 
197
147
  ---
198
148
 
199
- ### 4. `searchResults(query, objectApiName, params?, signal?)`
149
+ ## Optional: copy reference UI from the package
200
150
 
201
- **Purpose**: Performs keyword search on a specific object and returns matching records.
151
+ The package **does not export** UI components. If you prefer to copy the reference UI instead of building your own:
202
152
 
203
- **When to Use**:
153
+ - **What to import (do not copy):** API (`objectInfoService`, `objectDetailService`, …), hooks (`useObjectListMetadata`, `useRecordListGraphQL`, `useRecordDetailLayout`, …), types, Zod schemas, utilities.
154
+ - **What to copy into the app:** A single **constants** file (set `OBJECT_API_NAMES` to your chosen object(s)) and the **pages** and **components** from the package’s reference implementation.
204
155
 
205
- - When executing a search query for a specific object
206
- - When filters are applied (query should include filter criteria)
207
- - When pagination changes (use `pageToken` parameter)
208
- - When page size changes (use `pageSize` parameter)
209
- - Returns the actual search results with field values
156
+ **Path convention (reference implementation in this repo):**
210
157
 
211
- **Parameters**:
158
+ All paths below are relative to the **package root** of this feature. The reference implementation lives under:
212
159
 
213
- - `query`: The search query string
214
- - `objectApiName`: The object API name to search (e.g., `"Account"`)
215
- - `params?`: Optional pagination parameters (`pageSize`, `pageToken`)
216
- - `signal?`: Optional AbortSignal for cancellation (critical for race condition prevention)
160
+ `src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/`
217
161
 
218
- **Returns**: Array of `SearchResultRecord` objects with field values
162
+ Example paths:
219
163
 
220
- **Example**:
164
+ - Constants: `src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/constants.ts`
165
+ - Search page: `src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/pages/GlobalSearch.tsx`
166
+ - Components: `src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/components/`
221
167
 
222
- ```typescript
223
- const results = await searchResults("Acme", "Account", { pageSize: 25, pageToken: "0" });
224
- // Returns: [{ id: '001...', fields: { Name: 'Acme Corp', ... }, ... }, ...]
225
- ```
168
+ After copying **pages/** and **components/** into your app:
226
169
 
227
- **API Endpoint**: `POST /search/results/keyword?q={query}&objectApiName={objectApiName}`
170
+ 1. In the **copied files only**, replace imports from `@/api`, `@/hooks`, `@/types`, `@/utils`, `@/lib`, `@/features` with imports from **`@salesforce/webapp-template-feature-react-global-search-experimental`**.
171
+ 2. Keep constants and component-to-component imports pointing at your app’s copied constants and components.
172
+ 3. Adapt markup and styles to your app’s UI library (the reference uses shadcn and Tailwind).
228
173
 
229
- **Request Body**:
174
+ ---
230
175
 
231
- ```json
232
- {
233
- "pageSize": 25,
234
- "pageToken": "0"
176
+ ## Example: build filters → FilterCriteria[]
177
+
178
+ ```ts
179
+ import {
180
+ parseFilterValue,
181
+ sanitizeFilterValue,
182
+ type FilterCriteria,
183
+ } from "@salesforce/webapp-template-feature-react-global-search-experimental";
184
+
185
+ function toCriteria(
186
+ objectApiName: string,
187
+ fieldPath: string,
188
+ operator: "eq" | "like",
189
+ rawValues: string[],
190
+ ): FilterCriteria {
191
+ return {
192
+ objectApiName,
193
+ fieldPath,
194
+ operator,
195
+ values: rawValues
196
+ .map((v) => sanitizeFilterValue(v))
197
+ .filter(Boolean)
198
+ .map((v) => parseFilterValue(v)),
199
+ };
235
200
  }
236
201
  ```
237
202
 
238
203
  ---
239
204
 
240
- ## API Consumption Flow
241
-
242
- ### Initialization
243
-
244
- 1. **`getObjectInfoBatch`** is called with all object names from `OBJECT_API_NAMES`
245
- - Returns object metadata (labels, plural labels, etc.)
246
- - Use this to understand which objects are available for search
247
-
248
- 2. **For each object** (can be done in parallel):
249
- - **`getObjectListFilters`** - Gets filter definitions; column definitions for list/result UI are derived from filters (e.g. `filters.map(f => f.targetFieldPath)` for field names)
250
- - **`searchResults`** - Gets initial search results (if query is provided)
251
-
252
- 3. **For each picklist filter** (can be done in parallel):
253
- - **`getPicklistValues`** - Gets available values for picklist fields
254
-
255
- ### Search Execution
205
+ ## Implementation steps (for an agent)
256
206
 
257
- 1. **Initial Search**:
258
- - Call **`searchResults`** with the search query and object API name
259
- - Include pagination parameters (`pageSize`, `pageToken`)
207
+ 1. **Install**
208
+ From the consumer app root: `npm install @salesforce/webapp-template-feature-react-global-search-experimental`.
260
209
 
261
- 2. **Filter Application**:
262
- - Apply filters to the search query
263
- - Call **`searchResults`** with updated query/filters
264
- - Reset pagination to page 1 (use `pageToken: '0'`)
210
+ 2. **Determine search object(s)**
211
+ From user prompt or app context. Default to **Account** if none specified. Set `OBJECT_API_NAMES` in the app’s constants to this value.
265
212
 
266
- 3. **Pagination**:
267
- - Use `pageToken` from previous response for next page
268
- - Call **`searchResults`** with new `pageToken`
213
+ 3. **Constants**
214
+ In the app, create a constants file (or copy from the package reference path above) and set `OBJECT_API_NAMES`, `DEFAULT_PAGE_SIZE`, `DEFAULT_DETAIL_PAGE_TITLE`.
269
215
 
270
- 4. **Page Size Change**:
271
- - Call **`searchResults`** with new `pageSize`
272
- - Reset to page 1 (use `pageToken: '0'`)
216
+ 4. **API / types / utils / hooks**
217
+ **Import** only from the package. Do **not** create or copy `api/`, `hooks/`, `types/`, or `utils/` in the app.
273
218
 
274
- ### Race Condition Prevention
219
+ 5. **UI**
220
+ Either **(a)** build your own UI using the package’s hooks and types, or **(b)** copy the full **pages/** and **components/** from the package reference path into the app, then batch-replace imports in the copied files to use the package (see “Optional: copy reference UI” above).
275
221
 
276
- All API calls support `AbortSignal` to prevent stale data:
222
+ 6. **Routes and search input**
223
+ Add routes for search (e.g. `/global-search/:query`) and record detail (e.g. `/object/:objectApiName/:recordId`). Place the search input on the **Home page** or in the **Header** only—not in a root layout.
277
224
 
278
- - When a new search is triggered, cancel the previous request using `AbortSignal`
279
- - When switching between objects, cancel previous object requests
280
- - When component unmounts, cancel all pending requests
225
+ ### Checklist
281
226
 
282
- This ensures applications always work with the most recent data and avoid race conditions.
227
+ - [ ] Package installed.
228
+ - [ ] Search object(s) determined; `OBJECT_API_NAMES` set in app constants.
229
+ - [ ] API, types, utils, hooks **imported** from package only (no api/hooks/types/utils folders in app).
230
+ - [ ] UI: own implementation **or** full copy of reference pages/components with imports updated to the package.
231
+ - [ ] Routes and search entry placement done; GlobalSearchInput not in root layout.
283
232
 
284
- ## Setup
285
-
286
- ### Prerequisites
233
+ ---
287
234
 
288
- 1. **Salesforce CLI (`sf`)** - Must be installed and authenticated to a Salesforce org
289
- 2. **@salesforce/webapps packages** - Required for API proxy functionality (see linking instructions below)
235
+ ## Notes / constraints
290
236
 
291
- ### Linking @salesforce/webapps (Required until packages are published)
237
+ - **Single-object scope**: The package’s APIs and hooks are designed for one CRM object at a time (one `objectApiName` per call). There is no built-in multi-object search or tabbed results. You can still implement **multi-object CRM search** by calling the same services and hooks once per object (e.g. in parallel or per tab), then combining the results and building your own UI (tabs, merged lists, or separate sections).
238
+ - **Layout is REST**: record layout from UI API REST; record and object metadata are GraphQL-backed.
239
+ - **No UI exports**: build or copy UI in the app; import only the data layer from the package.
292
240
 
293
- Since `@salesforce/webapps` and `@salesforce/vite-plugin-webapps` are not yet published to npm, you need to link them locally:
241
+ ---
294
242
 
295
- ```bash
296
- # Step 1: In the webapps repo - register packages with npm's link registry
297
- cd /path/to/webapps/packages/webapps && npm link
298
- cd /path/to/webapps/packages/vite-plugin-webapps && npm link
299
-
300
- # Step 2: In this repo - link the packages
301
- cd /path/to/webapps-templates/packages/feature/feature-react-global-search
302
- npm link @salesforce/webapps
303
- npm link @salesforce/vite-plugin-webapps
304
- ```
243
+ ## Local development (this repo)
305
244
 
306
- ### Building and Running
245
+ When developing this feature inside the webapps repo:
307
246
 
308
247
  ```bash
309
- # Build the feature (applies patches from template to dist)
310
- npm run build
311
-
312
- # Start the development server
313
- npm run dev
314
- ```
315
-
316
- The `npm run build` command will:
317
-
318
- 1. Apply feature patches to create the dist directory
319
- 2. Remove the package-lock.json (to avoid resolution conflicts)
320
- 3. Re-link the @salesforce/webapps packages
321
-
322
- ## Configuration
323
-
324
- ### Setting Searchable Objects
325
-
326
- Configure which Salesforce objects to search by editing `src/constants.ts`:
327
-
328
- ```typescript
329
- export const OBJECT_API_NAMES = ["Account", "Property__c"] as const;
248
+ nx run @salesforce/webapp-template-feature-react-global-search-experimental:build:dist-app
249
+ nx run @salesforce/webapp-template-feature-react-global-search-experimental:dev
330
250
  ```
331
-
332
- This configuration determines which objects will be included when calling `getObjectInfoBatch` and when executing searches.
333
-
334
- ## Usage
335
-
336
- ### Using the API Service
337
-
338
- Import the service and use the available methods:
339
-
340
- ```typescript
341
- import { objectInfoService } from "@/api/objectInfoService";
342
-
343
- // Get object metadata
344
- const objectInfo = await objectInfoService.getObjectInfoBatch("Account,Property__c");
345
-
346
- // Get filter definitions (columns for list UI are derived from filters via targetFieldPath + label)
347
- const filters = await objectInfoService.getObjectListFilters("Account");
348
-
349
- // Get picklist values
350
- const picklistValues = await objectInfoService.getPicklistValues("Account", "Type");
351
-
352
- // Execute search
353
- const results = await objectInfoService.searchResults("Acme", "Account", {
354
- pageSize: 25,
355
- pageToken: "0",
356
- });
357
- ```
358
-
359
- ### Using the Custom Hook
360
-
361
- The `useObjectSearchData` hook manages API calls and data fetching:
362
-
363
- ```typescript
364
- import { useObjectSearchData } from "@/hooks/useObjectSearchData";
365
-
366
- const { tabs, loading, error, tabData, filtersData } = useObjectSearchData({
367
- filteredObjectApiNames: ["Account", "Property__c"],
368
- activeTab: "Account",
369
- searchQuery: "Acme",
370
- searchPageSize: 25,
371
- searchPageToken: "0",
372
- });
373
- ```
374
-
375
- ## Type Safety
376
-
377
- All API responses are validated using Zod schemas, ensuring:
378
-
379
- - Type safety at compile time
380
- - Runtime validation of API responses
381
- - Better error messages when API contracts change
382
- - Protection against malformed data
383
-
384
- Type definitions are derived from Zod schemas using `z.infer`, ensuring consistency between validation and TypeScript types.
385
-
386
- ## Error Handling
387
-
388
- The `fetchAndValidate` utility provides consistent error handling:
389
-
390
- - Network errors are caught and re-thrown with context
391
- - HTTP errors (non-200 status) are caught and reported
392
- - Zod validation errors are caught and reported (without logging sensitive details)
393
- - AbortErrors are properly propagated (not treated as regular errors)
394
-
395
- All errors are handled gracefully in the UI, preventing page refreshes and providing user-friendly error messages.
396
-
397
- ## Dependencies
398
-
399
- This feature depends on:
400
-
401
- - **feature-shadcn** - For all UI components (Button, Input, Select, Table, Pagination, etc.)
402
- - **@salesforce/webapps** - For API client and Salesforce integration
403
- - **zod** - For runtime validation and type inference
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: webapp-add-react-component
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
+ ---
5
+
6
+ # Add React Component
7
+
8
+ ## Step 1 — Identify the type of component
9
+
10
+ Determine which of these three categories the request falls into, then follow the corresponding section below:
11
+
12
+ - **Page** — user wants a new routed page (e.g. "add a contacts page", "create a dashboard page", "add a settings section")
13
+ - **Header / Footer** — user wants a site-wide header, footer, nav bar, or page footer that appears on every page
14
+ - **Component** — everything else: a widget, card, table, form, dialog, or other UI element placed within an existing page
15
+
16
+ If it is not immediately clear from the user's message, ask:
17
+
18
+ > "Are you looking to add a new page, a site-wide header or footer, or a component within an existing page?"
19
+
20
+ Then follow the matching section.
21
+
22
+ ---
23
+
24
+ ## Clarifying Questions
25
+
26
+ Ask **one question at a time** and wait for the response before asking the next. Stop when you have enough to build accurately — do not guess or assume.
27
+
28
+ ### For a Page
29
+
30
+ 1. **What is the name and purpose of the page?** (e.g., Contacts, Dashboard, Settings)
31
+ 2. **What URL path should it use?** (e.g., `/contacts`, `/dashboard`) — or derive from the page name?
32
+ 3. **Should the page appear in the navigation menu?**
33
+ 4. **Who can access it?** Public, authenticated users only (`PrivateRoute`), or unauthenticated only (e.g., login — `AuthenticationRoute`)?
34
+ 5. **What content or sections should the page include?** (list, form, table, detail view, etc.)
35
+ 6. **Does it need to fetch any data?** If so, from where?
36
+
37
+ ### For a Header / Footer
38
+
39
+ 1. **Header, footer, or both?**
40
+ 2. **What should the header contain?** (logo/app name, nav links, user avatar, CTA button, etc.)
41
+ 3. **What should the footer contain?** (copyright text, links, social icons, etc.)
42
+ 4. **Should the header be sticky (fixed to top while scrolling)?**
43
+ 5. **Is there a logo or brand name to display?** (or placeholder?)
44
+ 6. **Any specific color scheme or style direction?** (dark background, branded primary color, minimal, etc.)
45
+ 7. **Should navigation links appear in the header?** If so, which pages?
46
+
47
+ ### For a Component
48
+
49
+ 1. **What should the component do?** (display data, accept input, trigger an action, etc.)
50
+ 2. **What page or location should it appear on?**
51
+ 3. **Is this shared/reusable across pages, or specific to one feature?** (determines file location)
52
+ 4. **What data or props does it need?** (static content, props, fetched data)
53
+ 5. **Does it need internal state?** (loading, toggle, form state, etc.)
54
+ 6. **Are there any specific shadcn components to use?** (Card, Table, Dialog, Form, etc.)
55
+ 7. **Should it appear in a specific layout position?** (full-width, sidebar, inline, etc.)
56
+
57
+ ---
58
+
59
+ ## Implementation
60
+
61
+ Once you have identified the type and gathered answers to the clarifying questions, read and follow the corresponding implementation guide:
62
+
63
+ - **Page** — read `implementation/page.md` and follow the instructions there.
64
+ - **Header / Footer** — read `implementation/header-footer.md` and follow the instructions there.
65
+ - **Component** — read `implementation/component.md` and follow the instructions there.
66
+
67
+ ---
68
+
69
+ ## Verification
70
+
71
+ Before completing, run from the web app directory `force-app/main/default/webapplications/<appName>/` (use the actual app folder name):
72
+
73
+ ```bash
74
+ cd force-app/main/default/webapplications/<appName> && npm run lint && npm run build
75
+ ```
76
+
77
+ - **Lint:** MUST result in 0 errors. Fix any ESLint or TypeScript issues.
78
+ - **Build:** MUST succeed. Resolve any compilation or Vite build failures before finishing.