feat: pin template deps to latest npm versions and flatten skill folders

- Add pin-template-deps.js to resolve "*" deps to exact npm versions
- Integrate pinning into sync-template-skills npm script
- Remove check-template-skills-versions.js (no longer needed)
- Simplify workflow to single sync step
- Flatten skill output: one folder per skill with cleaned names

Made-with: Cursor
This commit is contained in:
k-j-kim 2026-03-17 17:22:48 -07:00
parent 965e4fe372
commit 6d9ba6f113
No known key found for this signature in database
GPG Key ID: 40FA25A7DE938B04
65 changed files with 1754 additions and 1191 deletions

View File

@ -29,30 +29,21 @@ jobs:
node-version-file: ".nvmrc"
cache: npm
# ── Version check ────────────────────────────────────────────
- name: Check for version changes
id: check
run: node scripts/check-template-skills-versions.js
# ── Install + sync ───────────────────────────────────────────
- name: Install dependencies
if: steps.check.outputs.skip != 'true'
run: npm install
# ── Pin + install + sync (all handled by the npm script) ────
- name: Sync template skills
if: steps.check.outputs.skip != 'true'
run: npm run sync-template-skills
# ── PR creation ──────────────────────────────────────────────
- name: Create PR when there are changes
if: steps.check.outputs.skip != 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.IDEE_GH_TOKEN }}
branch: chore/sync-template-skills
base: main
title: ${{ steps.check.outputs.title }}
body: ${{ steps.check.outputs.body }}
commit-message: ${{ steps.check.outputs.title }}
title: "chore: sync template skills from npm"
body: |
Synced skills from template npm packages into `skills/salesforce-webapp-*/`.
Same flow as running locally: `npm install` then `npm run sync-template-skills`.
commit-message: "chore: sync template skills from npm"
committer: svc-idee-bot <svc_idee_bot@salesforce.com>
author: svc-idee-bot <svc_idee_bot@salesforce.com>

4
package-lock.json generated
View File

@ -9,8 +9,8 @@
"version": "1.1.0",
"license": "CC-BY-NC-4.0",
"devDependencies": {
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.107.0",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.107.0",
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "1.107.0",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "1.107.0",
"tsx": "^4.21.0"
}
},

View File

@ -11,8 +11,8 @@
"registry": "https://registry.npmjs.org"
},
"devDependencies": {
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.107.0",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.107.0",
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "1.107.0",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "1.107.0",
"tsx": "^4.21.0"
},
"scripts": {

View File

@ -1,69 +0,0 @@
#!/usr/bin/env node
/**
* Compares npm-latest versions vs the synced .template-versions.json for each
* template skill package. Writes skip, branch, title, body to GITHUB_OUTPUT
* when running in CI; prints JSON locally.
*
* Used by .github/workflows/sync-template-skills.yml to decide whether a sync
* is needed and to build descriptive PR metadata.
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const { matchingDeps } = require('./lib/package-utils');
const VERSIONS_FILE = '.template-versions.json';
const repoRoot = process.cwd();
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
const packageNames = matchingDeps(pkg);
const versionsPath = path.join(repoRoot, 'skills', VERSIONS_FILE);
let syncedVersions = {};
if (fs.existsSync(versionsPath)) {
syncedVersions = JSON.parse(fs.readFileSync(versionsPath, 'utf8'));
}
const results = [];
for (const name of packageNames) {
let latest = '';
try {
latest = execSync(`npm view ${name} version`, { encoding: 'utf8' }).trim();
} catch {
latest = '';
}
const current = syncedVersions[name] || '';
results.push({ name, latest, current, changed: current !== latest });
}
const changed = results.filter((r) => r.changed);
const skip = changed.length === 0;
let title = 'chore: sync template skills from npm';
if (changed.length === 1) {
title = `chore: sync template skills from npm (${changed[0].name}@${changed[0].latest})`;
} else if (changed.length > 1) {
title = `chore: sync template skills from npm (${changed.length} packages)`;
}
const bodyLines = [
'Synced skills from template npm packages into `skills/salesforce-webapp-*/`.',
'',
...changed.map(
(r) => `- **${r.name}**: ${r.current || 'none'}${r.latest}`
),
'',
'Same flow as running locally: `npm install` then `npm run sync-template-skills`.',
];
const body = bodyLines.join('\n');
const out = process.env.GITHUB_OUTPUT;
if (out) {
const delim = 'EOF' + Math.random().toString(36).slice(2);
fs.appendFileSync(out, `skip=${skip}\n`, 'utf8');
fs.appendFileSync(out, `title=${title}\n`, 'utf8');
fs.appendFileSync(out, `body<<${delim}\n${body}\n${delim}\n`, 'utf8');
} else {
console.log(JSON.stringify({ skip, title, results }, null, 2));
}

View File

@ -0,0 +1,42 @@
#!/usr/bin/env node
/**
* Pins each template skill devDependency in package.json to its latest npm version.
* Run before `npm install` in CI so the lockfile and installed packages reflect
* exact published versions instead of "*".
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const { matchingDeps } = require('./lib/package-utils');
const pkgPath = path.join(process.cwd(), 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
const names = matchingDeps(pkg);
let changed = false;
for (const name of names) {
const current = pkg.devDependencies[name];
if (current.startsWith('file:')) continue;
let latest;
try {
latest = execSync(`npm view ${name} version`, { encoding: 'utf8' }).trim();
} catch (_) {
console.warn(`Could not resolve ${name} on npm, skipping.`);
continue;
}
if (current !== latest) {
console.log(`${name}: ${current} -> ${latest}`);
pkg.devDependencies[name] = latest;
changed = true;
}
}
if (changed) {
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
console.log('Updated package.json with pinned versions.');
} else {
console.log('All template deps already pinned to latest.');
}

View File

@ -1,11 +1,10 @@
{
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "1.59.1",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "1.61.4",
"@salesforce/webapp-template-base-sfdx-project-experimental": "1.103.1",
"@salesforce/webapp-template-feature-react-file-upload-experimental": "1.103.1",
"@salesforce/webapp-template-feature-react-chart-experimental": "1.103.1",
"@salesforce/webapp-template-feature-react-agentforce-conversation-client-experimental": "1.103.1",
"@salesforce/webapp-template-feature-micro-frontend": "1.103.1",
"@salesforce/webapp-template-feature-graphql-experimental": "1.103.1",
"@salesforce/webapps-features-experimental": "1.103.1"
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "1.107.0",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "1.107.0",
"@salesforce/webapp-template-base-sfdx-project-experimental": "1.107.0",
"@salesforce/webapp-template-feature-react-file-upload-experimental": "1.107.0",
"@salesforce/webapp-template-feature-react-chart-experimental": "1.107.0",
"@salesforce/webapp-template-feature-react-agentforce-conversation-client-experimental": "1.107.0",
"@salesforce/webapp-template-feature-micro-frontend": "1.107.0",
"@salesforce/webapps-features-experimental": "1.107.0"
}

View File

@ -0,0 +1,165 @@
---
name: salesforce-webapp-accessing-data
description: Salesforce data access patterns. Use when adding or modifying any code that fetches data from Salesforce (records, Chatter, Connect API, etc.).
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.graphql"
---
# Salesforce Data Access
Guidance for accessing Salesforce data from web apps. **All Salesforce data fetches MUST use the Data SDK** (`@salesforce/sdk-data`). The SDK provides authentication, CSRF handling, and correct base URL resolution — direct `fetch` or `axios` calls bypass these and are not allowed.
## Mandatory: Use the Data SDK
> **Every Salesforce data fetch must go through the Data SDK.** Obtain it via `createDataSDK()`, then use `sdk.graphql?.()` or `sdk.fetch?.()`. Never call `fetch()` or `axios` directly for Salesforce endpoints.
## Optional Chaining and Graceful Handling
**Always use optional chaining** when calling `sdk.graphql` or `sdk.fetch` — these methods may be undefined in some surfaces (e.g., Salesforce ACC, MCP Apps). Handle the case where they are not available gracefully:
```typescript
const sdk = await createDataSDK();
// ✅ Use optional chaining
const response = await sdk.graphql?.(query);
// ✅ Check before using fetch
if (!sdk.fetch) {
throw new Error("Data SDK fetch is not available in this context");
}
const res = await sdk.fetch(url);
```
For GraphQL, if `sdk.graphql` is undefined, the call returns `undefined` — handle that in your logic (e.g., throw a clear error or return a fallback). For `sdk.fetch`, check availability before calling when the operation is required.
## Preference: GraphQL First
**GraphQL is the preferred method** for querying and mutating Salesforce records. Use it when:
- Querying records (Account, Contact, Opportunity, custom objects)
- Creating, updating, or deleting records (when GraphQL supports the operation)
- Fetching related data, filters, sorting, pagination
**Use `sdk.fetch` only when GraphQL is not sufficient.** For REST API usage, invoke the `fetching-rest-api` skill, which documents:
- Chatter API (e.g., `/services/data/v65.0/chatter/users/me`)
- Connect REST API (e.g., `/services/data/v65.0/connect/file/upload/config`)
- Apex REST (e.g., `/services/apexrest/auth/login`)
- UI API REST (e.g., `/services/data/v65.0/ui-api/records/{recordId}`)
- Einstein LLM Gateway
---
## Getting the SDK
```typescript
import { createDataSDK } from "@salesforce/sdk-data";
const sdk = await createDataSDK();
```
---
## Example 1: GraphQL (Preferred)
For record queries and mutations, use GraphQL via the Data SDK. Invoke the `using-graphql` skill for the full workflow (schema exploration, query authoring, codegen, lint validate).
```typescript
import { createDataSDK, gql } from "@salesforce/sdk-data";
import type { GetAccountsQuery } from "../graphql-operations-types";
const GET_ACCOUNTS = gql`
query GetAccounts {
uiapi {
query {
Account(first: 10) {
edges {
node {
Id
Name { value }
}
}
}
}
}
}
`;
export async function getAccounts() {
const sdk = await createDataSDK();
const response = await sdk.graphql?.<GetAccountsQuery>(GET_ACCOUNTS);
if (response?.errors?.length) {
throw new Error(response.errors.map((e) => e.message).join("; "));
}
return response?.data?.uiapi?.query?.Account?.edges?.map((e) => e?.node) ?? [];
}
```
---
## Example 2: Fetch (When GraphQL Is Not Sufficient)
For REST endpoints that have no GraphQL equivalent, use `sdk.fetch`. **Invoke the `fetching-rest-api` skill** for full documentation of Chatter, Connect REST, Apex REST, UI API REST, and Einstein LLM endpoints.
```typescript
import { createDataSDK } from "@salesforce/sdk-data";
declare const __SF_API_VERSION__: string;
const API_VERSION = typeof __SF_API_VERSION__ !== "undefined" ? __SF_API_VERSION__ : "65.0";
export async function getCurrentUser() {
const sdk = await createDataSDK();
const response = await sdk.fetch?.(`/services/data/v${API_VERSION}/chatter/users/me`);
if (!response?.ok) throw new Error(`HTTP ${response?.status}`);
const data = await response.json();
return { id: data.id, name: data.name };
}
```
---
## Anti-Patterns (Forbidden)
### Direct fetch to Salesforce
```typescript
// ❌ FORBIDDEN — bypasses Data SDK auth and CSRF
const res = await fetch("/services/data/v65.0/chatter/users/me");
```
### Direct axios to Salesforce
```typescript
// ❌ FORBIDDEN — bypasses Data SDK
const res = await axios.get("/services/data/v65.0/chatter/users/me");
```
### Correct approach
```typescript
// ✅ CORRECT — use Data SDK
const sdk = await createDataSDK();
const res = await sdk.fetch?.("/services/data/v65.0/chatter/users/me");
```
---
## Decision Flow
1. **Need to query or mutate Salesforce records?** → Use GraphQL via the Data SDK. Invoke the `using-graphql` skill.
2. **Need Chatter, Connect REST, Apex REST, UI API REST, or Einstein LLM?** → Use `sdk.fetch`. Invoke the `fetching-rest-api` skill.
3. **Never** use `fetch`, `axios`, or similar directly for Salesforce API calls.
---
## Reference
- GraphQL workflow: invoke the `using-graphql` skill (`.a4drules/skills/using-graphql/`)
- REST API via fetch: invoke the `fetching-rest-api` skill (`.a4drules/skills/fetching-rest-api/`)
- Data SDK package: `@salesforce/sdk-data` (`createDataSDK`, `gql`, `NodeOfConnection`)
- `createRecord` for UI API record creation: `@salesforce/webapp-experimental/api` (uses Data SDK internally)

View File

@ -1,5 +1,5 @@
---
name: salesforce-webapp-react-data-visualization
name: salesforce-webapp-building-data-visualization
description: Adds data visualization components (charts, stat cards, KPI metrics) to React pages using Recharts. Use when the user asks to add a chart, graph, donut chart, pie chart, bar chart, stat card, KPI metric, dashboard visualization, or analytics component to the web application.
---
@ -46,8 +46,8 @@ Recharts is built on D3 and provides declarative React components. No additional
Read the corresponding guide:
- **Bar chart** — use the **`analytics-charts`** skill in `feature-react-chart` (AnalyticsChart component for categorical data).
- **Line / area chart** — use the **`analytics-charts`** skill in `feature-react-chart` (AnalyticsChart component for time-series data).
- **Bar chart** — use the **`building-analytics-charts`** skill in `feature-react-chart` (AnalyticsChart component for categorical data).
- **Line / area chart** — use the **`building-analytics-charts`** skill in `feature-react-chart` (AnalyticsChart component for time-series data).
- **Donut / pie chart** — read `implementation/donut-chart.md`
- **Stat card with trend** — read `implementation/stat-card.md`
- **Dashboard layout** — read `implementation/dashboard-layout.md`

View File

@ -156,7 +156,7 @@ Keep chart colors consistent with the app's design system. Define them as consta
## Other chart types
For **bar charts** and **line charts**, use the `AnalyticsChart` component from `feature-react-chart` instead of raw Recharts. See the **`analytics-charts`** skill for usage.
For **bar charts** and **line charts**, use the `AnalyticsChart` component from `feature-react-chart` instead of raw Recharts. See the **`building-analytics-charts`** skill for usage.
---

View File

@ -1,5 +1,5 @@
---
name: salesforce-webapp-react-interactive-map
name: salesforce-webapp-building-interactive-map
description: Adds interactive Leaflet maps with geocoded markers to React pages. Use when the user asks to add a map, show locations on a map, display property pins, add a map view, or integrate mapping into the web application.
---

View File

@ -1,5 +1,5 @@
---
name: salesforce-webapp-react
name: salesforce-webapp-building-react-components
description: Use when editing any React code in the web application — creating or modifying components, pages, layout, headers, footers, or any TSX/JSX files. Follow this skill for add component, add page, header/footer, and general React UI implementation patterns (shadcn UI and Tailwind CSS).
---

View File

@ -1,5 +1,5 @@
---
name: salesforce-webapp-react-weather-widget
name: salesforce-webapp-building-weather-widget
description: Adds a weather widget to React pages using the free Open-Meteo API. Use when the user asks to add weather, show a forecast, display current conditions, add a weather card, or integrate weather data into the web application.
---
@ -34,7 +34,7 @@ The widget needs a latitude/longitude. Identify where this comes from:
- **Fixed default** — hardcoded city (e.g. San Francisco: `37.7749, -122.4194`)
- **User's location** — browser Geolocation API
- **Address-based** — geocode an address to lat/lng (see the `webapp-react-interactive-map` skill for geocoding)
- **Address-based** — geocode an address to lat/lng (see the `building-interactive-map` skill for geocoding)
- **Prop-driven** — parent component passes lat/lng
---

View File

@ -1,5 +1,5 @@
---
name: salesforce-webapp-csp-trusted-sites
name: salesforce-webapp-configuring-csp-trusted-sites
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.
---

View File

@ -0,0 +1,158 @@
---
name: salesforce-webapp-configuring-webapp-metadata
description: Rules for web application metadata structure, webapplication.json configuration, and bundle organization
---
# WebApplication Requirements
## Bundle Rules
- A WebApplication bundle must live under `webapplications/<AppName>/`
- The bundle must contain `<AppName>.webapplication-meta.xml`
- The metadata filename must exactly match the folder name
- A build output directory must exist and contain at least one file
- Default build output directory: `dist/`
- If `webapplication.json.outputDir` is set, it overrides `dist/`
Valid example:
```text
webapplications/
MyApp/
MyApp.webapplication-meta.xml
webapplication.json
dist/
index.html
```
## Metadata XML
Required fields:
- `masterLabel`
- `version` (max 20 chars)
- `isActive` (boolean)
Optional fields:
- `description` (max 255 chars)
## webapplication.json
`webapplication.json` is optional.
Allowed top-level keys only:
- `outputDir`
- `routing`
- `headers`
### File Constraints
- Must be valid UTF-8 JSON
- Max size: 100 KB
- Root must be a non-empty object
- Never allow `{}`, arrays, or primitives as the root
### Path Safety
Applies to:
- `outputDir`
- `routing.fallback`
Reject:
- backslashes
- leading `/` or `\`
- `..` segments
- null or control characters
- globs: `*`, `?`, `**`
- `%`
All resolved paths must stay within the application bundle.
### outputDir
- Must be a non-empty string
- Must reference a subdirectory only
- Reject `.` and `./`
- The directory must exist in the bundle
- The directory must contain at least one file
### routing
- If present, must be a non-empty object
- Allowed keys only:
- `rewrites`
- `redirects`
- `fallback`
- `trailingSlash`
- `fileBasedRouting`
#### routing.trailingSlash
- Must be one of: `"always"`, `"never"`, `"auto"`
#### routing.fileBasedRouting
- Must be a boolean
#### routing.fallback
- Must be a non-empty string
- Must satisfy Path Safety rules
- Target file must exist
#### routing.rewrites
- Must be a non-empty array
- Each item must be a non-empty object
- Allowed keys: `route`, `rewrite`
- `rewrite` must be a non-empty string
- `route`, if present, must be a non-empty string
Example:
```json
{
"routing": {
"rewrites": [
{ "route": "/app/:path*", "rewrite": "/index.html" }
]
}
}
```
#### routing.redirects
- Must be a non-empty array
- Each item must be a non-empty object
- Allowed keys: `route`, `redirect`, `statusCode`
- `redirect` must be a non-empty string
- `route`, if present, must be a non-empty string
- `statusCode`, if present, must be one of: `301`, `302`, `307`, `308`
Example:
```json
{
"routing": {
"redirects": [
{ "route": "/old-page", "redirect": "/new-page", "statusCode": 301 }
]
}
}
```
### headers
- If present, must be a non-empty array
- Each item must be a non-empty object
- Allowed keys: `source`, `headers`
- `headers` must be a non-empty array
Each header entry must contain:
- `key`: non-empty string
- `value`: non-empty string
Example:
```json
{
"headers": [
{
"source": "/assets/**",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
}
]
}
```
## Never Suggest
- `{}` as the JSON root
- `"routing": {}`
- empty arrays
- empty array items such as `[{}]`
- `"outputDir": "."`
- `"outputDir": "./"`

View File

@ -0,0 +1,99 @@
---
name: salesforce-webapp-creating-webapp
description: Core web application rules for SFDX React apps
paths:
- "**/webapplications/**/*"
---
# Skills-First (MUST FOLLOW)
**Before writing any code or running any command**, search for relevant skills (`SKILL.md` files) that cover your task. Read the full skill and follow its instructions. Skills live in `.a4drules/skills/` and `feature/*/skills/`. See **webapp-skills-first.md** for the full protocol and a task-to-skill lookup table.
# Web App Generation
## Before `sf webapp generate`
**Webapp name (`-n`):** Must be **alphanumerical only**—no spaces, hyphens, underscores, or special characters. Use only letters (AZ, az) and digits (09). Example: `CoffeeBoutique` not `Coffee Boutique`.
```bash
sf webapp generate -n MyWebApp -t reactbasic
```
Do not use `create-react-app`, Vite, or other generic scaffolds; use `sf webapp generate` so the app is SFDX-aware.
## After Generation (MANDATORY)
After generating or when touching an existing app:
1. **Replace all default boilerplate** — "React App", "Vite + React", default `<title>`, placeholder text in shell. Use the actual app name.
2. **Populate the home page** — Never leave it as default template. Add real content: landing section, banners, hero, navigation to features.
3. **Update navigation and placeholders** — See [Navigation & Layout section](#navigation--layout-mandatory) below.
# Navigation & Layout (MANDATORY)
Agents consistently miss these. **You must not leave them default.**
## appLayout.tsx is the Source of Truth
- **Build navigation into the app layout** (`appLayout.tsx`). The layout must include nav (header, sidebar, or both) so every page shares the same shell.
- Path: `force-app/main/default/webapplications/<appName>/src/appLayout.tsx`
## When Making UI Changes
**When making any change** that affects navigation, header, footer, sidebar, theme, or overall layout:
1. **You MUST edit `src/appLayout.tsx`** (the layout used by `routes.tsx`).
2. Do not only edit pages/components and leave `appLayout.tsx` unchanged.
3. Before finishing: confirm you opened and modified `appLayout.tsx`. If you did not, the task is incomplete.
## Navigation Menu (Critical)
- **Always edit the navigation menu** in `appLayout.tsx`. Replace default nav items and labels with **app-specific** links and names.
- Do **not** leave template items (e.g. "Home", "About", generic placeholder links).
- Use real routes and labels matching the app (e.g. "Dashboard", "Products", "Orders").
**Check before finishing:** Did I change the nav items and labels to match this app?
## Placeholder Name & Design (Critical)
- **Replace the placeholder app name** everywhere: header, nav brand/logo, footer, `<title>` in `index.html`, any "Welcome to…" text.
- **Replace placeholder design** in the shell: default header/footer styling, generic branding.
**Check before finishing:** Is the app name and shell design still the template default? If yes, update it.
## Where to Edit
| What | Where |
| ------------------- | -------------------------------------------------------------------- |
| Layout/nav/branding | `force-app/main/default/webapplications/<appName>/src/appLayout.tsx` |
| Document title | `force-app/main/default/webapplications/<appName>/index.html` |
| Root page content | Component at root route (often `Home` in `routes.tsx`) |
# Frontend Aesthetics
**Avoid AI slop.** Make creative, distinctive frontends:
- **Typography:** Avoid Inter, Roboto, Arial, Space Grotesk as defaults. Choose distinctive fonts.
- **Color:** Use cohesive color with sharp accents via CSS variables. Avoid purple-on-white clichés.
- **Motion:** Use high-impact motion (e.g. staggered reveals).
- **Depth:** Add atmosphere/depth in backgrounds.
# Shell Command Safety (MUST FOLLOW)
**Never use complex `node -e` one-liners** for file edits or multi-line transforms. They break in Zsh due to `!` history expansion and backtick interpolation. Use a temporary `.js` file, `sed`/`awk`, `jq`, or IDE file-editing tools instead.
# Development Cycle
- Execute tasks continuously until all planned items complete in the current iteration.
- Maintain a running checklist and proceed sequentially.
## Stop Conditions
Only stop when:
- All checklist items are completed and quality gates pass, or
- A blocking error cannot be resolved after reasonable remediation, or
- The user explicitly asks to pause.

View File

@ -1,5 +1,5 @@
---
name: salesforce-webapp-ui-ux
name: salesforce-webapp-designing-webapp-ui-ux
description: Use when editing any UI code in the web application — styling, layout, design, appearance, Tailwind, shadcn, colors, typography, icons, or visual/UX changes. Comprehensive design guide and searchable database for React + Tailwind + shadcn/ui (styles, color palettes, font pairings, UX guidelines, chart types).
---
@ -35,7 +35,7 @@ Extract key information from user request:
**Always start with `--design-system`** to get comprehensive recommendations with reasoning:
```bash
node skills/ui-ux/scripts/search.js "<product_type> <industry> <keywords>" --design-system [-p "Project Name"]
node skills/designing-webapp-ui-ux/scripts/search.js "<product_type> <industry> <keywords>" --design-system [-p "Project Name"]
```
This command:
@ -46,7 +46,7 @@ This command:
**Example:**
```bash
node skills/ui-ux/scripts/search.js "beauty spa wellness service" --design-system -p "Serenity Spa"
node skills/designing-webapp-ui-ux/scripts/search.js "beauty spa wellness service" --design-system -p "Serenity Spa"
```
### Step 2b: Persist Design System (Master + Overrides Pattern)
@ -54,7 +54,7 @@ node skills/ui-ux/scripts/search.js "beauty spa wellness service" --design-syste
To save the design system for hierarchical retrieval across sessions, add `--persist`:
```bash
node skills/ui-ux/scripts/search.js "<query>" --design-system --persist -p "Project Name"
node skills/designing-webapp-ui-ux/scripts/search.js "<query>" --design-system --persist -p "Project Name"
```
This creates:
@ -63,7 +63,7 @@ This creates:
**With page-specific override:**
```bash
node skills/ui-ux/scripts/search.js "<query>" --design-system --persist -p "Project Name" --page "dashboard"
node skills/designing-webapp-ui-ux/scripts/search.js "<query>" --design-system --persist -p "Project Name" --page "dashboard"
```
This also creates:
@ -79,7 +79,7 @@ This also creates:
After getting the design system, use domain searches to get additional details:
```bash
node skills/ui-ux/scripts/search.js "<keyword>" --domain <domain> [-n <max_results>]
node skills/designing-webapp-ui-ux/scripts/search.js "<keyword>" --domain <domain> [-n <max_results>]
```
**When to use detailed searches:**
@ -97,7 +97,7 @@ node skills/ui-ux/scripts/search.js "<keyword>" --domain <domain> [-n <max_resul
Get implementation-specific best practices for React, Tailwind, or shadcn/ui:
```bash
node skills/ui-ux/scripts/search.js "<keyword>" --stack shadcn
node skills/designing-webapp-ui-ux/scripts/search.js "<keyword>" --stack shadcn
```
Available stacks: `html-tailwind`, `react`, `shadcn`
@ -143,7 +143,7 @@ Available stacks: `html-tailwind`, `react`, `shadcn`
### Step 2: Generate Design System (REQUIRED)
```bash
node skills/ui-ux/scripts/search.js "beauty spa wellness service elegant" --design-system -p "Serenity Spa"
node skills/designing-webapp-ui-ux/scripts/search.js "beauty spa wellness service elegant" --design-system -p "Serenity Spa"
```
**Output:** Complete design system with pattern, style, colors, typography, effects, and anti-patterns.
@ -152,17 +152,17 @@ node skills/ui-ux/scripts/search.js "beauty spa wellness service elegant" --desi
```bash
# Get UX guidelines for animation and accessibility
node skills/ui-ux/scripts/search.js "animation accessibility" --domain ux
node skills/designing-webapp-ui-ux/scripts/search.js "animation accessibility" --domain ux
# Get alternative typography options if needed
node skills/ui-ux/scripts/search.js "elegant luxury serif" --domain typography
node skills/designing-webapp-ui-ux/scripts/search.js "elegant luxury serif" --domain typography
```
### Step 4: Stack Guidelines
```bash
node skills/ui-ux/scripts/search.js "layout responsive form" --stack html-tailwind
node skills/ui-ux/scripts/search.js "form dialog" --stack shadcn
node skills/designing-webapp-ui-ux/scripts/search.js "layout responsive form" --stack html-tailwind
node skills/designing-webapp-ui-ux/scripts/search.js "form dialog" --stack shadcn
```
**Then:** Synthesize design system + detailed searches and implement the design.
@ -175,10 +175,10 @@ The `--design-system` flag supports two output formats:
```bash
# ASCII box (default) - best for terminal display
node skills/ui-ux/scripts/search.js "fintech crypto" --design-system
node skills/designing-webapp-ui-ux/scripts/search.js "fintech crypto" --design-system
# Markdown - best for documentation
node skills/ui-ux/scripts/search.js "fintech crypto" --design-system -f markdown
node skills/designing-webapp-ui-ux/scripts/search.js "fintech crypto" --design-system -f markdown
```
---

View File

Can't render this file because it contains an unexpected character in line 4 and column 188.

View File

@ -0,0 +1,160 @@
---
name: salesforce-webapp-exploring-graphql-schema
description: Explore the Salesforce GraphQL schema via grep-only lookups. Use before generating any GraphQL query — schema exploration must complete first.
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.graphql"
---
# Salesforce GraphQL Schema Exploration
Guidance for AI agents working with the Salesforce GraphQL API schema. **GREP ONLY** — the schema file is very large (~265,000+ lines). All lookups MUST use grep; do NOT open, read, stream, or parse the file.
## Schema File Location
**Location:** `schema.graphql` at the **SFDX project root** (NOT inside the webapp dir). All grep commands **must be run from the project root** where `schema.graphql` lives.
> ⚠️ **Important (Access Policy - GREP ONLY)**: Do NOT open, view, stream, paginate, or parse the schema with any tool other than grep. All lookups MUST be done via grep using anchored patterns with minimal context as defined below.
If the file is not present, generate it by running (from the **webapp dir**, not the project root):
```bash
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
npm run graphql:schema
```
**BEFORE generating any GraphQL query, you MUST:**
1. **Check if schema exists**: Look for `schema.graphql` in the **SFDX project root**
2. **If schema is missing**:
- `cd` to the **webapp dir** and run `npm run graphql:schema` to download it
- Wait for the command to complete successfully
- Then proceed with grep-only lookups as defined below.
3. **If schema exists**: Proceed with targeted searches as described below
> ⚠️ **DO NOT** generate GraphQL queries without first having access to the schema. Standard field assumptions may not match the target org's configuration.
## Schema Structure Overview
Main entry points: `Query { uiapi }` for reads; `Mutation { uiapi(input: ...) }` for creates/updates/deletes. Record queries use `uiapi.query.<ObjectName>`.
## Allowed Lookups (grep-only)
Use ONLY these grep commands to locate specific definitions in schema.graphql. Do not use editors (VS Code/vim/nano), cat/less/more/head/tail, or programmatic parsers (node/python/awk/sed/jq).
- Always include:
- `-n` (line numbers) and `-E` (extended regex)
- Anchors (`^`) and word boundaries (`\b`)
- Minimal context with `-A N` (prefer the smallest N that surfaces the needed lines)
### 1. Find Available Fields for a Record Type
Search for `type <ObjectName> implements Record` to find all queryable fields:
```bash
# Example: Find Account fields (anchored, minimal context)
grep -nE '^type[[:space:]]+Account[[:space:]]+implements[[:space:]]+Record\b' ./schema.graphql -A 60
```
### 2. Find Filter Options for a Record Type
Search for `input <ObjectName>_Filter` to find filterable fields and operators:
```bash
# Example: Find Account filter options (anchored)
grep -nE '^input[[:space:]]+Account_Filter\b' ./schema.graphql -A 40
```
### 3. Find OrderBy Options
Search for `input <ObjectName>_OrderBy` for sorting options:
```bash
# Example: Find Account ordering options (anchored)
grep -nE '^input[[:space:]]+Account_OrderBy\b' ./schema.graphql -A 30
```
### 4. Find Mutation Operations
Search for operations in `UIAPIMutations`:
```bash
# Example: Find Account mutations (extended regex)
grep -nE 'Account.*(Create|Update|Delete)' ./schema.graphql
```
### 5. Find Input Types for Mutations
Search for `input <ObjectName>CreateInput` or `input <ObjectName>UpdateInput`:
```bash
# Example: Find Account create input (anchored)
grep -nE '^input[[:space:]]+AccountCreateInput\b' ./schema.graphql -A 30
```
## Common Operator Types
- **StringOperators**: `eq`, `ne`, `like`, `lt`, `gt`, `lte`, `gte`, `in`, `nin`
- **OrderByClause**: `order: ResultOrder` (ASC/DESC), `nulls: NullOrder` (FIRST/LAST)
## Field Value Wrappers
Salesforce GraphQL returns field values wrapped in typed objects:
| Wrapper Type | Access Pattern |
| --------------- | ---------------------------------- |
| `StringValue` | `FieldName { value }` |
| `IntValue` | `FieldName { value }` |
| `BooleanValue` | `FieldName { value }` |
| `DateTimeValue` | `FieldName { value displayValue }` |
| `PicklistValue` | `FieldName { value displayValue }` |
| `CurrencyValue` | `FieldName { value displayValue }` |
## Agent Workflow for Building Queries (grep-only)
**Pre-requisites (MANDATORY):**
- [ ] Verified `schema.graphql` exists in the **SFDX project root**
- [ ] If missing, ran `npm run graphql:schema` from the **webapp dir** and waited for completion
- [ ] Confirmed connection to correct Salesforce org (if downloading fresh schema)
**Workflow Steps:**
1. **Identify the target object** (e.g., Account, Contact, Opportunity)
2. **Run the "Find Available Fields" grep command** for your object (copy only the field names visible in the grep output; do not open the file)
3. **Run the "Find Filter Options" grep command** (`<Object>_Filter`) to understand filtering options
4. **Run the "Find OrderBy Options" grep command** (`<Object>_OrderBy`) for sorting capabilities
5. **Build the query** following the patterns in the `generating-graphql-read-query` or `generating-graphql-mutation-query` skill using only values returned by grep
6. **Validate field names** using grep matches (case-sensitive). Do not open or parse the file beyond grep.
## Tips for Agents
- **Always verify field names** by running the specific grep commands; do not open the schema file
- **Use grep with anchors and minimal -A context** to explore the schema efficiently—never read or stream the file
- **Check relationships** by looking for `parentRelationship` and `childRelationship` comments in type definitions
- **Look for Connection types** (e.g., `AccountConnection`) via grep to understand pagination structure
- **Custom objects** end with `__c` (e.g., `CustomObject__c`)
- **Custom fields** also end with `__c` (e.g., `Custom_Field__c`)
## Forbidden Operations
To prevent accidental large reads, the following are prohibited for schema.graphql:
- Opening in any editor (VS Code, vim, nano)
- Using cat, less, more, head, or tail
- Programmatic parsing (node, python, awk, sed, jq)
- Streaming or paginating through large portions of the file
If any of the above occurs, stop and replace the action with one of the Allowed Lookups (grep-only).
## Output Minimization
- Prefer precise, anchored patterns with word boundaries
- Use the smallest `-A` context that surfaces required lines
- If results are noisy, refine the regex rather than increasing context
## Related Skills
- For generating read queries, invoke the `generating-graphql-read-query` skill
- For generating mutation queries, invoke the `generating-graphql-mutation-query` skill

View File

@ -1,155 +0,0 @@
---
name: salesforce-webapp-feature-graphql-data-access
description: Add or modify Salesforce GraphQL data access code. Use when the user asks to fetch, query, or mutate Salesforce data, or add a GraphQL operation for an object like Account, Contact, or Opportunity.
---
# GraphQL Data Access
Add or modify Salesforce GraphQL data access code using `getDataSDK()` + `data.graphql?.()` and codegen tooling.
## When to Use
- User asks to "fetch data from Salesforce"
- User asks to "query" or "mutate" Salesforce records
- User wants to add a new GraphQL operation (query or mutation)
- User asks to add data access for a Salesforce object (Account, Contact, Opportunity, etc.)
## Prerequisites
The base React app (`base-react-app`) ships with all GraphQL dependencies and tooling pre-configured:
- `@salesforce/sdk-data` — runtime SDK for `getDataSDK` and `gql`
- `@graphql-codegen/cli` + plugins — type generation from `.graphql` files and inline `gql` queries
- `@graphql-eslint/eslint-plugin` — linting for `.graphql` files and `gql` template literals
- `graphql` — shared by codegen, ESLint, and schema introspection
Before using this skill, ensure:
1. The `@salesforce/sdk-data` package is available (provides `getDataSDK`, `gql`, `NodeOfConnection`)
2. A `schema.graphql` file exists at the project root. If missing, generate it:
```bash
npm run graphql:schema
```
## npm Scripts
The base app provides two npm scripts for GraphQL tooling:
- **`npm run graphql:schema`** — Downloads the full GraphQL schema from a connected Salesforce org via introspection. Outputs `schema.graphql` to the project root.
- **`npm run graphql:codegen`** — Generates TypeScript types from `.graphql` files and inline `gql` queries. Outputs to `src/api/graphql-operations-types.ts`.
## Workflow
### Step 1: Explore the Schema
Before writing any query, verify the target object and its fields exist in the schema.
See `docs/explore-schema.md` for detailed guidance on exploring the Salesforce GraphQL schema.
Key actions:
- Search `schema.graphql` for `type <ObjectName> implements Record` to find available fields
- Search for `input <ObjectName>_Filter` for filter options
- Search for `input <ObjectName>_OrderBy` for sorting options
- For mutations: search for `input <ObjectName>CreateInput` or `<ObjectName>UpdateInput`
### Step 2: Choose the Query Pattern
**Pattern 1 — External `.graphql` file** (recommended for complex queries):
- Queries with variables, fragments, or shared across files
- Enables full codegen type generation
- See example: `api/utils/accounts.ts` + `api/utils/query/highRevenueAccountsQuery.graphql`
**Pattern 2 — Inline `gql` tag** (recommended for simple queries):
- Simple queries without variables
- Colocated with usage code
- See example: `api/utils/user.ts`
### Step 3: Write the Query
For **Pattern 1**:
1. Create a `.graphql` file under `src/api/utils/query/`
2. Follow UIAPI structure: `query { uiapi { query { ObjectName(...) { edges { node { ... } } } } } }`
3. For mutations, see `docs/generate-mutation-query.md`
4. For read queries, see `docs/generate-read-query.md`
For **Pattern 2**:
1. Define query inline using the `gql` template tag
2. Ensure the query name matches what codegen expects
### Step 4: Generate Types
```bash
npm run graphql:codegen
```
This updates `src/api/graphql-operations-types.ts` with:
- `<OperationName>Query` or `<OperationName>Mutation` — response type
- `<OperationName>QueryVariables` or `<OperationName>MutationVariables` — input variables type
### Step 5: Implement the Data Access Function
```typescript
// Pattern 1
import { getDataSDK, type NodeOfConnection } from "@salesforce/sdk-data";
import MY_QUERY from "./query/myQuery.graphql?raw";
import type { GetMyDataQuery, GetMyDataQueryVariables } from "../graphql-operations-types";
type MyNode = NodeOfConnection<GetMyDataQuery["uiapi"]["query"]["MyObject"]>;
export async function getMyData(variables: GetMyDataQueryVariables): Promise<MyNode[]> {
const data = await getDataSDK();
const response = await data.graphql?.<GetMyDataQuery, GetMyDataQueryVariables>(
MY_QUERY,
variables,
);
if (response?.errors?.length) {
const errorMessages = response.errors.map((e) => e.message).join("; ");
throw new Error(`GraphQL Error: ${errorMessages}`);
}
return response?.data?.uiapi?.query?.MyObject?.edges?.map((edge) => edge?.node) || [];
}
```
```typescript
// Pattern 2
import { getDataSDK, gql } from "@salesforce/sdk-data";
import type { MySimpleQuery } from "../graphql-operations-types";
const MY_QUERY = gql`
query MySimple {
uiapi { ... }
}
`;
export async function getSimpleData(): Promise<SomeType> {
const data = await getDataSDK();
const response = await data.graphql?.<MySimpleQuery>(MY_QUERY);
// check response.errors, then extract response.data
}
```
### Step 6: Verify
- [ ] Query field names match the schema exactly (case-sensitive)
- [ ] Response type generic is provided to `data.graphql?.<T>()`
- [ ] Optional chaining is used for nested response data
- [ ] Pattern 1: `.graphql` file imported with `?raw` suffix
- [ ] Pattern 2: Query uses `gql` tag (not plain string)
- [ ] Generated types imported from `graphql-operations-types.ts`
## Reference
- Schema exploration: `docs/explore-schema.md`
- Read query generation: `docs/generate-read-query.md`
- Mutation query generation: `docs/generate-mutation-query.md`
- Shared GraphQL schema types: `docs/shared-schema.graphqls`
- Schema download: `npm run graphql:schema` (in the base app)
- Type generation: `npm run graphql:codegen` (in the base app)

View File

@ -1,256 +0,0 @@
# GraphQL Schema Reference
This document provides guidance for AI agents working with the Salesforce GraphQL API schema in this project.
## Schema File Location
**The complete GraphQL schema is located at: `@schema.graphql`** (in the project root)
> ⚠️ **Important**: The schema file is very large (~265,000+ lines). Do NOT read it entirely. Instead, use targeted searches to find specific types, fields, or operations.
If the file is not present, generate it by running:
```bash
npm run graphql:schema
```
## Required Pre-Flight Check
**BEFORE generating any GraphQL query, you MUST:**
1. **Check if schema exists**: Look for `schema.graphql` in the project root
2. **If schema is missing**:
- Run `npm run graphql:schema` to download it
- Wait for the command to complete successfully
- Then proceed with schema exploration
3. **If schema exists**: Proceed with targeted searches as described below
> ⚠️ **DO NOT** generate GraphQL queries without first having access to the schema. Standard field assumptions may not match the target org's configuration.
## Schema Structure Overview
The schema follows the Salesforce GraphQL Wire Adapter pattern with these main entry points:
### Query Entry Point
```graphql
type Query {
uiapi: UIAPI!
}
```
### UIAPI Structure
```graphql
type UIAPI {
query: RecordQuery! # For querying records
aggregate: RecordQueryAggregate! # For aggregate queries
objectInfos: [ObjectInfo] # For metadata
relatedListByName: RelatedListInfo
}
```
### Mutation Entry Point
```graphql
type Mutation {
uiapi(input: UIAPIMutationsInput): UIAPIMutations!
}
```
## How to Explore the Schema
When you need to build a GraphQL query, use these search patterns:
### 1. Find Available Fields for a Record Type
Search for `type <ObjectName> implements Record` to find all queryable fields:
```bash
# Example: Find Account fields
grep "^type Account implements Record" schema.graphql -A 50
```
### 2. Find Filter Options for a Record Type
Search for `input <ObjectName>_Filter` to find filterable fields and operators:
```bash
# Example: Find Account filter options
grep "^input Account_Filter" schema.graphql -A 30
```
### 3. Find OrderBy Options
Search for `input <ObjectName>_OrderBy` for sorting options:
```bash
# Example: Find Account ordering options
grep "^input Account_OrderBy" schema.graphql -A 20
```
### 4. Find Mutation Operations
Search for operations in `UIAPIMutations`:
```bash
# Example: Find Account mutations
grep "Account.*Create\|Account.*Update\|Account.*Delete" schema.graphql
```
### 5. Find Input Types for Mutations
Search for `input <ObjectName>CreateInput` or `input <ObjectName>UpdateInput`:
```bash
# Example: Find Account create input
grep "^input AccountCreateInput" schema.graphql -A 30
```
## Common Operator Types
### StringOperators (for text fields)
```graphql
input StringOperators {
eq: String # equals
ne: String # not equals
like: String # pattern matching (use % as wildcard)
lt: String # less than
gt: String # greater than
lte: String # less than or equal
gte: String # greater than or equal
in: [String] # in list
nin: [String] # not in list
}
```
### OrderByClause
```graphql
input OrderByClause {
order: ResultOrder # ASC or DESC
nulls: NullOrder # FIRST or LAST
}
```
## Query Pattern Examples
### Basic Query Structure
All record queries follow this pattern:
```graphql
query {
uiapi {
query {
<ObjectName>(
first: Int # pagination limit
after: String # pagination cursor
where: <Object>_Filter
orderBy: <Object>_OrderBy
) {
edges {
node {
Id
<Field> { value }
# ... more fields
}
}
}
}
}
}
```
### Example: Query Accounts with Filter
```graphql
query GetHighRevenueAccounts($minRevenue: Currency) {
uiapi {
query {
Account(
where: { AnnualRevenue: { gt: $minRevenue } }
orderBy: { AnnualRevenue: { order: DESC } }
first: 50
) {
edges {
node {
Id
Name {
value
}
AnnualRevenue {
value
}
Industry {
value
}
}
}
}
}
}
}
```
### Mutation Pattern
```graphql
mutation CreateAccount($input: AccountCreateInput!) {
uiapi(input: { AccountCreate: { input: $input } }) {
AccountCreate {
Record {
Id
Name {
value
}
}
}
}
}
```
## Field Value Wrappers
Salesforce GraphQL returns field values wrapped in typed objects:
| Wrapper Type | Access Pattern |
| --------------- | ---------------------------------- |
| `StringValue` | `FieldName { value }` |
| `IntValue` | `FieldName { value }` |
| `BooleanValue` | `FieldName { value }` |
| `DateTimeValue` | `FieldName { value displayValue }` |
| `PicklistValue` | `FieldName { value displayValue }` |
| `CurrencyValue` | `FieldName { value displayValue }` |
## Agent Workflow for Building Queries
**Pre-requisites (MANDATORY):**
- [ ] Verified `schema.graphql` exists in project root
- [ ] If missing, ran `npm run graphql:schema` and waited for completion
- [ ] Confirmed connection to correct Salesforce org (if downloading fresh schema)
**Workflow Steps:**
1. **Identify the target object** (e.g., Account, Contact, Opportunity)
2. **Search the schema** for the object type to discover available fields
3. **Search for filter input** (`<Object>_Filter`) to understand filtering options
4. **Search for orderBy input** (`<Object>_OrderBy`) for sorting capabilities
5. **Build the query** following the patterns above
6. **Validate field names** match exactly as defined in the schema (case-sensitive)
## Tips for Agents
- **Always verify field names** by searching the schema before generating queries
- **Use grep/search** to explore the schema efficiently—never read the entire file
- **Check relationships** by looking for `parentRelationship` and `childRelationship` comments in type definitions
- **Look for Connection types** (e.g., `AccountConnection`) to understand pagination structure
- **Custom objects** end with `__c` (e.g., `CustomObject__c`)
- **Custom fields** also end with `__c` (e.g., `Custom_Field__c`)
## Related Documentation
- For generating mutations and queries, see `generate-mutation-query.md`
- For generating read queries, see `generate-read-query.md`

View File

@ -1,202 +0,0 @@
# GraphQL Read Query Generation
**Triggering conditions**
1. Only if the schema exploration phase completed successfully
2. Only if the query to generate is a read query
## Your Role
You are a GraphQL expert and your role is to help generate Salesforce compatible GraphQL read queries once the exploration phase has completed.
You will leverage the context provided by the requesting user as well as the validation phase provided by the schema exploration. This tool will also provide you with a method to dynamically query the target org instance that you will use to test the generated query.
If the schema exploration has not been executed yet, you **MUST** run it first, and then get back to read query generation.
## Read Query Generation Workflow
Strictly follow the rules below when generating the GraphQL read query:
1. **No Proliferation** - Only generate for the explicitly requested fields, nothing else
2. **Unique Query** - Leverage child relationships to query entities in one single query
3. **Navigate Entities** - Always use `relationshipName` to access reference fields and child entities
1. **Exception** - if the `relationshipName` field is null, you can't navigate the related entity, and will have to return the `Id` itself
4. **Leverage Fragments** - Generate one fragment per possible type on polymorphic fields (field with `dataType="REFERENCE"` and more than one entry in `referenceToInfos` introspection attribute)
5. **Type Consistency** - Make sure variables used as query arguments and their related fields share the same GraphQL type
6. **Type Enforcement** - Make sure to leverage field type information from introspection and GraphQL schema to generate field access
7. **Semi and anti joins** - Use the semi-join or anti-join templates to filter an entity with conditions on child entities
8. **Query Generation** - Use the [template](#read-query-template) to generate the query
9. **Output Format** - Use the [standalone](#read-standalone-default-output-format---clean-code-only)
10. **Test the Query** - Use the [Generated Read Query Testing](#generated-read-query-testing) workflow to test the generated query
1. **Report First** - Always report first, using the proper output format, before testing
## Read Query Template
```graphql
query QueryName {
uiapi {
query {
EntityName(
# conditions here
) {
edges {
node {
# Direct fields
FieldName { value }
# Non-polymorphic reference (single type)
RelationshipName {
Id
Name { value }
}
# Polymorphic reference (multiple types)
PolymorphicRelationshipName {
...TypeAInfo
...TypeBInfo
}
# Child relationship (subquery)
RelationshipName(
# conditions here
) {
edges {
node {
# fields
}
}
}
}
}
}
}
}
}
fragment TypeAInfo on TypeA {
Id
SpecificFieldA { value }
}
fragment TypeBInfo on TypeB {
Id
SpecificFieldB { value }
}
```
## Semi-Join and Anti-Join Condition Template
Semi-joins (resp. anti-joins) condition leverage parent-child relationships and allow filtering the parent entity using a condition on child entities.
This is a standard `where` condition, on the parent entity's `Id`, expressed using the `inq` (resp. `ninq`, i.e. not `inq`) operator. This operator accepts two attributes:
- The child entity camelcase name to apply the condition on, with a value expressing the condition
- The field name on the child entity containing the parent entity `Id`, which is the `fieldName` from the `childRelationships` information for the child entity
- If the only condition is related child entity existence, you can use an `Id: { ne: null }` condition
### Semi-Join Example - ParentEntity with at least one Matching ChildEntity
```graphql
query testSemiJoin {
uiapi {
query {
ParentEntity(
where: {
Id: {
inq: {
ChildEntity: {
# standard conditions here
Name: { like: "test%" }
Type: { eq: "some value" }
}
ApiName: "parentIdFieldInChild"
}
}
}
) {
edges {
node {
Id
Name {
value
}
}
}
}
}
}
}
```
### Anti-Join Example - ParentEntity with no Matching ChildEntity
Same example as the [Semi-Join Example](#semi-join-example---parententity-with-at-least-one-matching-childentity), but replacing the `inq` operator by the `ninq` one.
## Read Standalone (Default) Output Format - CLEAN CODE ONLY
```javascript
const QUERY_NAME = `
query GetData {
// query here
}
`;
const QUERY_VARIABLES = {
// variables here
};
```
**❌ DO NOT INCLUDE:**
- Explanatory comments about the query
- Field descriptions
- Additional text about what the query does
- Workflow step descriptions
**✅ ONLY INCLUDE:**
- Raw query string
- Variables object
- Nothing else
## Generated Read Query Testing
**Triggering conditions** - **ALL CONDITIONS MUST VALIDATE\***
1. Only if the [Read Query Generation Workflow](#read-query-generation-workflow) step global status is `SUCCESS` and you have a generated query
2. Only if the query to generate is a read query
3. Only if non manual method was used during schema exploration to retrieve introspection data
**Workflow**
1. **Report Step** - Explain that you are able to test the query using `sf api request rest`
2. **Interactive Step** - Ask the user whether they want you to test the query
1. **WAIT** for the user's answer.
3. **Test Query** - If the user are OK with you testing the query:
1. Use `sf api request rest` to POST the query to the GraphQL endpoint:
```bash
sf api request rest /services/data/v65.0/graphql \
--method POST \
--body '{"query":"query GetData { uiapi { query { EntityName { edges { node { Id } } } } } }"}'
```
2. Replace `v65.0` with the API version of the target org
3. Replace the `query` value with the generated read query string
4. If the query uses variables, include them in the JSON body as a `variables` key
5. Report the result of the test as `SUCCESS` if the query executed without error, or `FAILED` if you got errors
6. If the query executed without any errors, but you received no data, then the query is valid, and the result of the test is `SUCCESS`
4. **Remediation Step** - If status is `FAILED`, use the [`FAILED` status handling workflows](#failed-status-handling-workflow)
### `FAILED` Status Handling Workflow
The query is invalid:
1. **Error Analysis** - Parse and categorize the specific error messages
2. **Root Cause Identification** - Use error message to identify the root cause:
- **Syntax** - Error contains `invalid syntax`
- **Validation** - Error contains `validation error`
- **Type** - Error contains `VariableTypeMismatch` or `UnknownType`
3. **Targeted Resolution** - Depending on the root cause categorization
- **Syntax** - Update the query using the error message information to fix the syntax errors
- **Validation** - This field's name is most probably invalid, ask user for clarification and **WAIT** for the user's answer
- **Type** - Use the error details and GraphQL schema to correct argument's type, and adjust variables accordingly
4. **Test Again** - Resume the [query testing workflow](#generated-read-query-testing) with the updated query (increment and track attempt counter)
5. **Escalation Path** - If targeted resolution fails after 2 attempts, ask for additional details and restart the entire GraphQL workflow, going again through the introspection phase

View File

@ -1,5 +1,5 @@
---
name: salesforce-webapp-feature-micro-frontend
name: salesforce-webapp-feature-micro-frontend-generating-micro-frontend-lwc
description: Generate a Micro Frontend LWC component for a Web Application.
license: Proprietary. LICENSE.txt has complete terms
metadata:

View File

@ -1,167 +0,0 @@
---
name: salesforce-webapp-feature-react-agentforce-conversation-client-embedded-agent
description: Embed an Agentforce conversation client (chat UI) into a React web application. Use when the user wants to add an employee agent, a chat client, chatbot, chat widget, chat component, conversation client, or conversational interface to their React app. Also applies when the user asks to embed or integrate any Salesforce agent — including employee agent, travel agent, HR agent, or any custom-named agent — or mentions Agentforce, Agentforce widget, Agentforce chat, or agent chat. ALWAYS use this skill instead of building a chat UI from scratch. Do NOT generate custom chat components, use third-party chat libraries, or create WebSocket/REST chat implementations. Do NOT use for non-React contexts or Lightning Web Components without React.
---
# Embedded Agentforce chat (workflow)
When the user wants an embedded Agentforce chat client in a React app, follow this workflow.
## DO NOT build a chat UI from scratch
When the user asks for a chat UI, chat widget, chatbot, conversational interface, agent embed, or anything related to an embedded agent — **always use the `AgentforceConversationClient` component** from `@salesforce/webapp-template-feature-react-agentforce-conversation-client-experimental`.
**Never do any of the following:**
- Build a custom chat component from scratch (no custom message bubbles, input boxes, or chat layouts)
- Use third-party chat libraries (e.g. `react-chat-widget`, `stream-chat`, `chatscope`, or similar)
- Create WebSocket, polling, or REST-based chat implementations
- Generate custom HTML/CSS chat UIs
- Write a wrapper around `embedAgentforceClient` directly — always use the provided React component
If the user asks for chat functionality that goes beyond what `AgentforceConversationClient` supports (e.g. custom message rendering, message history, typing indicators), explain that the embedded Agentforce client handles all of this internally and cannot be customized beyond the supported `agentforceClientConfig` options (`renderingConfig`, `styleTokens`, `agentId`).
## CRITICAL: Agent ID is required
The Agentforce Conversation Client **will not work** without an `agentId`. There is no default agent — the component renders nothing and silently fails if `agentId` is missing. **Always ask the user for their agent ID before writing any code.**
> **Before proceeding:** Ask the user for their Salesforce agent ID (18-character record ID starting with `0Xx`). If they do not have one, direct them to **Setup → Agents** in their Salesforce org to find or create one. Do not generate code without an `agentId`.
## 1. Collect the agent ID
Ask the user:
- "What is your Salesforce agent ID? (You can find it in Setup → Agents → select an agent → copy the ID from the URL. It's an 18-character ID starting with `0Xx`.)"
If the user does not provide one:
- Explain that the conversation client **requires** an agent ID and will not function without it.
- Direct them to **Setup → Agents** in their org.
- Do **not** proceed to generate the embed code until an agent ID is provided.
## 2. Install the package
```bash
npm install @salesforce/webapp-template-feature-react-agentforce-conversation-client-experimental
```
This single install also brings in `@salesforce/agentforce-conversation-client` (the underlying SDK) automatically.
## 3. Use the shared wrapper
Use the `AgentforceConversationClient` React component. It resolves auth automatically:
- **Dev (localhost)**: fetches `frontdoorUrl` from `/__lo/frontdoor`
- **Prod (hosted in org)**: uses `salesforceOrigin` from `window.location.origin`
## 4. Embed in the layout
Render `<AgentforceConversationClient />` in the app layout so the chat client loads globally. Keep it alongside the existing layout (do not replace the page shell). **Always pass `agentId`.**
```tsx
import { Outlet } from "react-router";
import { AgentforceConversationClient } from "@salesforce/webapp-template-feature-react-agentforce-conversation-client-experimental";
export default function AppLayout() {
return (
<>
<Outlet />
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
}}
/>
</>
);
}
```
Replace `"0Xx000000000000AAA"` with the agent ID provided by the user.
## 5. Configure rendering and theming (optional)
Pass additional options via the `agentforceClientConfig` prop:
| Option | Purpose | Required |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------- | -------- |
| `agentId` | The agent to load — **required, will not work without it** | **Yes** |
| `renderingConfig.mode` | `"floating"` (default) or `"inline"` | No |
| `renderingConfig.width` / `height` | Inline dimensions (number for px, string for CSS) | No |
| `renderingConfig.headerEnabled` | Show or hide the chat header bar. Defaults to `false` (header hidden). Set to `true` to show the header. | No |
| `styleTokens` | Theme colors and style overrides | No |
See [embed-examples.md](docs/embed-examples.md) for complete examples of each mode.
## 6. Validate prerequisites
Before the conversation client will work, the user must verify all of the following in their Salesforce org:
1. **Agent is active:** The org must have the agent referenced by `agentId` in an **Active** state and deployed to the correct channel (**Setup → Agents**).
2. **Trusted domains:** The org must allow `localhost:<PORT>` in **Trusted Domains for Inline Frames** (**Setup → Session Settings → Trusted Domains for Inline Frames**). Required for local development.
3. **First-party cookies disabled:** **"Require first party use of Salesforce cookies"** must be **unchecked/disabled** in **Setup → My Domain**. If this setting is enabled, the embedded conversation client will fail to authenticate and will not load.
## Quick reference: rendering modes
### Floating (default rendering mode)
A persistent chat widget overlay pinned to the bottom-right corner. Floating is the default rendering mode — but `agentId` is still required.
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
}}
/>
```
### Inline
The chat renders within the page layout at a specific size.
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
renderingConfig: { mode: "inline", width: 420, height: 600 },
}}
/>
```
### Inline — with header
By default the header is hidden. To show the chat header bar (with agent name and controls), set `headerEnabled: true`:
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
renderingConfig: {
mode: "inline",
width: 420,
height: 600
headerEnabled: true,
},
}}
/>
```
### Theming
Use `styleTokens` to customize the chat appearance.
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
styleTokens: {
headerBlockBackground: "#0176d3",
headerBlockTextColor: "#ffffff",
messageBlockInboundColor: "#0176d3",
},
}}
/>
```
## Troubleshooting
If the chat widget does not appear, fails to authenticate, or behaves unexpectedly, see [troubleshooting.md](docs/troubleshooting.md).

View File

@ -1,192 +0,0 @@
# Embed examples
Detailed examples for configuring the Agentforce Conversation Client. All examples use the `AgentforceConversationClient` React component; the underlying `embedAgentforceClient` API accepts the same `agentforceClientConfig` shape.
> **Important:** Every example requires an `agentId`. The component will not render without one. There is no default agent. Replace `"0Xx000000000000AAA"` in every example with the user's actual agent ID.
---
## Floating mode (default rendering mode)
A floating chat widget appears in the bottom-right corner. It starts minimized and expands when the user clicks it. Floating is the default rendering mode — no `renderingConfig` is needed — but `agentId` is always required.
### Minimal
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
}}
/>
```
### Explicit floating
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
renderingConfig: { mode: "floating" },
}}
/>
```
### Floating with theming
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
renderingConfig: { mode: "floating" },
styleTokens: {
headerBlockBackground: "#032D60",
headerBlockTextColor: "#ffffff",
},
}}
/>
```
---
## Inline mode
The chat renders inside the parent container at a specific size. Use this when the chat should be part of the page layout rather than an overlay.
### Fixed pixel dimensions
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
renderingConfig: { mode: "inline", width: 420, height: 600 },
}}
/>
```
### CSS string dimensions
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
renderingConfig: { mode: "inline", width: "100%", height: "80vh" },
}}
/>
```
### Inline filling a sidebar
```tsx
<div style={{ display: "flex", height: "100vh" }}>
<main style={{ flex: 1 }}>{/* App content */}</main>
<aside style={{ width: 400 }}>
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
renderingConfig: { mode: "inline", width: "100%", height: "100%" },
}}
/>
</aside>
</div>
```
---
## Theming
Use `styleTokens` to customize colors. Tokens are passed directly to the Agentforce client.
### Brand-colored header
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
styleTokens: {
headerBlockBackground: "#0176d3",
headerBlockTextColor: "#ffffff",
},
}}
/>
```
### Full theme example
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
styleTokens: {
headerBlockBackground: "#0176d3",
headerBlockTextColor: "#ffffff",
messageBlockInboundColor: "#0176d3",
},
}}
/>
```
### Dark theme example
```tsx
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
styleTokens: {
headerBlockBackground: "#1a1a2e",
headerBlockTextColor: "#e0e0e0",
messageBlockInboundColor: "#16213e",
},
}}
/>
```
---
## Full layout example
Shows the recommended pattern: agent ID passed directly, single render in the app layout.
```tsx
import { Outlet } from "react-router";
import { AgentforceConversationClient } from "@salesforce/webapp-template-feature-react-agentforce-conversation-client-experimental";
export default function AppLayout() {
return (
<>
<Outlet />
<AgentforceConversationClient
agentforceClientConfig={{
agentId: "0Xx000000000000AAA",
styleTokens: {
headerBlockBackground: "#0176d3",
headerBlockTextColor: "#ffffff",
},
}}
/>
</>
);
}
```
---
## Using the low-level `embedAgentforceClient` API
The React component wraps `embedAgentforceClient`. If you need the raw API (e.g. in a non-React context), the config shape is the same — `agentId` is still required:
```ts
import { embedAgentforceClient } from "@salesforce/agentforce-conversation-client";
const { loApp, chatClientComponent } = embedAgentforceClient({
container: "#agentforce-container",
salesforceOrigin: "https://myorg.my.salesforce.com",
agentforceClientConfig: {
agentId: "0Xx000000000000AAA",
renderingConfig: { mode: "floating" },
styleTokens: {
headerBlockBackground: "#0176d3",
headerBlockTextColor: "#ffffff",
},
},
});
```

View File

@ -1,51 +0,0 @@
# Troubleshooting
Common issues when using the Agentforce Conversation Client.
---
### Chat widget does not appear
**Cause:** Missing or invalid `agentId`. The component will not render anything without a valid agent ID.
**Solution:**
1. Verify `agentId` is passed in `agentforceClientConfig` — it is required
2. Confirm the ID is correct (18-character Salesforce record ID, starts with `0Xx`)
3. Check that the agent exists and is **Active** in **Setup → Agents**
### Chat loads but shows "agent not available"
**Cause:** The agent exists but is not deployed or is inactive.
**Solution:**
1. In **Setup → Agents**, ensure the agent status is **Active**
2. Verify the agent is deployed to the correct channel
### Authentication error on localhost
**Cause:** `localhost:<PORT>` is not in the org's trusted domains for inline frames.
**Solution:**
1. Go to **Setup → Session Settings → Trusted Domains for Inline Frames**
2. Add `localhost:<PORT>` (e.g. `localhost:3000`)
3. Restart the dev server
### Chat fails to authenticate / blank iframe
**Cause:** "Require first party use of Salesforce cookies" is enabled in the org's session settings. This blocks the embedded client from establishing a session.
**Solution:**
1. Go to **Setup → Session Settings**
2. Find **"Require first party use of Salesforce cookies"**
3. **Uncheck / disable** this setting
4. Save and reload the app
### Multiple chat widgets appear
**Cause:** `AgentforceConversationClient` is rendered in multiple places.
**Solution:** Render it once in the app layout, not on individual pages. The component uses a singleton pattern — only one instance should exist per window.

View File

@ -0,0 +1,92 @@
---
name: salesforce-webapp-feature-react-agentforce-conversation-client-integrating-agentforce-conversation-client
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.
---
# Embedded Agentforce chat (flat-prop API)
Use this workflow whenever the user wants add or update Agentforce chat in React.
## 1) Get agent id first
Ask for the Salesforce agent id (18-char id starting with `0Xx`). Do not proceed without it.
Placeholder convention for all examples in this file:
`<AgentforceConversationClient agentId="<USER_AGENT_ID_18_CHAR_0Xx...>" />`
## 2) Install package
```bash
npm install @salesforce/webapp-template-feature-react-agentforce-conversation-client-experimental
```
## 3) Use component in app layout
Render a single instance in the shared layout (alongside `<Outlet />`).
```tsx
import { Outlet } from "react-router";
import { AgentforceConversationClient } from "@salesforce/webapp-template-feature-react-agentforce-conversation-client-experimental";
export default function AppLayout() {
return (
<>
<Outlet />
<AgentforceConversationClient agentId="<USER_AGENT_ID_18_CHAR_0Xx...>" />
</>
);
}
```
## 4) Flat props only
This package uses a flat prop API. Use these props directly on the component:
- `agentId` (required in practice)
- `inline` (`true` = inline, omitted/false = floating)
- `headerEnabled` (defaults to true for floating; actual use case for inline mode)
- `width`, `height` (actual work is when inline mode is true)
- `styleTokens`
- `salesforceOrigin`, `frontdoorUrl`
## 5) Inline mode example
```tsx
<AgentforceConversationClient
agentId="<USER_AGENT_ID_18_CHAR_0Xx...>"
inline
width={420}
height={600}
/>
```
## 6) Theming example
```tsx
<AgentforceConversationClient
agentId="<USER_AGENT_ID_18_CHAR_0Xx...>"
styleTokens={{
headerBlockBackground: "#0176d3",
headerBlockTextColor: "#ffffff",
}}
/>
```
## 7) Do not do this
- Do not create custom chat UIs.
- Do not use third-party chat libraries.
- Do not call `embedAgentforceClient` directly from @salesforce/agentforce-conversation-client.
## 8) Prerequisites
Ensure org setup is valid:
1. Agent is active and deployed to the correct channel.
2. `localhost:<PORT>` is trusted for inline frames in local dev.
3. First-party Salesforce cookie restriction is disabled when required for embedding.
## Troubleshooting
If the chat widget does not appear, fails to authenticate, or behaves unexpectedly, see [troubleshooting.md](docs/troubleshooting.md).

View File

@ -0,0 +1,116 @@
# Embed examples (flat-prop API)
All examples use `AgentforceConversationClient` with flat props.
> `agentId` is required in practice. Use this placeholder pattern in examples: `"<USER_AGENT_ID_18_CHAR_0Xx...>"`.
---
## Floating mode (default)
```tsx
<AgentforceConversationClient agentId="<USER_AGENT_ID_18_CHAR_0Xx...>" />
```
## Explicit floating
```tsx
<AgentforceConversationClient agentId="<USER_AGENT_ID_18_CHAR_0Xx...>" />
```
---
## Inline mode
### Fixed pixels
```tsx
<AgentforceConversationClient
agentId="<USER_AGENT_ID_18_CHAR_0Xx...>"
inline
width={420}
height={600}
/>
```
### CSS string size
```tsx
<AgentforceConversationClient
agentId="<USER_AGENT_ID_18_CHAR_0Xx...>"
inline
width="100%"
height="80vh"
/>
```
### Inline sidebar
```tsx
<div style={{ display: "flex", height: "100vh" }}>
<main style={{ flex: 1 }}>{/* App content */}</main>
<aside style={{ width: 400 }}>
<AgentforceConversationClient
agentId="<USER_AGENT_ID_18_CHAR_0Xx...>"
inline
width="100%"
height="100%"
/>
</aside>
</div>
```
---
## Theming
```tsx
<AgentforceConversationClient
agentId="<USER_AGENT_ID_18_CHAR_0Xx...>"
styleTokens={{
headerBlockBackground: "#0176d3",
headerBlockTextColor: "#ffffff",
messageBlockInboundColor: "#0176d3",
}}
/>
```
---
## Inline with header enabled
```tsx
<AgentforceConversationClient
agentId="<USER_AGENT_ID_18_CHAR_0Xx...>"
inline
width={420}
height={600}
headerEnabled
/>
```
`headerEnabled` defaults to `true` for floating mode, and you can use it in inline mode to add/remove the header.
---
## Full layout example
```tsx
import { Outlet } from "react-router";
import { AgentforceConversationClient } from "@salesforce/webapp-template-feature-react-agentforce-conversation-client-experimental";
export default function AppLayout() {
return (
<>
<Outlet />
<AgentforceConversationClient
agentId="<USER_AGENT_ID_18_CHAR_0Xx...>"
styleTokens={{
headerBlockBackground: "#0176d3",
headerBlockTextColor: "#ffffff",
}}
/>
</>
);
}
```

View File

@ -0,0 +1,60 @@
# Troubleshooting
Common issues when using the Agentforce Conversation Client.
---
### Component throws "requires agentId"
**Cause:** `agentId` was not passed.
**Solution:** Pass `agentId` directly as a flat prop:
```tsx
<AgentforceConversationClient agentId="0Xx000000000000AAA" />
```
---
### Chat widget does not appear
**Cause:** Invalid `agentId` or inactive agent.
**Solution:**
1. Confirm the id is correct (18-char Salesforce id, starts with `0Xx`).
2. Ensure the agent is Active in **Setup → Agents**.
3. Verify the agent is deployed to the target channel.
---
### Authentication error on localhost
**Cause:** `localhost:<PORT>` is not trusted for inline frames.
**Solution:**
1. Go to **Setup → Session Settings → Trusted Domains for Inline Frames**.
2. Add `localhost:<PORT>` (example: `localhost:3000`).
3. Restart the dev server.
---
### Blank iframe / auth session issues
**Cause:** First-party Salesforce cookie restriction is enabled.
**Solution:**
1. Go to **Setup → Session Settings**.
2. Find **Require first party use of Salesforce cookies**.
3. Disable it.
4. Save and reload.
---
### Multiple chat widgets appear
**Cause:** Component rendered more than once.
**Solution:** Render one instance in app layout only.

View File

@ -1,5 +1,5 @@
---
name: salesforce-webapp-feature-react-chart-analytics-charts
name: salesforce-webapp-feature-react-chart-building-analytics-charts
description: Add or change charts from raw data. Use when the user asks for a chart, graph, or analytics visualization from JSON/data.
---

View File

@ -1,5 +1,5 @@
---
name: salesforce-webapp-feature-react-file-upload
name: salesforce-webapp-feature-react-file-upload-implementing-file-upload
description: Add file upload functionality to React webapps with progress tracking and Salesforce ContentVersion integration. Use when the user wants to upload files, attach documents, handle file input, create file dropzones, track upload progress, or link files to Salesforce records. This feature provides programmatic APIs ONLY — no components or hooks are exported. Build your own custom UI using the upload() API. ALWAYS use this feature instead of building file upload from scratch with FormData or XHR.
---

View File

@ -0,0 +1,167 @@
---
name: salesforce-webapp-fetching-rest-api
description: REST API usage via the Data SDK fetch method. Use when implementing Chatter, Connect REST, Apex REST, UI API REST, or Einstein LLM calls — only when GraphQL is not sufficient.
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.graphql"
---
# Salesforce REST API via Data SDK Fetch
Use `sdk.fetch` from the Data SDK when GraphQL is not sufficient. The SDK applies authentication, CSRF handling, and base URL resolution. **Always use optional chaining** (`sdk.fetch?.()`) and handle the case where `fetch` is not available.
Invoke this skill when you need to call Chatter, Connect REST, Apex REST, UI API REST, or Einstein LLM endpoints.
## API Version
Use the project's API version. It is typically injected as `__SF_API_VERSION__`; fallback to `"65.0"`:
```typescript
declare const __SF_API_VERSION__: string;
const API_VERSION = typeof __SF_API_VERSION__ !== "undefined" ? __SF_API_VERSION__ : "65.0";
```
## Base Path
URLs are relative to the Salesforce API base. The SDK prepends the correct base path. Use paths starting with `/services/...`.
---
## Chatter API
User and collaboration data. No GraphQL equivalent.
| Endpoint | Method | Purpose |
| -------- | ------ | ------- |
| `/services/data/v{version}/chatter/users/me` | GET | Current user (id, name, email, username) |
```typescript
const sdk = await createDataSDK();
const response = await sdk.fetch?.(`/services/data/v${API_VERSION}/chatter/users/me`);
if (!response?.ok) throw new Error(`HTTP ${response?.status}`);
const data = await response.json();
return { id: data.id, name: data.name };
```
---
## Connect REST API
File and content operations.
| Endpoint | Method | Purpose |
| -------- | ------ | ------- |
| `/services/data/v{version}/connect/file/upload/config` | GET | Upload config (token, uploadUrl) for file uploads |
```typescript
const sdk = await createDataSDK();
const configRes = await sdk.fetch?.(`/services/data/v${API_VERSION}/connect/file/upload/config`, {
method: "GET",
});
if (!configRes?.ok) throw new Error(`Failed to get upload config: ${configRes?.status}`);
const config = await configRes.json();
const { token, uploadUrl } = config;
```
---
## Apex REST
Custom Apex REST resources. Requires corresponding Apex classes in the org. CSRF protection is applied automatically for `services/apexrest` URLs.
| Endpoint | Method | Purpose |
| -------- | ------ | ------- |
| `/services/apexrest/auth/login` | POST | User login |
| `/services/apexrest/auth/register` | POST | User registration |
| `/services/apexrest/auth/forgot-password` | POST | Request password reset |
| `/services/apexrest/auth/reset-password` | POST | Reset password with token |
| `/services/apexrest/auth/change-password` | POST | Change password (authenticated) |
| `/services/apexrest/{resource}` | GET/POST | Custom Apex REST resources |
**Example (login):**
```typescript
const sdk = await createDataSDK();
const response = await sdk.fetch?.("/services/apexrest/auth/login", {
method: "POST",
body: JSON.stringify({ email, password, startUrl: "/" }),
headers: { "Content-Type": "application/json", Accept: "application/json" },
});
```
Apex REST paths do not include the API version.
---
## UI API (REST)
When GraphQL cannot cover the use case. **Prefer GraphQL** when possible.
| Endpoint | Method | Purpose |
| -------- | ------ | ------- |
| `/services/data/v{version}/ui-api/records/{recordId}` | GET | Fetch a single record |
```typescript
const sdk = await createDataSDK();
const response = await sdk.fetch?.(`/services/data/v${API_VERSION}/ui-api/records/${recordId}`);
```
---
## Einstein LLM Gateway
AI features. Requires Einstein API setup.
| Endpoint | Method | Purpose |
| -------- | ------ | ------- |
| `/services/data/v{version}/einstein/llm/prompt/generations` | POST | Generate text from Einstein LLM |
```typescript
const sdk = await createDataSDK();
const response = await sdk.fetch?.(`/services/data/v${API_VERSION}/einstein/llm/prompt/generations`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
additionalConfig: { applicationName: "PromptTemplateGenerationsInvocable" },
promptTextorId: prompt,
}),
});
if (!response?.ok) throw new Error(`Einstein LLM failed (${response?.status})`);
const data = await response.json();
return data?.generations?.[0]?.text ?? "";
```
---
## General Pattern
```typescript
import { createDataSDK } from "@salesforce/sdk-data";
const sdk = await createDataSDK();
if (!sdk.fetch) {
throw new Error("Data SDK fetch is not available in this context");
}
const response = await sdk.fetch(url, {
method: "GET", // or POST, PUT, PATCH, DELETE
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: method !== "GET" ? JSON.stringify(payload) : undefined,
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
```
---
## Reference
- Parent: `accessing-data` — enforces Data SDK usage for all Salesforce data fetches
- GraphQL: `using-graphql` — use for record queries and mutations when possible
- `createRecord` from `@salesforce/webapp-experimental/api` for UI API record creation (uses SDK internally)

View File

@ -1,40 +1,47 @@
# GraphQL Mutation Query Generation
---
name: salesforce-webapp-generating-graphql-mutation-query
description: Generate Salesforce GraphQL mutation queries. Use when the query to generate is a mutation query. Schema exploration must complete first — invoke exploring-graphql-schema first.
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.graphql"
---
# Salesforce GraphQL Mutation Query Generation
**Triggering conditions**
1. Only if the schema exploration phase completed successfully
1. Only if the schema exploration phase completed successfully (invoke `exploring-graphql-schema` first)
2. Only if the query to generate is a mutation query
## Schema Access Policy
> ⚠️ **GREP ONLY** — During mutation generation you may need to verify field names, input types, or representations. All schema lookups **MUST** use the grep-only commands defined in the `exploring-graphql-schema` skill. Do NOT open, read, stream, or parse `./schema.graphql` with any tool other than grep.
## Your Role
You are a GraphQL expert and your role is to help generate Salesforce compatible GraphQL mutation queries once the exploration phase has completed.
You will leverage the context provided by the requesting user as well as the validation phase provided by the schema exploration. This tool will also provide you with a method to dynamically query the target org instance that you will use to test the generated query.
If the schema exploration has not been executed yet, you **MUST** run it first, and then get back to mutation query generation.
You are a GraphQL expert. Generate Salesforce-compatible mutation queries. Schema exploration must complete first. If the schema exploration has not been executed yet, you **MUST** run the full exploration workflow from the `exploring-graphql-schema` skill first, then return here for mutation query generation.
## Mutation Queries General Information
**IMPORTANT**:
1. **Mutation Types**: The GraphQL engine supports `Create`, `Update` and `Delete` operations
2. **Id Based Mutations**: `Update` and `Delete` operations operate on Id-based entity identification
3. **Mutation Schema**: Defined in the [mutation query schema](#mutation-query-schema) section
The GraphQL engine supports `Create`, `Update`, and `Delete` operations. `Update` and `Delete` operate on Id-based entity identification. See the [mutation query schema](#mutation-query-schema) section.
## Mutation Query Generation Workflow
Strictly follow the rules below when generating the GraphQL mutation query:
1. **Input Fields Validation** - Validate that the set of fields validate [input field constraints](#mutation-queries-input-field-constraints)
1. **Input Fields Validation** - Validate that the set of fields validate [input field constraints](#mutation-queries-input-field-constraints). Verify every field name and type against grep output from the schema — do NOT guess or assume
2. **Output Fields Validation** - Validate that the set of fields used in the select part of the query validate the [output fields constraints](#mutation-queries-output-field-constraints)
3. **Type Consistency** - Make sure variables used as query arguments and their related fields share the same GraphQL type
3. **Type Consistency** - Make sure variables used as query arguments and their related fields share the same GraphQL type. Verify types via grep lookup — do NOT assume types
4. **Report Phase** - Use the [Mutation Query Report Template](#mutation-query-report-template) below to report on the previous validation phases
5. **Input Arguments** - `input` is the default name for the argument, unless otherwise specified
6. **Output Field** - For `Create` and `Update` operations, the output field is always named `Record`, and is of type EntityName
7. **Query Generation** - Use the [mutation query](#mutation-query-templates) template and adjust it based on the selected operation
8. **Output Format** - Use the [standalone](#mutation-standalone-default-output-format---clean-code-only)
9. **Test the Query** - Use the [Generated Mutation Query Testing](#generated-mutation-query-testing) workflow to test the generated query
1. **Report First** - Always report first, using the proper output format, before testing
7. **Field Name Validation** - Every field name in the generated mutation **MUST** match a field confirmed via grep lookup in the schema. Do NOT guess or assume field names exist
8. **Query Generation** - Use the [mutation query](#mutation-query-templates) template and adjust it based on the selected operation
9. **Output Format** - Use the [standalone](#mutation-standalone-default-output-format---clean-code-only)
10. **Lint Validation** - After writing the mutation to a file, run `npx eslint <file>` from the webapp dir to validate it against the schema. Fix any reported errors before proceeding. See [Lint Validation](#lint-validation) for details
11. **Test the Query** - Use the [Generated Mutation Query Testing](#generated-mutation-query-testing) workflow to test the generated query
1. **Report First** - Always output the generated mutation in the proper output format BEFORE initiating any test
## Mutation Query Schema
@ -127,7 +134,7 @@ mutation mutateEntityName(
## Mutation Standalone (Default) Output Format - CLEAN CODE ONLY
```javascript
import { gql } from 'api/graphql.ts';
import { gql } from '@salesforce/sdk-data';
const QUERY_NAME = gql`
mutation mutateEntity($input: EntityNameOperationInput!) {
uiapi {
@ -150,36 +157,54 @@ const QUERY_VARIABLES = {
};
```
**❌ DO NOT INCLUDE:**
**❌ FORBIDDEN — Do NOT include any of the following:**
- Explanatory comments about the query
- Field descriptions
- Explanatory comments about the query (inline or surrounding)
- Field descriptions or annotations
- Additional text about what the query does
- Workflow step descriptions
- Workflow step descriptions or summaries
- Comments like `// fetches...`, `// creates...`, `/* ... */`
**✅ ONLY INCLUDE:**
**✅ ONLY output:**
- Raw query string
- Variables object
- Nothing else
- The raw query string constant (using `gql` tagged template)
- The variables object constant
- Nothing else — no extra imports, no exports, no wrapper functions
## Lint Validation
After writing the generated mutation into a source file, validate it against the schema using the project's GraphQL ESLint setup:
```bash
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
npx eslint <path-to-file-containing-mutation>
```
**How it works:** The ESLint config uses `@graphql-eslint/eslint-plugin` with its `processor`, which extracts GraphQL operations from `gql` template literals in `.ts`/`.tsx` files and validates the extracted `.graphql` virtual files against `schema.graphql`.
**Rules enforced:** `no-anonymous-operations`, `no-duplicate-fields`, `known-fragment-names`, `no-undefined-variables`, `no-unused-variables`
**On failure:** Fix the reported issues, re-run `npx eslint <file>` until clean, then proceed to testing.
> ⚠️ **Prerequisites**: The `schema.graphql` file must exist (invoke `exploring-graphql-schema` first) and project dependencies must be installed (`npm install`).
## Generated Mutation Query Testing
**Triggering conditions** - **ALL CONDITIONS MUST VALIDATE\***
**Triggering conditions** — **ALL conditions must be true:**
1. Only if the [Mutation Query Generation Workflow](#mutation-query-generation-workflow) step global status is `SUCCESS` and you have a generated query
2. Only if the query to generate is a mutation query
3. Only if non manual method was used during schema exploration to retrieve introspection data
1. The [Mutation Query Generation Workflow](#mutation-query-generation-workflow) completed with status `SUCCESS` and you have a generated query
2. The query is a mutation query
3. A non-manual method was used during schema exploration to retrieve introspection data
**Workflow**
1. **Report Step** - Explain that you are able to test the query using `sf api request rest`
2. **Interactive Step** - Ask the user whether they want you to test the query
1. **WAIT** for the user's answer.
3. **Input Arguments** - You **MUST** ask the user for the input arguments to use
1. **WAIT** for the user's answer.
4. **Test Query** - If the user are OK with you testing the query:
1. Use `sf api request rest` to POST the query and variables to the GraphQL endpoint:
1. **Report Step** - State the exact method you will use to test (e.g., `sf api request graphql` from the **project root**, Connect API, etc.) — this **MUST** match the method used during schema exploration
2. **Interactive Step** - Ask the user whether they want you to test the query using the proposed method
1. **STOP and WAIT** for the user's answer. Do NOT proceed until the user responds. Do NOT assume consent.
3. **Input Arguments** - You **MUST** ask the user for the input argument values to use in the test
1. **STOP and WAIT** for the user's answer. Do NOT proceed until the user provides values. Do NOT fabricate test data.
4. **Test Query** - Only if the user explicitly agrees and has provided input values:
1. Execute the mutation using the reported method (e.g., `sf api request rest` to POST the query and variables to the GraphQL endpoint):
```bash
sf api request rest /services/data/v65.0/graphql \
--method POST \
@ -207,14 +232,14 @@ The query is invalid:
- **Navigation** - Error contains `is not currently available in mutation results`
- **API Version** - Query deals with updates, you're testing with Connect API and error contains `Cannot invoke JsonElement.isJsonObject()`
3. **Targeted Resolution** - Depending on the root cause categorization
- **Execution** - You're trying to update or delete an unknown/no longer available entity: either create an entity first, if you have generated the related query, or ask for a valid entity id to use
- **Execution** - You're trying to update or delete an unknown/no longer available entity: either create an entity first, if you have generated the related query, or ask for a valid entity id to use. **STOP and WAIT** for the user to provide a valid Id
- **Syntax** - Update the query using the error message information to fix the syntax errors
- **Validation** - This field's name is most probably invalid, ask user for clarification and **WAIT** for the user's answer
- **Type** - Use the error details and GraphQL schema to correct argument's type, and adjust variables accordingly
- **Validation** - The field name is most probably invalid. Re-run the relevant grep command from the `exploring-graphql-schema` skill to verify the correct field name. If still unclear, ask the user for clarification and **STOP and WAIT** for their answer
- **Type** - Use the error details and re-verify the type via grep lookup in the schema. Correct the argument type and adjust variables accordingly
- **Navigation** - Use the [`PARTIAL` status handling workflow](#partial-status-handling-workflow) below
- **API Version** - `Record` selection is only available with API version 64 and higher, **report** the issue, and try again with API version 64
4. **Test Again** - Resume the [query testing workflow](#generated-mutation-query-testing) with the updated query (increment and track attempt counter)
5. **Escalation Path** - If targeted resolution fails after 2 attempts, ask for additional details and restart the entire GraphQL workflow, going again through the introspection phase
5. **Escalation Path** - If targeted resolution fails after 2 attempts, ask for additional details and restart the entire GraphQL workflow from the `exploring-graphql-schema` skill
### `PARTIAL` Status Handling Workflow
@ -224,5 +249,10 @@ The query can be improved:
2. Explain that these fields can't be queried as part of a mutation query
3. Explain that the query might be considered as failing, as it will report errors
4. Offer to remove the offending fields
5. **WAIT** for the user's answer
5. **STOP and WAIT** for the user's answer. Do NOT remove fields without explicit consent.
6. If they are OK with removing the fields restart the [generation workflow](#mutation-query-generation-workflow) with the new field list
## Related Skills
- Schema exploration: `exploring-graphql-schema` (must complete first)
- Read query generation: `generating-graphql-read-query`

View File

@ -0,0 +1,253 @@
---
name: salesforce-webapp-generating-graphql-read-query
description: Generate Salesforce GraphQL read queries. Use when the query to generate is a read query. Schema exploration must complete first — invoke exploring-graphql-schema first.
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.graphql"
---
# Salesforce GraphQL Read Query Generation
**Triggering conditions**
1. Only if the schema exploration phase completed successfully (invoke `exploring-graphql-schema` first)
2. Only if the query to generate is a read query
## Schema Access Policy
> ⚠️ **GREP ONLY** — During query generation you may need to verify field names, types, or relationships. All schema lookups **MUST** use the grep-only commands defined in the `exploring-graphql-schema` skill. Do NOT open, read, stream, or parse `./schema.graphql` with any tool other than grep.
## Field-Level Security and @optional
Field-level security (FLS) restricts which fields different users can see. Use the `@optional` directive on Salesforce record fields when possible. The server omits the field when the user lacks access, allowing the query to succeed instead of failing. Apply `@optional` to scalar fields, value-type fields (e.g. `Name { value }`), parent relationships, and child relationships. Available in API v65.0+.
**Consuming code must defend against missing fields.** When a field is omitted due to FLS, it will be `undefined` (or absent) in the response. Use optional chaining (`?.`), nullish coalescing (`??`), and explicit null/undefined checks when reading query results. Never assume an optional field is present — otherwise the app may crash or behave incorrectly for users without field access.
```ts
// ✅ Defend against missing fields
const name = node.Name?.value ?? '';
const relatedName = node.RelationshipName?.Name?.value ?? 'N/A';
// ❌ Unsafe — will throw if field omitted due to FLS
const name = node.Name.value;
```
## Your Role
You are a GraphQL expert. Generate Salesforce-compatible read queries. Schema exploration must complete first. If the schema exploration has not been executed yet, you **MUST** run the full exploration workflow from the `exploring-graphql-schema` skill first, then return here for read query generation.
## Read Query Generation Workflow
Strictly follow the rules below when generating the GraphQL read query:
1. **No Proliferation** - Only generate for the explicitly requested fields, nothing else. Do NOT add fields the user did not ask for.
2. **Unique Query** - Leverage child relationships to query entities in one single query
3. **Navigate Entities** - Always use `relationshipName` to access reference fields and child entities
1. **Exception** - if the `relationshipName` field is null, you can't navigate the related entity, and will have to return the `Id` itself
4. **Leverage Fragments** - Generate one fragment per possible type on polymorphic fields (field with `dataType="REFERENCE"` and more than one entry in `referenceToInfos` introspection attribute)
5. **Type Consistency** - Make sure variables used as query arguments and their related fields share the same GraphQL type. Verify types against grep output from the schema — do not assume types
6. **Type Enforcement** - Make sure to leverage field type information from introspection and GraphQL schema to generate field access
7. **Field Name Validation** - Every field name in the generated query **MUST** match a field confirmed via grep lookup in the schema. Do NOT guess or assume field names exist
8. **@optional for FLS** - Apply `@optional` on all Salesforce record fields when possible (see [Field-Level Security and @optional](#field-level-security-and-optional)). This lets the query succeed when the user lacks field-level access; the server omits inaccessible fields instead of failing
9. **Consuming code defense** - When generating or modifying code that consumes read query results, defend against missing fields (see [Field-Level Security and @optional](#field-level-security-and-optional)). Use optional chaining, nullish coalescing, and null/undefined checks — never assume optional fields are present
10. **Semi and anti joins** - Use the semi-join or anti-join templates to filter an entity with conditions on child entities
11. **Query Generation** - Use the [template](#read-query-template) to generate the query
12. **Output Format** - Use the [standalone](#read-standalone-default-output-format---clean-code-only)
13. **Lint Validation** - After writing the query to a file, run `npx eslint <file>` from the webapp dir to validate it against the schema. Fix any reported errors before proceeding. See [Lint Validation](#lint-validation) for details
14. **Test the Query** - Use the [Generated Read Query Testing](#generated-read-query-testing) workflow to test the generated query
1. **Report First** - Always output the generated query in the proper output format BEFORE initiating any test
## Read Query Template
```graphql
query QueryName {
uiapi {
query {
EntityName(
# conditions here
) {
edges {
node {
# Direct fields — use @optional for FLS resilience
FieldName @optional { value }
# Non-polymorphic reference (single type)
RelationshipName @optional {
Id
Name { value }
}
# Polymorphic reference (multiple types)
PolymorphicRelationshipName @optional {
...TypeAInfo
...TypeBInfo
}
# Child relationship (subquery)
RelationshipName @optional (
# conditions here
) {
edges {
node {
# fields
}
}
}
}
}
}
}
}
}
fragment TypeAInfo on TypeA {
Id
SpecificFieldA @optional { value }
}
fragment TypeBInfo on TypeB {
Id
SpecificFieldB @optional { value }
}
```
## Semi-Join and Anti-Join Condition Template
Semi-joins (resp. anti-joins) condition leverage parent-child relationships and allow filtering the parent entity using a condition on child entities.
This is a standard `where` condition, on the parent entity's `Id`, expressed using the `inq` (resp. `ninq`, i.e. not `inq`) operator. This operator accepts two attributes:
- The child entity camelcase name to apply the condition on, with a value expressing the condition
- The field name on the child entity containing the parent entity `Id`, which is the `fieldName` from the `childRelationships` information for the child entity
- If the only condition is related child entity existence, you can use an `Id: { ne: null }` condition
### Semi-Join Example - ParentEntity with at least one Matching ChildEntity
```graphql
query testSemiJoin {
uiapi {
query {
ParentEntity(
where: {
Id: {
inq: {
ChildEntity: {
# standard conditions here
Name: { like: "test%" }
Type: { eq: "some value" }
}
ApiName: "parentIdFieldInChild"
}
}
}
) {
edges {
node {
Id
Name @optional {
value
}
}
}
}
}
}
}
```
### Anti-Join Example - ParentEntity with no Matching ChildEntity
Same example as the [Semi-Join Example](#semi-join-example---parententity-with-at-least-one-matching-childentity), but replacing the `inq` operator by the `ninq` one.
## Read Standalone (Default) Output Format - CLEAN CODE ONLY
```javascript
const QUERY_NAME = `
query GetData {
# query here
}
`;
const QUERY_VARIABLES = {
// variables here
};
```
**❌ FORBIDDEN — Do NOT include any of the following:**
- Explanatory comments about the query (inline or surrounding)
- Field descriptions or annotations
- Additional text about what the query does
- Workflow step descriptions or summaries
- Comments like `// fetches...`, `// returns...`, `/* ... */`
**✅ ONLY output:**
- The raw query string constant
- The variables object constant
- Nothing else — no imports, no exports, no wrapper functions
## Lint Validation
After writing the generated query into a source file, validate it against the schema using the project's GraphQL ESLint setup:
```bash
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
npx eslint <path-to-file-containing-query>
```
**How it works:** The ESLint config uses `@graphql-eslint/eslint-plugin` with its `processor`, which extracts GraphQL operations from `gql` template literals in `.ts`/`.tsx` files and validates the extracted `.graphql` virtual files against `schema.graphql`.
**Rules enforced:** `no-anonymous-operations`, `no-duplicate-fields`, `known-fragment-names`, `no-undefined-variables`, `no-unused-variables`
**On failure:** Fix the reported issues, re-run `npx eslint <file>` until clean, then proceed to testing.
> ⚠️ **Prerequisites**: The `schema.graphql` file must exist (invoke `exploring-graphql-schema` first) and project dependencies must be installed (`npm install`).
## Generated Read Query Testing
**Triggering conditions** — **ALL conditions must be true:**
1. The [Read Query Generation Workflow](#read-query-generation-workflow) completed with status `SUCCESS` and you have a generated query
2. The query is a read query
3. A non-manual method was used during schema exploration to retrieve introspection data
**Workflow**
1. **Report Step** - State the exact method you will use to test (e.g., `sf api request graphql` from the **project root**, Connect API, etc.) — this **MUST** match the method used during schema exploration
2. **Interactive Step** - Ask the user whether they want you to test the query using the proposed method
1. **STOP and WAIT** for the user's answer. Do NOT proceed until the user responds. Do NOT assume consent.
3. **Test Query** - Only if the user explicitly agrees:
1. Use `sf api request rest` to POST the query to the GraphQL endpoint:
```bash
sf api request rest /services/data/v65.0/graphql \
--method POST \
--body '{"query":"query GetData { uiapi { query { EntityName { edges { node { Id } } } } } }"}'
```
2. Replace `v65.0` with the API version of the target org
3. Replace the `query` value with the generated read query string
4. If the query uses variables, include them in the JSON body as a `variables` key
5. Report the result as `SUCCESS` if the query executed without error, or `FAILED` if errors were returned
6. An empty result set with no errors is `SUCCESS` — the query is valid, the org simply has no matching data
4. **Remediation Step** - If status is `FAILED`, use the [`FAILED` status handling workflows](#failed-status-handling-workflow)
### `FAILED` Status Handling Workflow
The query is invalid:
1. **Error Analysis** - Parse and categorize the specific error messages
2. **Root Cause Identification** - Use error message to identify the root cause:
- **Syntax** - Error contains `invalid syntax`
- **Validation** - Error contains `validation error`
- **Type** - Error contains `VariableTypeMismatch` or `UnknownType`
3. **Targeted Resolution** - Depending on the root cause categorization
- **Syntax** - Update the query using the error message information to fix the syntax errors
- **Validation** - The field name is most probably invalid. Re-run the relevant grep command from the `exploring-graphql-schema` skill to verify the correct field name. If still unclear, ask the user for clarification and **STOP and WAIT** for their answer
- **Type** - Use the error details and re-verify the type via grep lookup in the schema. Correct the argument type and adjust variables accordingly
4. **Test Again** - Resume the [query testing workflow](#generated-read-query-testing) with the updated query (increment and track attempt counter)
5. **Escalation Path** - If targeted resolution fails after 2 attempts, ask for additional details and restart the entire GraphQL workflow from the `exploring-graphql-schema` skill
## Related Skills
- Schema exploration: `exploring-graphql-schema` (must complete first)
- Mutation generation: `generating-graphql-mutation-query`

View File

@ -1,5 +1,5 @@
---
name: salesforce-webapp-features
name: salesforce-webapp-installing-webapp-features
description: Search, describe, and install pre-built UI features (authentication, shadcn components, navigation, charts, search, GraphQL, Agentforce AI) into Salesforce webapps. Use this when the user wants to add functionality to a webapp, or when determining what salesforce-provided features are available — whether prompted by the user or on your own initiative. Always check for an existing feature before building from scratch.
---

View File

@ -1,5 +1,5 @@
---
name: salesforce-webapp-unsplash-images
name: salesforce-webapp-integrating-unsplash-images
description: Adds high-quality Unsplash images to React pages. Use when the user asks to add a hero image, background image, placeholder image, stock photo, decorative image, or any Unsplash-sourced imagery to the web application.
---

View File

@ -0,0 +1,323 @@
---
name: salesforce-webapp-using-graphql
description: Salesforce GraphQL data access. Use when the user asks to fetch, query, or mutate Salesforce data, or add a GraphQL operation for an object like Account, Contact, or Opportunity.
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.graphql"
---
# Salesforce GraphQL
Guidance for querying and mutating Salesforce data via the Salesforce GraphQL API. Use `createDataSDK()` + `sdk.graphql?.()` and codegen tooling.
## When to Use
- User asks to "fetch data from Salesforce"
- User asks to "query" or "mutate" Salesforce records
- User wants to add a new GraphQL operation (query or mutation)
- User asks to add data access for a Salesforce object (Account, Contact, Opportunity, etc.)
## Schema Access Policy (GREP ONLY)
> **GREP ONLY** — The `schema.graphql` file is very large (~265,000+ lines). All schema lookups **MUST** use the grep-only commands defined in the `exploring-graphql-schema` skill. Do NOT open, read, stream, or parse `./schema.graphql` with any tool other than grep.
## Directory Context
The generated app has a two-level directory structure. Commands must run from the correct directory.
```
<project-root>/ ← SFDX project root
├── schema.graphql ← grep target
├── sfdx-project.json
└── force-app/main/default/webapplications/<app-name>/ ← webapp dir
├── package.json (npm scripts: graphql:schema, graphql:codegen, lint)
├── eslint.config.js (schema ref: ../../../../../schema.graphql)
├── codegen.yml (schema ref: ../../../../../schema.graphql)
└── src/ (source code, .graphql query files)
```
| Command | Run from | Why |
| ------------------------- | ---------------- | -------------------------------------- |
| `npm run graphql:schema` | **webapp dir** | Script is in webapp's `package.json` |
| `npm run graphql:codegen` | **webapp dir** | Reads `codegen.yml` in webapp dir |
| `npx eslint <file>` | **webapp dir** | Reads `eslint.config.js` in webapp dir |
| `grep ... schema.graphql` | **project root** | `schema.graphql` lives at project root |
| `sf api request graphql` | **project root** | Needs `sfdx-project.json` |
> **Wrong directory = silent failures.** `npm run graphql:schema` from the project root will fail with "missing script." `grep ./schema.graphql` from the webapp dir will fail with "no such file."
## Prerequisites
The base React app (`base-react-app`) ships with all GraphQL dependencies and tooling pre-configured:
- `@salesforce/sdk-data` — runtime SDK for `createDataSDK` and `gql`
- `@graphql-codegen/cli` + plugins — type generation from `.graphql` files and inline `gql` queries
- `@graphql-eslint/eslint-plugin` — validates `.graphql` files and `gql` template literals against `schema.graphql` (used as a query validation gate — see Step 6)
- `graphql` — shared by codegen, ESLint, and schema introspection
Before using this skill, ensure:
1. The `@salesforce/sdk-data` package is available (provides `createDataSDK`, `gql`, `NodeOfConnection`)
2. A `schema.graphql` file exists at the project root. If missing, generate it:
```bash
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
npm run graphql:schema
```
## npm Scripts
- **`npm run graphql:schema`** — _(run from webapp dir)_ Downloads the full GraphQL schema from a connected Salesforce org via introspection. Outputs `schema.graphql` to the project root.
- **`npm run graphql:codegen`** — _(run from webapp dir)_ Generates TypeScript types from `.graphql` files and inline `gql` queries. Outputs to `src/api/graphql-operations-types.ts`.
## Workflow
### Step 1: Download Schema
Ensure `schema.graphql` exists at the project root. If missing, run `npm run graphql:schema` from the webapp dir.
### Step 2: Explore the Schema (grep-only)
Before writing any query, verify the target object and its fields exist in the schema.
**Invoke the `exploring-graphql-schema` skill** for the full exploration workflow and **mandatory grep-only access policy**.
> **GREP ONLY** — All schema lookups MUST use the grep commands defined in the `exploring-graphql-schema` skill. Do NOT open, read, stream, or parse `./schema.graphql` with any tool other than grep.
Key actions (all via grep):
- `type <ObjectName> implements Record` — find available fields
- `input <ObjectName>_Filter` — find filter options
- `input <ObjectName>_OrderBy` — find sorting options
- `input <ObjectName>CreateInput` / `<ObjectName>UpdateInput` — find mutation input types
### Step 3: Choose the Query Pattern
**Pattern 1 — External `.graphql` file** (recommended for complex queries):
- Queries with variables, fragments, or shared across files
- Full codegen support, syntax highlighting, shareable
- Requires codegen step after changes
- See example: `api/utils/accounts.ts` + `api/utils/query/highRevenueAccountsQuery.graphql`
**Pattern 2 — Inline `gql` tag** (recommended for simple queries):
- Simple queries without variables; colocated with usage code
- Supports dynamic queries (field set varies at runtime)
- **MUST use `gql` tag** — plain template strings bypass `@graphql-eslint` validation
- See example: `api/utils/user.ts`
### Step 4: Write the Query
For **Pattern 1**:
1. Create a `.graphql` file under `src/api/utils/query/`
2. Follow UIAPI structure: `query { uiapi { query { ObjectName(...) { edges { node { ... } } } } } }`
3. For mutations, invoke the `generating-graphql-mutation-query` skill
4. For read queries, invoke the `generating-graphql-read-query` skill
For **Pattern 2**:
1. Define query inline using the `gql` template tag
2. Ensure the query name matches what codegen expects
### Step 5: Test Queries Against Live Org
Use the testing workflows in the `generating-graphql-read-query` and `generating-graphql-mutation-query` skills to validate queries against the connected org before integrating into the app.
### Step 6: Generate Types
```bash
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
npm run graphql:codegen
```
This updates `src/api/graphql-operations-types.ts` with `<OperationName>Query`/`<OperationName>Mutation` and `<OperationName>QueryVariables`/`<OperationName>MutationVariables`.
### Step 7: Lint Validate
Run ESLint on the file containing the query to validate it against the schema **before** any live testing:
```bash
# Run from webapp dir
npx eslint <path-to-file>
```
The `@graphql-eslint/eslint-plugin` processor extracts GraphQL from `gql` template literals and validates them against `schema.graphql`. Fix all ESLint errors before proceeding.
### Step 8: Implement and Verify
Implement the data access function using the pattern below. Use the Quality Checklist before completing.
---
## Core Types & Function Signatures
### createDataSDK and graphql
```typescript
import { createDataSDK } from "@salesforce/sdk-data";
const sdk = await createDataSDK();
const response = await sdk.graphql?.<ResponseType, VariablesType>(query, variables);
```
`createDataSDK()` returns a `DataSDK` instance. The `graphql` method uses optional chaining (`?.`) because not all surfaces support GraphQL.
### gql Template Tag
```typescript
import { gql } from "@salesforce/sdk-data";
const MY_QUERY = gql`
query MyQuery {
uiapi { ... }
}
`;
```
The `gql` tag enables ESLint validation against the schema. Plain template strings bypass validation.
### Error Handling
Default: treat any errors as failure (Strategy A). For partial data tolerance, log errors but use data. For mutations where some return fields are inaccessible, use Strategy C (fail only when no data).
```typescript
// Default: strict
if (response?.errors?.length) {
throw new Error(response.errors.map((e) => e.message).join("; "));
}
const result = response?.data;
```
Responses follow `uiapi.query.ObjectName.edges[].node`; fields use `{ value }`.
### NodeOfConnection
```typescript
import { type NodeOfConnection } from "@salesforce/sdk-data";
type AccountNode = NodeOfConnection<GetHighRevenueAccountsQuery["uiapi"]["query"]["Account"]>;
```
---
## Pattern 1: External .graphql File
Create a `.graphql` file, run `npm run graphql:codegen`, import with `?raw` suffix, and use generated types.
**Required imports:**
```typescript
import { createDataSDK, type NodeOfConnection } from "@salesforce/sdk-data";
import MY_QUERY from "./query/myQuery.graphql?raw"; // ← ?raw suffix required
import type { GetMyDataQuery, GetMyDataQueryVariables } from "../graphql-operations-types";
```
**When to use:** Complex queries with variables, fragments, or shared across files. Does NOT support dynamic queries (field set varies at runtime).
---
## Pattern 2: Inline gql Tag
**Required imports:**
```typescript
import { createDataSDK, gql } from "@salesforce/sdk-data";
import { type CurrentUserQuery } from "../graphql-operations-types";
const MY_QUERY = gql`
query CurrentUser {
uiapi { ... }
}
`;
```
> **MUST use `gql` tag** — plain template strings bypass the `@graphql-eslint` processor entirely, meaning no lint validation against the schema.
**When to use:** Simple, colocated queries. Supports dynamic queries (field set varies at runtime).
---
## Conditional Field Selection
For dynamic fieldsets with **known** fields, use `@include(if: $condition)` and `@skip(if: $condition)` in `.graphql` files. See GraphQL spec for details.
---
## Anti-Patterns (Not Recommended)
### Direct API Calls
```typescript
// NOT RECOMMENDED: Direct axios/fetch calls for GraphQL
// PREFERRED: Use the Data SDK
const sdk = await createDataSDK();
const response = await sdk.graphql?.<ResponseType>(query, variables);
```
### Missing Type Definitions
```typescript
// NOT RECOMMENDED: Untyped GraphQL calls
// PREFERRED: Provide response type
const response = await sdk.graphql?.<GetMyDataQuery>(query);
```
### Plain String Queries (Without gql Tag)
```typescript
// NOT RECOMMENDED: Plain strings bypass ESLint validation
const query = `query { ... }`;
// PREFERRED: Use gql tag for inline queries
const QUERY = gql`query { ... }`;
```
---
## Quality Checklist
> If you have not completed the workflow above, **stop and complete it first**. Invoke the skill workflow before using this checklist.
Before completing GraphQL data access code:
### For Pattern 1 (.graphql files):
1. [ ] All field names verified via grep against `schema.graphql` (invoke `exploring-graphql-schema`)
2. [ ] Create `.graphql` file for the query/mutation
3. [ ] Run `npm run graphql:codegen` to generate types
4. [ ] Import query with `?raw` suffix
5. [ ] Import generated types from `graphql-operations-types.ts`
6. [ ] Use `sdk.graphql?.<ResponseType>()` with proper generic
7. [ ] Handle `response.errors` and destructure `response.data`
8. [ ] Use `NodeOfConnection` for cleaner node types when needed
9. [ ] Run `npx eslint <file>` from webapp dir — fix all GraphQL errors
### For Pattern 2 (inline with gql):
1. [ ] All field names verified via grep against `schema.graphql`
2. [ ] Define query using `gql` template tag (NOT a plain string)
3. [ ] Ensure query name matches generated types in `graphql-operations-types.ts`
4. [ ] Import generated types for the query
5. [ ] Use `sdk.graphql?.<ResponseType>()` with proper generic
6. [ ] Handle `response.errors` and destructure `response.data`
7. [ ] Run `npx eslint <file>` from webapp dir — fix all GraphQL errors
### General:
- [ ] Lint validation passes (`npx eslint <file>` reports no GraphQL errors)
- [ ] Query field names match the schema exactly (case-sensitive, confirmed via grep)
- [ ] Response type generic is provided to `sdk.graphql?.<T>()`
- [ ] Optional chaining is used for nested response data
---
## Reference
- Schema exploration: invoke the `exploring-graphql-schema` skill
- Read query generation: invoke the `generating-graphql-read-query` skill
- Mutation query generation: invoke the `generating-graphql-mutation-query` skill
- Shared GraphQL schema types: `shared-schema.graphqls` (in this skill directory)
- Schema download: `npm run graphql:schema` (run from webapp dir)
- Type generation: `npm run graphql:codegen` (run from webapp dir)