# Feature: React Global Search (Single Object)

This package is a **reference implementation** for adding global search (keyword search, filters, results list, and record detail) to a Salesforce UI Bundle. 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.

**Intended use (vibing model):**

- **Do not copy the feature source** (api, hooks, types, utils).
- **Build your own UI** (recommended), or **copy** the reference pages/components from the package and adapt.
- **Import** data-layer pieces from the package: services, hooks, types, utilities.

UI components and feature constants are deliberately **not exported** from the public API.

---

## What you get (public API)

Import from the package root:

```ts
import {
  objectInfoService,
  objectDetailService,
  useObjectListMetadata,
  useRecordListGraphQL,
  useRecordDetailLayout,
  useObjectColumns,
  useObjectFilters,
  useObjectInfoBatch,
  type FilterCriteria,
  parseFilterValue,
  sanitizeFilterValue,
} from "@salesforce/ui-bundle-template-feature-react-global-search";
```

### API services

- **`objectInfoService`**
  - `getObjectInfoBatch(objectApiNames)` (GraphQL uiapi.objectInfos)
  - `getPicklistValues(objectApiName, fieldName, recordTypeId?)` (GraphQL uiapi.objectInfos)
  - `getObjectListFilters(objectApiName)` (REST search-info filters)
- **`objectDetailService`**
  - `getLayout(objectApiName, recordTypeId?)` (REST layout)
  - `getRecordDetail(objectApiName, recordId, recordTypeId?)` (layout + object info + record)
- **GraphQL record helpers**
  - `getRecordsGraphQL(...)` (list query, `first + after`)
  - `getRecordByIdGraphQL(...)` (single record by Id)
  - `buildWhereFromCriteria(...)`, `buildOrderByFromSort(...)`, `buildGetRecordsQuery(...)`

### Hooks

- **List metadata**: `useObjectListMetadata(objectApiName)` — filters, columns (derived from filters), picklistValues.
- **List columns (legacy)**: `useObjectColumns(objectApiName)` — columns, columnsLoading, columnsError.
- **List filters**: `useObjectFilters(objectApiName)` — filtersData (filters + picklist values per object).
- **List records**: `useRecordListGraphQL({ objectApiName, first, after, filters, sortBy, searchQuery, columns? })` — GraphQL list with cursor pagination.
- **Detail**: `useRecordDetailLayout({ objectApiName, recordId, recordTypeId?, initialData? })` — layout (REST) + object metadata + record (GraphQL).
- **Batch object info**: `useObjectInfoBatch(objectApiNames)` — objectInfos, loading, error.

### Types, schemas, utils

- **Types**: `Filter`, `FilterCriteria`, `Column`, `SearchResultRecord`, `LayoutResponse`, `ObjectInfoResult`, …
- **Zod schemas**: `LayoutResponseSchema`, `ObjectInfoResultSchema`, `SearchResultsResponseSchema`, …
- **Utilities**: `parseFilterValue`, `sanitizeFilterValue`, `createFiltersKey`, `getGraphQLNodeValue`, `graphQLNodeToSearchResultRecordData`, `fetchAndValidate`, `safeEncodePath`, `getNestedFieldValue`, `getRecordDisplayName`, layout transform and form helpers, etc.

The definitive type surface is in `index.d.ts` in this package.

---

## Features

- **Single object browse & search**: keyword search and “browse all” mode (`browse__all`).
- **Filterable list**: filter definitions + picklist values, criteria → GraphQL where clause.
- **Sortable list**: build orderBy from sort selection.
- **Cursor pagination (forward-only)** with **previous-page simulation via cursor stack**.
- **Record detail**: layout-driven field selection (layout REST + record GraphQL).
- **Type safety**: Zod validation + exported TypeScript types.

---

## Architecture

### Data layer (GraphQL vs REST)

- **GraphQL (uiapi)**  
  Object metadata via `getObjectInfoBatch` and `getPicklistValues`. Records via `getRecordsGraphQL` and `getRecordByIdGraphQL`.
- **REST (uiApiClient)**  
  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).

In the reference implementation:

- **Detail page**: `useRecordDetailLayout` → `objectDetailService.getRecordDetail` (layout REST + record GraphQL).
- **List page**: `useObjectListMetadata` (filters + columns + picklists) + `useRecordListGraphQL` (records).

### API flow (conceptual)

- **Initialization**: `getObjectInfoBatch` for object metadata; per object: `getObjectListFilters` (columns derived from filters), then `getPicklistValues` for picklist filters.
- **List/search**: `useRecordListGraphQL` (GraphQL UI API with cursor pagination; use a cursor stack for “Previous”).
- **Detail**: `useRecordDetailLayout` / `objectDetailService.getRecordDetail`.

All API calls use `fetchAndValidate` and validate with Zod.

---

## Install

```bash
npm install @salesforce/ui-bundle-template-feature-react-global-search
```

### Required peer environment

- `@salesforce/ui-bundle` (API proxy, auth, etc.)
- React and React Router

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.

---

## Configuration (constants)

Constants are **not exported**. When you build your app (or copy reference UI), define them in the app:

- **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.
- **DEFAULT_PAGE_SIZE** – e.g. `20`.
- **DEFAULT_DETAIL_PAGE_TITLE** – e.g. `"Untitled"`.

**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.

---

## Vibing integration (recommended: build your own UI)

1. **Routes**  
   Search results: e.g. `/global-search/:query` (use `browse__all` for “show all”). Record detail: e.g. `/object/:objectApiName/:recordId`.

2. **Search entry**  
   On submit, navigate to `/global-search/${encodeURIComponent(query)}` or `/global-search/browse__all`.

3. **Results page (list)**  
   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).

4. **Detail page**  
   Use `useRecordDetailLayout({ objectApiName, recordId })` for `layout`, `record`, `objectMetadata`; render with `layoutTransformUtils` and `graphQLNodeFieldUtils` (or equivalent).

5. **Search input placement**  
   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.

---

## Optional: copy reference UI from the package

The package **does not export** UI components. If you prefer to copy the reference UI instead of building your own:

- **What to import (do not copy):** API (`objectInfoService`, `objectDetailService`, …), hooks (`useObjectListMetadata`, `useRecordListGraphQL`, `useRecordDetailLayout`, …), types, Zod schemas, utilities.
- **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.

**Path convention (reference implementation in this repo):**

All paths below are relative to the **package root** of this feature. The reference implementation lives under:

`src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/`

Example paths:

- Constants: `src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/constants.ts`
- Search page: `src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/pages/GlobalSearch.tsx`
- Components: `src/force-app/main/default/uiBundles/feature-react-global-search/src/features/global-search/components/`

After copying **pages/** and **components/** into your app:

1. In the **copied files only**, replace imports from `@/api`, `@/hooks`, `@/types`, `@/utils`, `@/lib`, `@/features` with imports from **`@salesforce/ui-bundle-template-feature-react-global-search`**.
2. Keep constants and component-to-component imports pointing at your app’s copied constants and components.
3. Adapt markup and styles to your app’s UI library (the reference uses shadcn and Tailwind).

---

## Example: build filters → FilterCriteria[]

```ts
import {
  parseFilterValue,
  sanitizeFilterValue,
  type FilterCriteria,
} from "@salesforce/ui-bundle-template-feature-react-global-search";

function toCriteria(
  objectApiName: string,
  fieldPath: string,
  operator: "eq" | "like",
  rawValues: string[],
): FilterCriteria {
  return {
    objectApiName,
    fieldPath,
    operator,
    values: rawValues
      .map((v) => sanitizeFilterValue(v))
      .filter(Boolean)
      .map((v) => parseFilterValue(v)),
  };
}
```

---

## Implementation steps (for an agent)

1. **Install**  
   From the consumer app root: `npm install @salesforce/ui-bundle-template-feature-react-global-search`.

2. **Determine search object(s)**  
   From user prompt or app context. Default to **Account** if none specified. Set `OBJECT_API_NAMES` in the app’s constants to this value.

3. **Constants**  
   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`.

4. **API / types / utils / hooks**  
   **Import** only from the package. Do **not** create or copy `api/`, `hooks/`, `types/`, or `utils/` in the app.

5. **UI**  
   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).

6. **Routes and search input**  
   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.

### Checklist

- [ ] Package installed.
- [ ] Search object(s) determined; `OBJECT_API_NAMES` set in app constants.
- [ ] API, types, utils, hooks **imported** from package only (no api/hooks/types/utils folders in app).
- [ ] UI: own implementation **or** full copy of reference pages/components with imports updated to the package.
- [ ] Routes and search entry placement done; GlobalSearchInput not in root layout.

---

## Notes / constraints

- **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).
- **Layout is REST**: record layout from UI API REST; record and object metadata are GraphQL-backed.
- **No UI exports**: build or copy UI in the app; import only the data layer from the package.

---

## Local development (this repo)

When developing this feature inside the ui-bundle repo:

```bash
nx run @salesforce/ui-bundle-template-feature-react-global-search:build:dist-app
nx run @salesforce/ui-bundle-template-feature-react-global-search:dev
```
