mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-09 08:53:18 +08:00
Compare commits
14 Commits
67baac0acb
...
a7d9feef64
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7d9feef64 | ||
|
|
3bfd122458 | ||
|
|
288d7ef763 | ||
|
|
ddc6ec0ce3 | ||
|
|
e787ead627 | ||
|
|
ae4e46d97f | ||
|
|
ff08ad58e2 | ||
|
|
2320c50264 | ||
|
|
eb5662a707 | ||
|
|
95be7a36fb | ||
|
|
396c0c0d41 | ||
|
|
8e70f5341e | ||
|
|
dcedc58cde | ||
|
|
2a91a5eb6a |
43
.github/pull_request_template.md
vendored
Normal file
43
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
**References:** [Contributing guide](../CONTRIBUTING.md) · [Skill authoring guide](../README.md) · [Agent Skills spec](https://agentskills.io/specification)
|
||||
|
||||
## What changed
|
||||
|
||||
<!-- Briefly describe what skill(s) were added, updated, or removed. -->
|
||||
|
||||
## Why
|
||||
|
||||
<!-- What gap does this fill, or what problem does it solve? -->
|
||||
|
||||
## Notes
|
||||
|
||||
<!-- Anything reviewers should know — testing approach, follow-ups, open questions. -->
|
||||
|
||||
---
|
||||
|
||||
## Skills
|
||||
|
||||
### Manual checklist
|
||||
|
||||
**Description quality**
|
||||
- [ ] Describes what the skill does and the expected output
|
||||
- [ ] Includes relevant Salesforce domain keywords (Apex, LWC, SOQL, metadata types, etc.)
|
||||
- [ ] Trigger phrases are specific enough for Vibes to select this skill reliably
|
||||
|
||||
**Instructions**
|
||||
- [ ] Clear goal statement
|
||||
- [ ] Step-by-step workflow
|
||||
- [ ] Validation rules for generated output
|
||||
- [ ] Defined output / artifact
|
||||
|
||||
**Context efficiency**
|
||||
- [ ] Core instructions are concise — supporting material lives in `templates/`, `examples/`, or `docs/` subdirectories
|
||||
- [ ] No unnecessary background explanation in the body
|
||||
|
||||
### Automated checks
|
||||
|
||||
Enforced by CI ([`npm run validate:skills`](../scripts/validate-skills.ts)) per the [Agent Skills spec](https://agentskills.io/specification):
|
||||
|
||||
- Directory is one level deep, named in kebab-case (max 64 chars), contains `SKILL.md`
|
||||
- Frontmatter `name` matches directory name; `description` is present, ≥ 20 words, ≤ 1024 characters, and includes trigger language
|
||||
- Body is non-empty and under 500 lines
|
||||
- Name uses gerund form ⚠ (warning — does not block merge)
|
||||
91
.github/workflows/release-skills.yml
vendored
Normal file
91
.github/workflows/release-skills.yml
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
name: release-skills
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: ['skills/**']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: "Branch or tag to release from (must be main)"
|
||||
required: false
|
||||
default: main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
skipped: ${{ steps.changelog.outputs.skipped }}
|
||||
version: ${{ steps.changelog.outputs.version }}
|
||||
steps:
|
||||
- name: Require main branch
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
if [ "${{ github.ref_name }}" != "main" ]; then
|
||||
echo "Releases must be run from the main branch (got '${{ github.ref_name }}')."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.IDEE_GH_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- run: npm ci
|
||||
|
||||
- name: Validate skills
|
||||
run: npm run validate:skills
|
||||
|
||||
- name: Conventional Changelog Action
|
||||
id: changelog
|
||||
uses: TriPSs/conventional-changelog-action@v5
|
||||
with:
|
||||
git-user-name: svc-idee-bot
|
||||
git-user-email: svc_idee_bot@salesforce.com
|
||||
github-token: ${{ secrets.IDEE_GH_TOKEN }}
|
||||
tag-prefix: ""
|
||||
release-count: "0"
|
||||
skip-on-empty: ${{ github.event_name == 'push' }}
|
||||
version-file: "package.json"
|
||||
output-file: "CHANGELOG.md"
|
||||
|
||||
- name: Create Github Release
|
||||
id: release
|
||||
uses: ncipollo/release-action@v1
|
||||
if: ${{ steps.changelog.outputs.skipped == 'false' }}
|
||||
with:
|
||||
name: "${{ steps.changelog.outputs.version }}"
|
||||
tag: "${{ steps.changelog.outputs.version }}"
|
||||
commit: ${{ github.sha }}
|
||||
body: |
|
||||
## Changes in @salesforce/afv-skills
|
||||
|
||||
${{ steps.changelog.outputs.clean_changelog }}
|
||||
token: ${{ secrets.IDEE_GH_TOKEN }}
|
||||
skipIfReleaseExists: true
|
||||
|
||||
- name: Publish to npm
|
||||
id: publish
|
||||
if: ${{ steps.changelog.outputs.skipped == 'false' && steps.release.outputs.id != '' }}
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
|
||||
npm publish --access public
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Notify cline-fork
|
||||
if: ${{ steps.changelog.outputs.skipped == 'false' && steps.release.outputs.id != '' && steps.publish.outcome == 'success' }}
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.IDEE_GH_TOKEN }}
|
||||
repository: forcedotcom/cline-fork
|
||||
event-type: skills-released
|
||||
client-payload: '{"version": "${{ steps.changelog.outputs.version }}"}'
|
||||
10
.github/workflows/validate-pr.yml
vendored
Normal file
10
.github/workflows/validate-pr.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
name: Validate PR Title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, edited]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
pr-title:
|
||||
uses: salesforcecli/github-workflows/.github/workflows/validatePR.yml@main
|
||||
29
.github/workflows/validate-skills.yml
vendored
Normal file
29
.github/workflows/validate-skills.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
name: Validate Skills
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
validate-skills:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- run: npm ci
|
||||
|
||||
# Run against only changed skills when skills files are touched; fall back
|
||||
# to full corpus validation when only tooling files changed, so validator
|
||||
# regressions against existing skills are caught in the same PR.
|
||||
- name: Validate skills
|
||||
run: |
|
||||
if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q '^skills/'; then
|
||||
npm run validate:skills -- --changed --base=origin/${{ github.base_ref }}
|
||||
else
|
||||
npm run validate:skills
|
||||
fi
|
||||
11
CHANGELOG.md
Normal file
11
CHANGELOG.md
Normal file
@ -0,0 +1,11 @@
|
||||
# 1.1.0 (2026-03-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add first pass at b2e sample ([ce615bf](https://github.com/forcedotcom/afv-library/commit/ce615bf0361e4a6a8f5635726ce304e6de921413))
|
||||
* add metadata type skills ([3689f14](https://github.com/forcedotcom/afv-library/commit/3689f14af7eeb1dd8ea80ae206437f3483d37938))
|
||||
* add skills from 260 ([65947b1](https://github.com/forcedotcom/afv-library/commit/65947b15509fef576ac683226976ee3d60a624f3))
|
||||
|
||||
|
||||
|
||||
552
package-lock.json
generated
552
package-lock.json
generated
@ -1,13 +1,459 @@
|
||||
{
|
||||
"name": "afv-library",
|
||||
"name": "@salesforce/afv-skills",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "afv-library",
|
||||
"name": "@salesforce/afv-skills",
|
||||
"version": "1.0.0",
|
||||
"license": "CC-BY-NC-4.0",
|
||||
"devDependencies": {
|
||||
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "*",
|
||||
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "*"
|
||||
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "*",
|
||||
"tsx": "^4.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
|
||||
"integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
|
||||
"integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
|
||||
"integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
|
||||
"integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
|
||||
"integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
|
||||
"integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
|
||||
"integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
|
||||
"integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
|
||||
"integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
|
||||
"integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@salesforce/webapp-template-app-react-sample-b2e-experimental": {
|
||||
@ -22,6 +468,106 @@
|
||||
"integrity": "sha512-ry4U36CjJx9h7pv5R5q0tVQCHkEvEYm/4u/5Ysh3UqTz89s3B9o/GPb0JK9fZ2Mgt/DCd0zfPUm1oRlCplnhGQ==",
|
||||
"dev": true,
|
||||
"license": "SEE LICENSE IN LICENSE.txt"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
|
||||
"integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.3",
|
||||
"@esbuild/android-arm": "0.27.3",
|
||||
"@esbuild/android-arm64": "0.27.3",
|
||||
"@esbuild/android-x64": "0.27.3",
|
||||
"@esbuild/darwin-arm64": "0.27.3",
|
||||
"@esbuild/darwin-x64": "0.27.3",
|
||||
"@esbuild/freebsd-arm64": "0.27.3",
|
||||
"@esbuild/freebsd-x64": "0.27.3",
|
||||
"@esbuild/linux-arm": "0.27.3",
|
||||
"@esbuild/linux-arm64": "0.27.3",
|
||||
"@esbuild/linux-ia32": "0.27.3",
|
||||
"@esbuild/linux-loong64": "0.27.3",
|
||||
"@esbuild/linux-mips64el": "0.27.3",
|
||||
"@esbuild/linux-ppc64": "0.27.3",
|
||||
"@esbuild/linux-riscv64": "0.27.3",
|
||||
"@esbuild/linux-s390x": "0.27.3",
|
||||
"@esbuild/linux-x64": "0.27.3",
|
||||
"@esbuild/netbsd-arm64": "0.27.3",
|
||||
"@esbuild/netbsd-x64": "0.27.3",
|
||||
"@esbuild/openbsd-arm64": "0.27.3",
|
||||
"@esbuild/openbsd-x64": "0.27.3",
|
||||
"@esbuild/openharmony-arm64": "0.27.3",
|
||||
"@esbuild/sunos-x64": "0.27.3",
|
||||
"@esbuild/win32-arm64": "0.27.3",
|
||||
"@esbuild/win32-ia32": "0.27.3",
|
||||
"@esbuild/win32-x64": "0.27.3"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.13.6",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
|
||||
"integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.27.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
package.json
16
package.json
@ -1,12 +1,22 @@
|
||||
{
|
||||
"name": "afv-library",
|
||||
"description": "AI prompts and rules library for Agentforce Vibes development",
|
||||
"private": true,
|
||||
"name": "@salesforce/afv-skills",
|
||||
"version": "1.1.0",
|
||||
"description": "Salesforce skills for Agentforce Vibes",
|
||||
"license": "CC-BY-NC-4.0",
|
||||
"files": [
|
||||
"skills/"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.21.0",
|
||||
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "*",
|
||||
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"validate:skills": "tsx scripts/validate-skills.ts",
|
||||
"sync-react-b2e-sample": "node scripts/sync-react-b2e-sample.js",
|
||||
"sync-react-b2x-sample": "node scripts/sync-react-b2x-sample.js"
|
||||
}
|
||||
|
||||
@ -41,3 +41,14 @@ Source is synced from the npm package [@salesforce/webapp-template-app-react-sam
|
||||
### Version tracking
|
||||
|
||||
The file `samples/webapp-template-app-react-sample-b2x-experimental/.version` stores the last-synced npm version. The Action compares it to the latest on npm and only creates a PR when they differ.
|
||||
|
||||
## native-mobile-rental-tenant-app
|
||||
|
||||
A sample Custom Agentic Mobile App (CAMA) for rental property tenants. This sample is maintained directly in this repository (not synced from npm). It includes:
|
||||
|
||||
- **digitalExperiences** metadata: CAMA app config (`experience__camaAppMetadata`), build metadata (`experience__camaBuildMetadata`), EC definition (`experience__camaECDefinition`), and screens (`experience__camaScreen`) with tabs (Home, Tenants, Properties), theme, and toolbar settings
|
||||
- **Source**: Synced from [cama-mcp-server](https://git.soma.salesforce.com/khawkins/cama-mcp-server) (branch `apply_metadata_updates`)
|
||||
|
||||
### How it's used
|
||||
|
||||
The sample appears on the Agentforce Vibes welcome page under the **Mobile** app type. Users can clone it via the welcome page wizard or directly from this repo.
|
||||
|
||||
15
samples/native-mobile-rental-tenant-app/.forceignore
Normal file
15
samples/native-mobile-rental-tenant-app/.forceignore
Normal file
@ -0,0 +1,15 @@
|
||||
# List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status
|
||||
# More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm
|
||||
#
|
||||
|
||||
package.xml
|
||||
|
||||
# LWC configuration files
|
||||
**/jsconfig.json
|
||||
**/.eslintrc.json
|
||||
|
||||
# LWC Jest
|
||||
**/__tests__/**
|
||||
|
||||
node_modules/
|
||||
.DS_Store
|
||||
20
samples/native-mobile-rental-tenant-app/.gitignore
vendored
Normal file
20
samples/native-mobile-rental-tenant-app/.gitignore
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Salesforce
|
||||
.sfdx/
|
||||
.localdevserver/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
9
samples/native-mobile-rental-tenant-app/README.md
Normal file
9
samples/native-mobile-rental-tenant-app/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Native Mobile Rental Tenant App
|
||||
|
||||
A sample Custom Agentic Mobile App (CAMA) for property managers and tenants. Use this as a starting point for building native mobile apps with Salesforce Mobile Publisher, MCF (Mobile Component Framework), and SharedUI.
|
||||
|
||||
## Overview
|
||||
|
||||
This sample demonstrates a rental tenant experience—browse properties, submit maintenance requests, pay rent, and communicate with property management—all from a native iOS or Android app.
|
||||
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
{
|
||||
"orgName": "Rental Tenant Dev",
|
||||
"edition": "Developer",
|
||||
"features": [],
|
||||
"settings": {
|
||||
"mobileSettings": {
|
||||
"enableS1EncryptedStoragePref2": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"apiName": "appMetadata",
|
||||
"type": "experience__camaAppMetadata",
|
||||
"path": "appMetadata"
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
{
|
||||
"type": "experience__camaAppMetadata",
|
||||
"title": "App Metadata",
|
||||
"contentBody": {
|
||||
"name": "Homestead",
|
||||
"description": "Mobile application for managing property rentals, tenant information, lease agreements, and maintenance requests.",
|
||||
"version": "1.0.0",
|
||||
"url": "http://login.test1.pc-rnd.salesforce.com/",
|
||||
"templateID": "1",
|
||||
"tabs": [
|
||||
{
|
||||
"id": "home",
|
||||
"label": "Home",
|
||||
"icon": "house.fill",
|
||||
"screen": "homeScreen"
|
||||
},
|
||||
{
|
||||
"id": "tenants",
|
||||
"label": "Tenants",
|
||||
"icon": "person.2.fill",
|
||||
"screen": "tenantsScreen"
|
||||
},
|
||||
{
|
||||
"id": "properties",
|
||||
"label": "Properties",
|
||||
"icon": "building.2.fill",
|
||||
"screen": "propertiesScreen"
|
||||
}
|
||||
],
|
||||
"toolbarActions": {
|
||||
"notifications": false,
|
||||
"search": false,
|
||||
"agentforce": false
|
||||
},
|
||||
"theme": {
|
||||
"accentColor": "#9400D3",
|
||||
"primaryColor": "#000000",
|
||||
"secondaryColor": "#B3B3B3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"apiName": "buildMetadata",
|
||||
"type": "experience__camaBuildMetadata",
|
||||
"path": "buildMetadata"
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"type": "experience__camaBuildMetadata",
|
||||
"title": "Build Metadata",
|
||||
"contentBody": {
|
||||
"BiometricOptIn": false,
|
||||
"NotificationOptIn": false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"apiName": "rentalApp",
|
||||
"type": "experience__camaECDefinition",
|
||||
"path": "rentalApp"
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "experience__camaECDefinition",
|
||||
"title": "CAMA EC Definition",
|
||||
"contentBody": {}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"apiName": "homeScreen",
|
||||
"type": "experience__camaScreen",
|
||||
"path": "homeScreen"
|
||||
}
|
||||
@ -0,0 +1,489 @@
|
||||
{
|
||||
"type": "experience__camaScreen",
|
||||
"title": "Home Screen",
|
||||
"contentBody": {
|
||||
"view": {
|
||||
"definition": "homestead/HomeScreen",
|
||||
"properties": {
|
||||
"pullToRefresh": true
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/container",
|
||||
"properties": {
|
||||
"padding": "$spacing.spacing4",
|
||||
"backgroundColor": "$colors.surface2"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/card",
|
||||
"properties": {},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/column",
|
||||
"properties": {
|
||||
"width": "fill",
|
||||
"height": "fill",
|
||||
"alignment": "start",
|
||||
"gap": "$spacing.spacing4",
|
||||
"padding": "$spacing.spacing4"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/text",
|
||||
"properties": {
|
||||
"text": "Today",
|
||||
"style": "titlesFontScale4Semibold",
|
||||
"color": "$colors.onSurface1"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/text",
|
||||
"properties": {
|
||||
"text": "You have no event.",
|
||||
"color": "$colors.onSuccess1",
|
||||
"style": "bodyFontScale2Regular"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/card",
|
||||
"properties": {},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/column",
|
||||
"properties": {
|
||||
"width": "fill",
|
||||
"height": "fill",
|
||||
"alignment": "start",
|
||||
"gap": "$spacing.spacing4",
|
||||
"padding": "$spacing.spacing4"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/text",
|
||||
"properties": {
|
||||
"text": "Upcoming",
|
||||
"style": "titlesFontScale4Semibold",
|
||||
"color": "$colors.onSurface1"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "123 Oak Street",
|
||||
"subtitle": "Inspection on 01/08/2026"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/nothing"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Scheduled",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "456 Maple Avenue",
|
||||
"subtitle": "Lease Signing on 01/10/2026"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/nothing"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Scheduled",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "789 Pine Road",
|
||||
"subtitle": "Maintenance on 01/15/2026"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/nothing"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Requested",
|
||||
"variant": "error"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "321 Elm Street",
|
||||
"subtitle": "Rent Collection on 01/20/2026"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/nothing"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Scheduled",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Community Center",
|
||||
"subtitle": "HOA Meeting on 01/22/2026"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/nothing"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Proposed",
|
||||
"variant": "warning"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "555 Cedar Lane",
|
||||
"subtitle": "Background Check"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/nothing"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "In Progress",
|
||||
"variant": "warning"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/card",
|
||||
"properties": {},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/column",
|
||||
"properties": {
|
||||
"width": "fill",
|
||||
"height": "fill",
|
||||
"alignment": "start",
|
||||
"gap": "$spacing.spacing4",
|
||||
"padding": "$spacing.spacing4"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/text",
|
||||
"properties": {
|
||||
"text": "Tenants",
|
||||
"style": "titlesFontScale4Semibold",
|
||||
"color": "$colors.onSurface1"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Sarah Johnson",
|
||||
"subtitle": "456 Maple Avenue, Apt 5"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Paid",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Michael Chen",
|
||||
"subtitle": "789 Pine Road"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Overdue",
|
||||
"variant": "error"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Emily Davis",
|
||||
"subtitle": "321 Elm Street, Unit 5B"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Paid",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Robert Williams",
|
||||
"subtitle": "555 Cedar Lane"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Paid",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Jessica Martinez",
|
||||
"subtitle": "123 Oak Street, Unit 3C"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Moved Out",
|
||||
"variant": "warning"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "David Lee",
|
||||
"subtitle": "456 Maple Avenue, Apt 2"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Signed",
|
||||
"variant": "lightest"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"target": "native__homesteadHome",
|
||||
"apiName": "uem_homesteadHome"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"apiName": "propertiesScreen",
|
||||
"type": "experience__camaScreen",
|
||||
"path": "propertiesScreen"
|
||||
}
|
||||
@ -0,0 +1,565 @@
|
||||
{
|
||||
"type": "experience__camaScreen",
|
||||
"title": "Properties Screen",
|
||||
"contentBody": {
|
||||
"view": {
|
||||
"definition": "homestead/PropertiesScreen",
|
||||
"properties": {
|
||||
"pullToRefresh": true
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/container",
|
||||
"properties": {
|
||||
"padding": "$spacing.spacing4",
|
||||
"backgroundColor": "$colors.surface2"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/card",
|
||||
"properties": {},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Oak Street Apartments",
|
||||
"subtitle": "123 Oak Street"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Full",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Maple Avenue Complex",
|
||||
"subtitle": "456 Maple Avenue"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Partial",
|
||||
"variant": "warning"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Pine Road House",
|
||||
"subtitle": "789 Pine Road"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Vacant",
|
||||
"variant": "lightest"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Cedar Lane Condos",
|
||||
"subtitle": "555 Cedar Lane"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Full",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Elm Street Tower",
|
||||
"subtitle": "321 Elm Street"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Partial",
|
||||
"variant": "warning"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Birch Boulevard Villa",
|
||||
"subtitle": "234 Birch Boulevard"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Full",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Willow Way Estates",
|
||||
"subtitle": "890 Willow Way"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Vacant",
|
||||
"variant": "lightest"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Sunset Plaza",
|
||||
"subtitle": "567 Sunset Drive"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Full",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Riverside Apartments",
|
||||
"subtitle": "678 River Road"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Partial",
|
||||
"variant": "warning"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Lakeside Manor",
|
||||
"subtitle": "901 Lake Avenue"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Full",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Mountain View Heights",
|
||||
"subtitle": "112 Mountain Road"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Maintenance",
|
||||
"variant": "error"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Harbor Point Lofts",
|
||||
"subtitle": "445 Harbor Street"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Partial",
|
||||
"variant": "warning"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Garden Terrace",
|
||||
"subtitle": "223 Garden Lane"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Full",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Park Place Residences",
|
||||
"subtitle": "334 Park Avenue"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Vacant",
|
||||
"variant": "lightest"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Hillside Court",
|
||||
"subtitle": "778 Hill Street"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Full",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Downtown Tower",
|
||||
"subtitle": "990 Main Street"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Partial",
|
||||
"variant": "warning"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Bayside Commons",
|
||||
"subtitle": "101 Bay Road"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Full",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Meadow Brook Village",
|
||||
"subtitle": "212 Meadow Lane"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/home",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Maintenance",
|
||||
"variant": "error"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"target": "native__homesteadProperties",
|
||||
"apiName": "uem_homesteadProperties"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"apiName": "tenantsScreen",
|
||||
"type": "experience__camaScreen",
|
||||
"path": "tenantsScreen"
|
||||
}
|
||||
@ -0,0 +1,478 @@
|
||||
{
|
||||
"type": "experience__camaScreen",
|
||||
"title": "Tenants Screen",
|
||||
"contentBody": {
|
||||
"view": {
|
||||
"definition": "homestead/TenantsScreen",
|
||||
"properties": {
|
||||
"pullToRefresh": true
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/container",
|
||||
"properties": {
|
||||
"padding": "$spacing.spacing4",
|
||||
"backgroundColor": "$colors.surface2"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/card",
|
||||
"properties": {},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Sarah Johnson",
|
||||
"subtitle": "456 Maple Avenue, Apt 5"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Paid",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Michael Chen",
|
||||
"subtitle": "789 Pine Road"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Overdue",
|
||||
"variant": "error"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Emily Davis",
|
||||
"subtitle": "321 Elm Street, Unit 5B"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Paid",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Robert Williams",
|
||||
"subtitle": "555 Cedar Lane"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Paid",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Jessica Martinez",
|
||||
"subtitle": "123 Oak Street, Unit 3C"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Moved Out",
|
||||
"variant": "warning"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "David Lee",
|
||||
"subtitle": "456 Maple Avenue, Apt 2"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Signed",
|
||||
"variant": "lightest"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Amanda Thompson",
|
||||
"subtitle": "789 Pine Road, Unit 7A"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Paid",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Christopher Garcia",
|
||||
"subtitle": "321 Elm Street, Apt 8"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Overdue",
|
||||
"variant": "error"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Nicole Anderson",
|
||||
"subtitle": "555 Cedar Lane, Unit 12B"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Paid",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "James Wilson",
|
||||
"subtitle": "123 Oak Street, Apt 1A"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Pending",
|
||||
"variant": "warning"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Rachel Brown",
|
||||
"subtitle": "456 Maple Avenue, Unit 9C"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Paid",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Kevin Taylor",
|
||||
"subtitle": "789 Pine Road, Apt 4B"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Signed",
|
||||
"variant": "lightest"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Stephanie Miller",
|
||||
"subtitle": "321 Elm Street, Unit 6D"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Paid",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Brian Martinez",
|
||||
"subtitle": "555 Cedar Lane, Apt 10A"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Overdue",
|
||||
"variant": "error"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"definition": "ui/baseRow",
|
||||
"properties": {
|
||||
"title": "Laura Rodriguez",
|
||||
"subtitle": "123 Oak Street, Unit 11B"
|
||||
},
|
||||
"regions": {
|
||||
"components": {
|
||||
"components": [
|
||||
{
|
||||
"definition": "ui/utilityIcon",
|
||||
"properties": {
|
||||
"iconName": "utility/user",
|
||||
"size": "small"
|
||||
},
|
||||
"regions": {}
|
||||
},
|
||||
{
|
||||
"definition": "ui/badge",
|
||||
"properties": {
|
||||
"label": "Paid",
|
||||
"variant": "success"
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"target": "native__homesteadTenants",
|
||||
"apiName": "uem_homesteadTenants"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<DigitalExperienceBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>CAMA Rental App</label>
|
||||
</DigitalExperienceBundle>
|
||||
13
samples/native-mobile-rental-tenant-app/package.json
Normal file
13
samples/native-mobile-rental-tenant-app/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "native-mobile-rental-tenant-app",
|
||||
"version": "1.0.0",
|
||||
"description": "Sample CAMA app for rental property tenants",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "echo 'No build required for base project'",
|
||||
"clean": "echo 'No clean required'",
|
||||
"lint": "eslint **/{aura,lwc}/**/*.js 2>/dev/null || true",
|
||||
"test": "echo 'Add tests when ready'"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
12
samples/native-mobile-rental-tenant-app/sfdx-project.json
Normal file
12
samples/native-mobile-rental-tenant-app/sfdx-project.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"packageDirectories": [
|
||||
{
|
||||
"path": "force-app",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"name": "RentalTenantApp",
|
||||
"namespace": "",
|
||||
"sfdcLoginUrl": "https://login.salesforce.com",
|
||||
"sourceApiVersion": "66.0"
|
||||
}
|
||||
368
scripts/validate-skills.ts
Normal file
368
scripts/validate-skills.ts
Normal file
@ -0,0 +1,368 @@
|
||||
#!/usr/bin/env tsx
|
||||
// Validates the skills/ directory structure and SKILL.md format.
|
||||
// Exits with code 1 if any violations are found.
|
||||
//
|
||||
// Usage:
|
||||
// npm run validate:skills # validate all skills
|
||||
// npm run validate:skills -- --changed --base=origin/main # validate only skills changed vs base
|
||||
|
||||
import { execSync } from "child_process"
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import { parseArgs } from "util"
|
||||
|
||||
const SKILLS_DIR = path.join(__dirname, "..", "skills")
|
||||
|
||||
/** Parsed context for a single skill directory, built before content checks run. */
|
||||
interface SkillContext {
|
||||
dirName: string
|
||||
dirPath: string
|
||||
content: string
|
||||
frontmatter: Record<string, string> | null
|
||||
body: string
|
||||
}
|
||||
|
||||
/** Return value of every check function. */
|
||||
interface CheckResult {
|
||||
errors: string[]
|
||||
/** When true and errors is non-empty, skip remaining checks for this entry. */
|
||||
fatal?: boolean
|
||||
/** Defaults to "error". Warnings are printed but do not cause a non-zero exit code. */
|
||||
severity?: "error" | "warning"
|
||||
}
|
||||
|
||||
/** Collected results for a single skill entry. */
|
||||
interface SkillResult {
|
||||
errors: string[]
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
/** Runs before SKILL.md is read — validates directory layout. */
|
||||
interface StructureCheck {
|
||||
description: string
|
||||
run(dirName: string, dirPath: string): CheckResult
|
||||
}
|
||||
|
||||
/** Runs after SKILL.md is read — validates file content. */
|
||||
interface ContentCheck {
|
||||
description: string
|
||||
run(ctx: SkillContext): CheckResult
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure checks run on every entry in `skills/` before SKILL.md is read.
|
||||
* A fatal error aborts content checks for that entry.
|
||||
*/
|
||||
const STRUCTURE_CHECKS: StructureCheck[] = [
|
||||
{
|
||||
description: "Entry must be a directory (no loose files in skills/)",
|
||||
run(dirName, dirPath) {
|
||||
if (!fs.statSync(dirPath).isDirectory()) {
|
||||
return { errors: [`Loose file in skills/: ${dirName} (expected only directories)`], fatal: true }
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Skill directory must contain SKILL.md",
|
||||
run(dirName, dirPath) {
|
||||
if (!fs.existsSync(path.join(dirPath, "SKILL.md"))) {
|
||||
return { errors: [`Missing SKILL.md in skills/${dirName}/`], fatal: true }
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Name must be kebab-case (lowercase letters, digits, and hyphens only)",
|
||||
run(dirName) {
|
||||
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(dirName)) {
|
||||
return { errors: [`skills/${dirName}: name must be kebab-case (only lowercase letters, digits, and hyphens)`] }
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Name must be at most 64 characters",
|
||||
run(dirName) {
|
||||
if (dirName.length > 64) {
|
||||
return { errors: [`skills/${dirName}: name is ${dirName.length} characters (maximum 64)`] }
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Name should use gerund form — first word should end in -ing (e.g. generating-apex-tests)",
|
||||
run(dirName) {
|
||||
if (!dirName.split("-")[0].endsWith("ing")) {
|
||||
return {
|
||||
errors: [`skills/${dirName}: name should use gerund form (e.g. generating-apex-tests, refactoring-triggers)`],
|
||||
severity: "warning",
|
||||
}
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Skills must be exactly one level deep (no nested category directories)",
|
||||
run(dirName, dirPath) {
|
||||
const errors: string[] = []
|
||||
for (const sub of fs.readdirSync(dirPath)) {
|
||||
const subPath = path.join(dirPath, sub)
|
||||
if (fs.statSync(subPath).isDirectory() && fs.existsSync(path.join(subPath, "SKILL.md"))) {
|
||||
errors.push(
|
||||
`Nested skill detected: skills/${dirName}/${sub}/SKILL.md — skill directories must be exactly one level deep under skills/`
|
||||
)
|
||||
}
|
||||
}
|
||||
return { errors }
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Content checks run only on entries that have a valid SKILL.md.
|
||||
* A fatal error aborts remaining content checks for that entry.
|
||||
*/
|
||||
const CONTENT_CHECKS: ContentCheck[] = [
|
||||
{
|
||||
description: "SKILL.md must have a valid YAML frontmatter block (--- ... ---)",
|
||||
run({ dirName, frontmatter }) {
|
||||
if (!frontmatter) {
|
||||
return {
|
||||
errors: [`skills/${dirName}/SKILL.md: missing or malformed YAML frontmatter (expected --- ... --- block at top)`],
|
||||
fatal: true,
|
||||
}
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Frontmatter "name" must be present and match the directory name',
|
||||
run({ dirName, frontmatter }) {
|
||||
if (!frontmatter) return { errors: [] }
|
||||
if (!frontmatter.name) {
|
||||
return { errors: [`skills/${dirName}/SKILL.md: missing "name" field in frontmatter`] }
|
||||
}
|
||||
if (frontmatter.name !== dirName) {
|
||||
return {
|
||||
errors: [
|
||||
`skills/${dirName}/SKILL.md: "name" value ("${frontmatter.name}") does not match directory name ("${dirName}")`,
|
||||
],
|
||||
}
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Frontmatter "description" must be present and non-empty',
|
||||
run({ dirName, frontmatter }) {
|
||||
if (!frontmatter) return { errors: [] }
|
||||
if (!frontmatter.description?.trim()) {
|
||||
return { errors: [`skills/${dirName}/SKILL.md: missing or empty "description" field in frontmatter`] }
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "SKILL.md must have a non-empty body (instructions after the frontmatter block)",
|
||||
run({ dirName, body }) {
|
||||
if (!body.trim()) {
|
||||
return { errors: [`skills/${dirName}/SKILL.md: body (instructions after frontmatter) is empty`] }
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Description must be at least 20 words to be information-rich",
|
||||
run({ dirName, frontmatter }) {
|
||||
if (!frontmatter) return { errors: [] }
|
||||
const words = frontmatter.description?.trim().split(/\s+/) ?? []
|
||||
if (words.length < 20) {
|
||||
return {
|
||||
errors: [`skills/${dirName}/SKILL.md: description too short (${words.length} word(s), minimum 20)`],
|
||||
}
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Description must be at most 1024 characters",
|
||||
run({ dirName, frontmatter }) {
|
||||
if (!frontmatter) return { errors: [] }
|
||||
const len = frontmatter.description?.length ?? 0
|
||||
if (len > 1024) {
|
||||
return { errors: [`skills/${dirName}/SKILL.md: description is ${len} characters (maximum 1024)`] }
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Description must include trigger/activation language (contain "use")',
|
||||
run({ dirName, frontmatter }) {
|
||||
if (!frontmatter) return { errors: [] }
|
||||
if (!frontmatter.description?.toLowerCase().includes("use")) {
|
||||
return {
|
||||
errors: [
|
||||
`skills/${dirName}/SKILL.md: description must include trigger context (e.g. "Use this skill when...")`,
|
||||
],
|
||||
}
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Skill body should be under 500 lines for context efficiency",
|
||||
run({ dirName, body }) {
|
||||
const lines = body.split("\n").length
|
||||
if (lines > 500) {
|
||||
return {
|
||||
errors: [`skills/${dirName}/SKILL.md: body is ${lines} lines (recommended maximum is 500)`],
|
||||
severity: "warning",
|
||||
}
|
||||
}
|
||||
return { errors: [] }
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Returns the deduplicated list of top-level skill directory names that have
|
||||
* changed relative to `base` (e.g. `origin/main`) and still exist on disk.
|
||||
* Deleted skill directories are intentionally excluded — removing a skill is
|
||||
* valid and requires no structural validation.
|
||||
*/
|
||||
function getChangedSkillDirs(base: string): string[] {
|
||||
const output = execSync(`git diff --name-only ${base}...HEAD`, { encoding: "utf8" })
|
||||
return [
|
||||
...new Set(
|
||||
output
|
||||
.split("\n")
|
||||
.filter((f) => f.startsWith("skills/"))
|
||||
.map((f) => f.split("/")[1])
|
||||
.filter(Boolean)
|
||||
),
|
||||
].filter((dir) => fs.existsSync(path.join(SKILLS_DIR, dir)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a SKILL.md file into its frontmatter fields and body.
|
||||
* `frontmatter` is `null` if the `--- ... ---` block is missing or malformed.
|
||||
* Wrapping quotes on frontmatter values are stripped.
|
||||
*/
|
||||
function parseSkillMd(content: string): { frontmatter: Record<string, string> | null; body: string } {
|
||||
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/)
|
||||
if (!match) return { frontmatter: null, body: content }
|
||||
|
||||
const frontmatter: Record<string, string> = {}
|
||||
for (const line of match[1].split(/\r?\n/)) {
|
||||
const colonIdx = line.indexOf(":")
|
||||
if (colonIdx === -1) continue
|
||||
const key = line.slice(0, colonIdx).trim()
|
||||
const raw = line.slice(colonIdx + 1).trim()
|
||||
// Strip wrapping single or double quotes to match how consumers read values
|
||||
frontmatter[key] = raw.replace(/^(['"])([\s\S]*)\1$/, "$2")
|
||||
}
|
||||
|
||||
return { frontmatter, body: content.slice(match[0].length) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all structure and content checks for a single skill directory.
|
||||
* Returns errors (block CI) and warnings (advisory, printed but exit 0) separately.
|
||||
*/
|
||||
function validateSkill(dirName: string, dirPath: string): SkillResult {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
return { errors: [`skills/${dirName}: directory not found`], warnings: [] }
|
||||
}
|
||||
|
||||
const errors: string[] = []
|
||||
const warnings: string[] = []
|
||||
|
||||
const collectIssues = (result: CheckResult): boolean => {
|
||||
if (result.errors.length === 0) return false
|
||||
if (result.severity === "warning") {
|
||||
warnings.push(...result.errors)
|
||||
} else {
|
||||
errors.push(...result.errors)
|
||||
}
|
||||
return result.fatal === true && result.severity !== "warning"
|
||||
}
|
||||
|
||||
for (const check of STRUCTURE_CHECKS) {
|
||||
if (collectIssues(check.run(dirName, dirPath))) return { errors, warnings }
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(path.join(dirPath, "SKILL.md"), "utf8")
|
||||
const { frontmatter, body } = parseSkillMd(content)
|
||||
const ctx: SkillContext = { dirName, dirPath, content, frontmatter, body }
|
||||
|
||||
for (const check of CONTENT_CHECKS) {
|
||||
if (collectIssues(check.run(ctx))) return { errors, warnings }
|
||||
}
|
||||
|
||||
return { errors, warnings }
|
||||
}
|
||||
|
||||
/** CLI entry point. Parses flags, resolves the list of skills to check, and reports results. */
|
||||
function main(): void {
|
||||
const { values } = parseArgs({
|
||||
args: process.argv.slice(2),
|
||||
options: {
|
||||
/** Validate only skill dirs touched in this branch vs the given base ref. */
|
||||
changed: { type: "boolean", default: false },
|
||||
/** Base ref for --changed (e.g. origin/main). Defaults to origin/HEAD. */
|
||||
base: { type: "string", default: "origin/HEAD" },
|
||||
},
|
||||
})
|
||||
|
||||
let entries: string[]
|
||||
|
||||
if (values.changed) {
|
||||
entries = getChangedSkillDirs(values.base!)
|
||||
if (entries.length === 0) {
|
||||
console.log("No skill directories changed — nothing to validate.")
|
||||
return
|
||||
}
|
||||
console.log(`Validating ${entries.length} changed skill(s): ${entries.join(", ")}`)
|
||||
} else {
|
||||
entries = fs.readdirSync(SKILLS_DIR)
|
||||
}
|
||||
|
||||
const allErrors: string[] = []
|
||||
const allWarnings: string[] = []
|
||||
let passed = 0
|
||||
|
||||
for (const entry of entries) {
|
||||
const { errors, warnings } = validateSkill(entry, path.join(SKILLS_DIR, entry))
|
||||
allErrors.push(...errors)
|
||||
allWarnings.push(...warnings)
|
||||
if (errors.length === 0) passed++
|
||||
}
|
||||
|
||||
const hasIssues = allErrors.length > 0 || allWarnings.length > 0
|
||||
const footer = "Spec: https://agentskills.io/specification · Authoring guide: https://github.com/forcedotcom/afv-library#readme"
|
||||
|
||||
if (allWarnings.length > 0) {
|
||||
console.warn(`\n${allWarnings.length} warning(s):\n`)
|
||||
for (const w of allWarnings) {
|
||||
console.warn(` ⚠ ${w}`)
|
||||
}
|
||||
console.warn("")
|
||||
}
|
||||
|
||||
if (allErrors.length > 0) {
|
||||
console.error(`\nSkill validation failed with ${allErrors.length} error(s):\n`)
|
||||
for (const err of allErrors) {
|
||||
console.error(` ✗ ${err}`)
|
||||
}
|
||||
console.error("")
|
||||
console.error(footer)
|
||||
console.error("")
|
||||
process.exit(1)
|
||||
} else {
|
||||
console.log(`Skill validation passed: ${passed} of ${entries.length} skill(s) checked.`)
|
||||
if (hasIssues) console.warn(footer)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
171
skills/generate-permission-set/SKILL.md
Normal file
171
skills/generate-permission-set/SKILL.md
Normal file
@ -0,0 +1,171 @@
|
||||
---
|
||||
name: generate-permission-set
|
||||
description: Generates correct, deployable Salesforce permission set metadata (PermissionSet XML) with object, field, user, and app permissions. Use when creating or editing permission set metadata, PermissionSet XML, object permissions, field-level security (FLS), tab visibility, or deploying permission sets.
|
||||
compatibility: Salesforce Metadata API v60.0+
|
||||
metadata:
|
||||
author: afv-library
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use when generating or editing permission set metadata, or when granting object, field, user, and app permissions.
|
||||
|
||||
## Step 1: Define Core Properties
|
||||
|
||||
Start by defining the required permission set properties:
|
||||
|
||||
```xml
|
||||
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>YourPermissionSetName</fullName>
|
||||
<label>Display Name for Administrators</label>
|
||||
<description>Clear description of purpose and intended audience</description>
|
||||
</PermissionSet>
|
||||
```
|
||||
|
||||
**Naming conventions:**
|
||||
- Use descriptive API names (e.g., `Sales_Manager_Access`)
|
||||
|
||||
## Step 2: Configure Object Permissions
|
||||
|
||||
Add CRUD permissions for standard and custom objects:
|
||||
|
||||
```xml
|
||||
<objectPermissions>
|
||||
<allowCreate>true</allowCreate>
|
||||
<allowRead>true</allowRead>
|
||||
<allowEdit>true</allowEdit>
|
||||
<allowDelete>false</allowDelete>
|
||||
<modifyAllRecords>false</modifyAllRecords>
|
||||
<viewAllRecords>false</viewAllRecords>
|
||||
<viewAllFields>false</viewAllFields>
|
||||
<object>Account</object>
|
||||
</objectPermissions>
|
||||
```
|
||||
|
||||
## Step 3: Set Field-Level Security
|
||||
|
||||
Define field permissions for sensitive or custom fields:
|
||||
|
||||
```xml
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<readable>true</readable>
|
||||
<field>Account.SSN__c</field>
|
||||
</fieldPermissions>
|
||||
```
|
||||
|
||||
**Important:**
|
||||
- Required fields must NEVER appear in list of field permissions. Granting field-level security on required fields is not allowed by the platform and will cause deployment failure.
|
||||
- Before adding any field, confirm from the object metadata that the field exists and is not required
|
||||
- A field is required when its metadata contains `<required>true</required>`:
|
||||
```xml
|
||||
<fields>
|
||||
<fullName>FieldName__c</fullName>
|
||||
<required>true</required>
|
||||
</fields>
|
||||
```
|
||||
- Use format `ObjectName.FieldName` for field references
|
||||
- Set both readable and editable to true when the user needs edit access; editable implies readable
|
||||
- If all fields should be visible, can alternatively enable the "viewAllFields" object permission
|
||||
|
||||
## Step 4: Grant User Permissions
|
||||
|
||||
Add system-level permissions for features and capabilities:
|
||||
|
||||
```xml
|
||||
<userPermissions>
|
||||
<enabled>true</enabled>
|
||||
<name>ApiEnabled</name>
|
||||
</userPermissions>
|
||||
<userPermissions>
|
||||
<enabled>true</enabled>
|
||||
<name>RunReports</name>
|
||||
</userPermissions>
|
||||
```
|
||||
|
||||
**Common permissions:**
|
||||
- `ApiEnabled`: API access
|
||||
- `ViewSetup`: View Setup menu
|
||||
- `ManageUsers`: User management
|
||||
- `RunReports`: Report execution
|
||||
|
||||
**Security review required for:**
|
||||
- `ViewAllData`: Read all records
|
||||
- `ModifyAllData`: Edit all records
|
||||
- `ManageUsers`: User administration
|
||||
|
||||
## Step 5: Configure App and Tab Visibility
|
||||
|
||||
Make applications and tabs visible to users:
|
||||
|
||||
```xml
|
||||
<applicationVisibilities>
|
||||
<application>Sales_Console</application>
|
||||
<visible>true</visible>
|
||||
</applicationVisibilities>
|
||||
<tabSettings>
|
||||
<tab>CustomTab__c</tab>
|
||||
<visibility>Visible</visibility>
|
||||
</tabSettings>
|
||||
```
|
||||
|
||||
**Application visibility options:**
|
||||
- <visible> can be true or false
|
||||
|
||||
**Tab visibility options:**
|
||||
- `Visible`: Always shown
|
||||
- `Available`: Available but not default
|
||||
- `Hidden`: Not visible
|
||||
|
||||
**CRITICAL - Tab Naming:**
|
||||
- Custom object tabs: MUST include the __c suffix (e.g., MyCustomObject__c)
|
||||
- Standard object tabs: Use the object name with "standard-" prefix (e.g., standard-Account, standard-Contact)
|
||||
- The tab name matches the object's API name exactly
|
||||
|
||||
## Step 6: Add Apex and Visualforce Access (Optional)
|
||||
|
||||
Grant access to custom code:
|
||||
|
||||
```xml
|
||||
<classAccesses>
|
||||
<apexClass>CustomController</apexClass>
|
||||
<enabled>true</enabled>
|
||||
</classAccesses>
|
||||
<pageAccesses>
|
||||
<apexPage>CustomPage</apexPage>
|
||||
<enabled>true</enabled>
|
||||
</pageAccesses>
|
||||
```
|
||||
|
||||
## Step 7: Set License and Record Type Settings (Optional)
|
||||
|
||||
Specify license requirements and record type visibility:
|
||||
|
||||
```xml
|
||||
<license>Salesforce</license>
|
||||
<hasActivationRequired>false</hasActivationRequired>
|
||||
<recordTypeVisibilities>
|
||||
<recordType>Account.Business</recordType>
|
||||
<visible>true</visible>
|
||||
<default>true</default>
|
||||
</recordTypeVisibilities>
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Before deploying, verify:
|
||||
- [ ] fullName, label, description set
|
||||
- [ ] Permissions follow least privilege
|
||||
- [ ] No required fields in `<fieldPermissions>`
|
||||
- [ ] No duplicate permissions
|
||||
- [ ] no lengthy comments
|
||||
|
||||
## What Causes Deployment Failure
|
||||
|
||||
- **Field permissions on required fields:** Any required field in `<fieldPermissions>` fails deployment. Required fields cannot have FLS; omit them entirely. Always confirm from object/field metadata that a field exists and is not required—never assume.
|
||||
- **Incorrect API names:** Using the wrong name or missing suffixes (e.g. missing `__c` for custom objects, fields, tabs) cause failure.
|
||||
|
||||
## Deployment
|
||||
|
||||
Deploy using Salesforce CLI
|
||||
@ -1,76 +0,0 @@
|
||||
---
|
||||
name: lex-app-solution
|
||||
description: Use this skill to build and orchestrate complete Salesforce Lightning Applications (LEX Apps), custom projects, or end-to-end business solutions from a natural language scenario. Triggers when a user requests a "custom app", a "business solution", or describes any scenario requiring multiple interconnected Salesforce components to be built together into a complete Lightning Experience (LEX) Application. Orchestrates the sequenced creation of Custom Objects, Relationships, Fields, Lightning Record Pages, Custom Tabs, Custom Applications, Permission Sets, and OPTIONALLY Flows and Validation Rules.
|
||||
---
|
||||
|
||||
# Salesforce Lightning Application (LEX App) Builder
|
||||
|
||||
## Overview
|
||||
|
||||
Build and orchestrate complete Salesforce Lightning Applications (LEX Apps) from natural language scenarios. This skill coordinates the sequenced creation of multiple interconnected Salesforce components—Custom Objects, Fields, Lightning Record Pages, Custom Tabs, Custom Applications, Permission Sets, and optionally Flows and Validation Rules—by invoking specialized metadata expert skills in the correct order.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when you need to:
|
||||
- Build or enhance a complete Lightning Application (LEX App) from a single user prompt.
|
||||
- Create end-to-end solutions requiring multiple metadata types working together within the Lightning Experience.
|
||||
- Ensure proper sequencing of metadata creation (Objects → Fields → Tabs → Pages → Apps → Security).
|
||||
|
||||
**Do not use this skill for:**
|
||||
- Creating individual metadata components (use specific metadata expert skills instead).
|
||||
- Troubleshooting or fixing deployment errors.
|
||||
- Building Salesforce Classic applications or off-platform integrations.
|
||||
|
||||
## Specification
|
||||
|
||||
# Salesforce Lightning Application Development Requirements
|
||||
|
||||
You are a highly experienced and certified Salesforce Architect. Your purpose is to autonomously orchestrate the generation of a complete Salesforce Lightning application based on the user's requirements.
|
||||
|
||||
## Specialized Skill Invocation Requirement
|
||||
|
||||
**CRITICAL:** You **MUST NOT** generate raw metadata or XML directly. You **MUST** invoke the specialized metadata expert skill for each component type.
|
||||
|
||||
### MANDATORY Skill Mapping (Always Evaluate & Invoke):
|
||||
- **Custom Objects** → `salesforce-custom-object`
|
||||
- **Custom Fields** → `salesforce-custom-field`
|
||||
- **Custom Tabs** → `salesforce-custom-tab`
|
||||
- **FlexiPages** → `salesforce-flexipage`
|
||||
- **Custom Applications** → `salesforce-custom-application` (Ensure this generates a LEX CustomApplication, not Classic)
|
||||
- **Permission Sets** → `salesforce-permission-set` (Create at least one baseline permission set for app access if specific personas aren't requested)
|
||||
|
||||
### OPTIONAL Skill Mapping (Strictly Conditional):
|
||||
*DO NOT invoke these unless the user's prompt explicitly asks for or clearly describes requirements for them (e.g., "enforce that...", "automate the...").*
|
||||
- **Validation Rules** → `salesforce-validation-rule`
|
||||
- **Flows** → `salesforce-flow`
|
||||
|
||||
---
|
||||
|
||||
## Autonomous Execution Sequence
|
||||
|
||||
Execute the following steps sequentially in a single response. Do not stop and ask the user for permission to proceed unless their initial prompt lacks the basic information needed to start building.
|
||||
|
||||
### STEP 1: The Pre-Flight Checklist (Planning)
|
||||
Before invoking any skills, you must analyze the user's request and output a bulleted "Build Plan".
|
||||
- Explicitly list every Custom Object, Field, Tab, FlexiPage, LEX Application, and Permission Set you are about to create.
|
||||
- **Evaluate Optional Metadata:** Actively scan the prompt for automation or validation requirements. If found, add them to the plan. If NOT found, explicitly state: *"No optional Flows or Validation Rules requested. Skipping."*
|
||||
- Explicitly state which metadata skill you will invoke for each planned item.
|
||||
|
||||
### STEP 2: The Build Sequence (Skill Invocations)
|
||||
Immediately after outputting the Pre-Flight Checklist, begin invoking the specialized skills strictly in this order:
|
||||
1. **Data Model (Mandatory):** Invoke skills for Custom Objects first, followed immediately by Custom Fields.
|
||||
2. **Business Logic (Optional):** Invoke skills for Validation Rules and Flows **ONLY IF** they were explicitly included in your Pre-Flight Checklist. Otherwise, skip this step entirely.
|
||||
3. **User Experience (Mandatory):** Invoke skills for Custom Tabs. Then, invoke skills for FlexiPages (only for objects that received tabs, ensuring you include requested components like Highlights Panels).
|
||||
4. **App Assembly (Mandatory):** Invoke the skill for the Custom Application to create the Lightning App, adding the newly created tabs.
|
||||
5. **Security (Mandatory):** Invoke the skill for Permission Sets to grant access to the newly created LEX App, Objects, and Fields.
|
||||
|
||||
### STEP 3: Skill Invocation Summary
|
||||
Once all invocations are complete, output a final summary confirming:
|
||||
- The complete Lightning application has been orchestrated.
|
||||
- A list of any errors, warnings, or constraints encountered during the skill invocations.
|
||||
|
||||
---
|
||||
|
||||
### Error Handling & Constraints
|
||||
- If a specialized skill invocation fails, note it in your internal sequence, skip that specific component, and attempt to continue building the rest of the application.
|
||||
- Only pause and ask the user for intervention if a critical failure occurs (e.g., a primary Custom Object fails to generate).
|
||||
@ -42,6 +42,10 @@ Every generated field must include these tags:
|
||||
| `<description>` | Mandatory | State the business "why" behind the field |
|
||||
| `<inlineHelpText>` | Mandatory | Provide actionable guidance for the end-user. Must add value beyond the label (e.g., "Enter the value in USD including tax" instead of just "The amount") |
|
||||
|
||||
### XML Comments — NEVER Before Root Element
|
||||
|
||||
**NEVER place XML comments (`<!-- ... -->`) before the root `<CustomField>` element in metadata XML files.** Comments between the XML declaration and `<CustomField>` cause a `ConversionError` during deployment. Comments inside the root element are safe.
|
||||
|
||||
### External ID Configuration
|
||||
|
||||
**Trigger:** If the user mentions "integration," "importing data," "external system ID," or "unique key from [System Name]," set `<externalId>true</externalId>`.
|
||||
@ -440,6 +444,7 @@ Formula fields that reference other fields will fail deployment if the reference
|
||||
|
||||
| Error Message | Cause | Fix |
|
||||
|---------------|-------|-----|
|
||||
| `ConversionError: Invalid XML tags or unable to find matching parent xml file for CustomField` | XML comments placed before the root `<CustomField>` element | Remove XML comments (`<!-- ... -->`) that appear before `<CustomField>` in the `.field-meta.xml` file |
|
||||
| `Field [FieldName] does not exist. Check spelling.` | Referenced field does not exist or has not been deployed yet | Verify the referenced field exists and is deployed before this field |
|
||||
| `DUPLICATE_DEVELOPER_NAME` | Field fullName already exists on the object | Use a unique business-driven name |
|
||||
| `MAX_RELATIONSHIPS_EXCEEDED` | More than 2 Master-Detail or 15 Lookup fields on the object | Use Lookup for 3rd+ Master-Detail; review Lookup count |
|
||||
@ -455,6 +460,7 @@ Before generating CustomField XML, verify:
|
||||
- [ ] Does `<fullName>` use valid format and end in `__c`?
|
||||
- [ ] Are `<description>` and `<inlineHelpText>` both populated and meaningful?
|
||||
- [ ] Is `<label>` in Title Case?
|
||||
- [ ] Are there no XML comments (`<!-- ... -->`) before the root `<CustomField>` element? (Comments before the root element break SDR's parser)
|
||||
|
||||
### Master-Detail Field Checks ⭐ CRITICAL
|
||||
- [ ] Is `<required>` attribute ABSENT? (Master-Detail is always required)
|
||||
@ -496,4 +502,4 @@ Before generating CustomField XML, verify:
|
||||
|
||||
### Naming Checks
|
||||
- [ ] Is the API name free of reserved words (`Order`, `Group`, `Select`, etc.)?
|
||||
- [ ] Is the API name unique on this object?
|
||||
- [ ] Is the API name unique on this object?
|
||||
@ -16,7 +16,9 @@ Use this skill when you need to:
|
||||
|
||||
## 1. Overview and Purpose
|
||||
|
||||
This document defines the mandatory constraints for generating CustomObject metadata XML (`.object` file). The agent must verify these constraints before outputting XML to prevent Metadata API deployment errors.
|
||||
This document defines the mandatory constraints for generating CustomObject metadata XML (`.object-meta.xml` file). The agent must verify these constraints before outputting XML to prevent Metadata API deployment errors.
|
||||
|
||||
**File extension:** `.object-meta.xml`
|
||||
|
||||
---
|
||||
|
||||
@ -24,7 +26,7 @@ This document defines the mandatory constraints for generating CustomObject meta
|
||||
|
||||
The following constraints must be true for the XML body to deploy successfully.
|
||||
|
||||
**Note:** The API Name (fullName) is NOT a tag; it is the filename (e.g., `Vehicle__c.object`).
|
||||
**Note:** The API Name (fullName) is NOT a tag; it is the filename (e.g., `Vehicle__c.object-meta.xml`).
|
||||
|
||||
### Required Elements
|
||||
|
||||
@ -145,7 +147,7 @@ Do not create more than **2 Master-Detail relationships** for a single object. I
|
||||
|
||||
### XML Root Element
|
||||
|
||||
Do NOT include the `<fullName>` tag at the root of the `.object` XML file. The API name is derived from the filename.
|
||||
Do NOT include the `<fullName>` tag at the root of the `.object-meta.xml` file. The API name is derived from the filename.
|
||||
|
||||
**❌ INCORRECT:**
|
||||
```xml
|
||||
@ -159,7 +161,7 @@ Do NOT include the `<fullName>` tag at the root of the `.object` XML file. The A
|
||||
```xml
|
||||
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Vehicle</label>
|
||||
<!-- fullName comes from filename: Vehicle__c.object -->
|
||||
<!-- fullName comes from filename: Vehicle__c.object-meta.xml -->
|
||||
</CustomObject>
|
||||
```
|
||||
|
||||
@ -233,4 +235,4 @@ Before generating the Custom Object XML, verify:
|
||||
### Architectural Checks
|
||||
- [ ] Is `<description>` present with a meaningful summary?
|
||||
- [ ] Are `<enableSearch>` and `<enableReports>` set to `true` if user-facing?
|
||||
- [ ] Does the filename match the intended API name?
|
||||
- [ ] Does the filename match the intended API name?
|
||||
@ -6,17 +6,21 @@ description: Use this skill when users need to create, generate, modify, or vali
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when you need to:
|
||||
- Create Lightning pages (Record, App, or Home pages)
|
||||
- Build custom page layouts in Lightning Experience
|
||||
- Add components to Lightning pages
|
||||
- Configure page structure and components
|
||||
- Troubleshoot deployment errors related to FlexiPages
|
||||
- Create Lightning pages (RecordPage, AppPage, HomePage)
|
||||
- Generate FlexiPage metadata XML
|
||||
- Add components to existing FlexiPages
|
||||
- Troubleshoot FlexiPage deployment errors
|
||||
- Understand FlexiPage structure and component configuration
|
||||
- Work with page layouts or Lightning page customization
|
||||
- Edit or update ANY *.flexipage-meta.xml file
|
||||
|
||||
## Specification
|
||||
|
||||
# FlexiPage Generation Guide
|
||||
|
||||
## Overview
|
||||
Generate Lightning pages (RecordPage, AppPage, HomePage) using CLI bootstrapping + MCP actions for component discovery and configuration.
|
||||
|
||||
Generate Lightning pages (RecordPage, AppPage, HomePage) using CLI bootstrapping for component discovery and configuration.
|
||||
|
||||
---
|
||||
|
||||
@ -37,10 +41,16 @@ sf template generate flexipage \
|
||||
|
||||
**Template-specific requirements:**
|
||||
- **RecordPage**: Requires `--sobject` (e.g., Account, Custom_Object__c)
|
||||
- **RecordPage**: Requires `--primary-field` and `--secondary-fields` for dynamic highlights, `--detail-fields` for full record details. Use the most important identifying field as primary, e.g. Name. Use the secondary fields (max 12, recommended 4-6) to show a summary of the record. Use Use detail fields to show the full details of the record.
|
||||
- **RecordPage**: Requires `--primary-field` and `--secondary-fields` for dynamic highlights, `--detail-fields` for full record details. Use the most important identifying field as primary, e.g. Name. Use the secondary fields (max 12, recommended 4-6) to show a summary of the record. Use detail fields to show the full details of the record.
|
||||
- **AppPage**: No additional requirements
|
||||
- **HomePage**: No additional requirements
|
||||
|
||||
**Note:** If the `sf template generate flexipage` command fails, recommend users upgrade to the latest version of the Salesforce CLI:
|
||||
```bash
|
||||
npm install -g @salesforce/cli@latest
|
||||
```
|
||||
|
||||
|
||||
**What you get:**
|
||||
- Valid FlexiPage XML with correct structure
|
||||
- Pre-configured regions and basic components
|
||||
@ -55,32 +65,12 @@ sf project deploy start --source-dir force-app/main/default/flexipages
|
||||
|
||||
**Deploy early, deploy often.** Start with the bootstrapped page, validate it works, then enhance.
|
||||
|
||||
### Step 3: Enhance with MCP Actions (Optional)
|
||||
|
||||
If you need to add more components or customize:
|
||||
|
||||
#### A. Discover Available Components
|
||||
```
|
||||
DISCOVER_UI_COMPONENTS
|
||||
```
|
||||
Returns: List of components available for this page type with descriptions.
|
||||
|
||||
#### B. Get Component Schemas
|
||||
```
|
||||
GET_UI_COMPONENT_SCHEMAS
|
||||
```
|
||||
Returns: JSON schemas showing required/optional properties, types, data sources. Also includes instructions specific to each component.
|
||||
|
||||
#### C. Get Data Source Values
|
||||
```
|
||||
GET_DATA_SOURCE_VALUES
|
||||
```
|
||||
Returns: Valid values for properties with data sources.
|
||||
|
||||
### Step 4: Update and Redeploy
|
||||
### Step 3: Update and Redeploy
|
||||
|
||||
Modify the generated XML, adding components discovered via MCP. Deploy incrementally.
|
||||
|
||||
**Note:** Warn users to use caution with updates beyond this step when using this command.
|
||||
|
||||
---
|
||||
|
||||
## Critical XML Rules
|
||||
@ -118,7 +108,7 @@ Modify the generated XML, adding components discovered via MCP. Deploy increment
|
||||
<!-- Correct -->
|
||||
<fieldItem>Record.Name</fieldItem>
|
||||
|
||||
<!-- Wrong -->
|
||||
<!-- Wrong -->
|
||||
<fieldItem>Account.Name</fieldItem>
|
||||
```
|
||||
|
||||
@ -143,14 +133,14 @@ Modify the generated XML, adding components discovered via MCP. Deploy increment
|
||||
Every fieldInstance requires:
|
||||
```xml
|
||||
<itemInstances>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value> <!-- none|readonly|required -->
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.FieldName__c</fieldItem>
|
||||
<identifier>RecordFieldName_cField</identifier>
|
||||
</fieldInstance>
|
||||
<fieldInstance>
|
||||
<fieldInstanceProperties>
|
||||
<name>uiBehavior</name>
|
||||
<value>none</value> <!-- none|readonly|required -->
|
||||
</fieldInstanceProperties>
|
||||
<fieldItem>Record.FieldName__c</fieldItem>
|
||||
<identifier>RecordFieldName_cField</identifier>
|
||||
</fieldInstance>
|
||||
</itemInstances>
|
||||
```
|
||||
|
||||
@ -161,38 +151,6 @@ Every fieldInstance requires:
|
||||
|
||||
---
|
||||
|
||||
## Using MCP Actions
|
||||
|
||||
### When to Use Each Action
|
||||
|
||||
#### DISCOVER_UI_COMPONENTS
|
||||
**When:** You want to see what components are available for your page type.
|
||||
|
||||
**Returns:** Component list with names, namespaces, descriptions.
|
||||
|
||||
**Use for:** Finding components to add to your bootstrapped page.
|
||||
|
||||
#### GET_UI_COMPONENT_SCHEMAS
|
||||
**When:** You know which components you want but need to understand their properties. If you have issues configuring a component and need more detailed instructions or knowledge.
|
||||
|
||||
**Returns:** JSON schemas with:
|
||||
- Required vs optional properties
|
||||
- Property types (string, boolean, array, etc.)
|
||||
- Data source references
|
||||
- Descriptions
|
||||
- Additional component-specific instructions or knowledge, often useful for more complex components.
|
||||
|
||||
**Use for:** Understanding how to configure components before adding to XML.
|
||||
|
||||
#### GET_DATA_SOURCE_VALUES
|
||||
**When:** A component property references a data source and you need valid values.
|
||||
|
||||
**Returns:** Valid values (e.g., "1", "2", "3" for column count).
|
||||
|
||||
**Use for:** Ensuring property values match allowed options.
|
||||
|
||||
---
|
||||
|
||||
## Common Deployment Errors
|
||||
|
||||
### "Invalid field reference"
|
||||
@ -207,10 +165,6 @@ Every fieldInstance requires:
|
||||
**Cause:** No uiBehavior specified
|
||||
**Fix:** Add `fieldInstanceProperties` with `uiBehavior`
|
||||
|
||||
### "Invalid component property"
|
||||
**Cause:** Wrong property name or format
|
||||
**Fix:** Use `GET_UI_COMPONENT_SCHEMAS` to see exact property names and types, or to find additional documentation.
|
||||
|
||||
### "Unused Facet"
|
||||
**Cause:** Facet defined but not referenced by any component
|
||||
**Fix:** Remove Facet or reference it in a component property
|
||||
@ -229,36 +183,6 @@ Every fieldInstance requires:
|
||||
|
||||
---
|
||||
|
||||
## Component-Specific Tips
|
||||
|
||||
### dynamicHighlights (RecordPage Header)
|
||||
|
||||
**Location:** Must be in `header` region.
|
||||
**Explicit Fields** (via CLI): Use the most important fields to show a summary of the record. The single primary field is used to identify the record, like a name. The secondary fields (max 12, recommended 6) are used as a summary of the record.
|
||||
```bash
|
||||
--primary-field Name
|
||||
--secondary-fields Phone,Industry,AnnualRevenue
|
||||
```
|
||||
CLI generates Facets with field references automatically.
|
||||
|
||||
### fieldSection
|
||||
|
||||
**Use for:** Displaying fields in columns.
|
||||
|
||||
**Structure:** Three-level nesting:
|
||||
1. Template Region (Region type)
|
||||
2. Column Facets (Facet type)
|
||||
3. Field Facets (Facet type)
|
||||
|
||||
**Referenced in component property:**
|
||||
```xml
|
||||
<componentInstanceProperties>
|
||||
<name>columns</name>
|
||||
<value>Facet-{uuid}</value>
|
||||
</componentInstanceProperties>
|
||||
```
|
||||
---
|
||||
|
||||
## Incremental Development Pattern
|
||||
|
||||
**Philosophy:** Deploy small, working increments. Don't build entire complex page at once.
|
||||
@ -290,14 +214,10 @@ When user provides an existing FlexiPage file path:
|
||||
- Existing component identifiers
|
||||
- Available regions (parse from file, don't assume names)
|
||||
- Existing facets
|
||||
3. **Use MCP actions** for discovery:
|
||||
- DISCOVER_UI_COMPONENTS (find available components)
|
||||
- GET_UI_COMPONENT_SCHEMAS (understand properties and get additional documentation)
|
||||
- GET_DATA_SOURCE_VALUES (validate data source values)
|
||||
4. **Generate component XML** (apply all rules from "Critical XML Rules" section)
|
||||
5. **Insert** into appropriate region
|
||||
6. **Write** modified XML back to file
|
||||
7. **Deploy**: `sf project deploy start --source-dir force-app/...`
|
||||
3. **Generate component XML** (apply all rules from "Critical XML Rules" section)
|
||||
4. **Insert** into appropriate region
|
||||
5. **Write** modified XML back to file
|
||||
6. **Deploy**: `sf project deploy start --source-dir force-app/...`
|
||||
|
||||
---
|
||||
|
||||
@ -347,13 +267,13 @@ When user provides an existing FlexiPage file path:
|
||||
**Insertion pattern**:
|
||||
```xml
|
||||
<flexiPageRegions>
|
||||
<name>main</name> <!-- or whatever region name exists -->
|
||||
<type>Region</type>
|
||||
<itemInstances><!-- Existing component 1 --></itemInstances>
|
||||
<itemInstances><!-- Existing component 2 --></itemInstances>
|
||||
<itemInstances>
|
||||
<!-- INSERT NEW COMPONENT HERE -->
|
||||
</itemInstances>
|
||||
<name>main</name> <!-- or whatever region name exists -->
|
||||
<type>Region</type>
|
||||
<itemInstances><!-- Existing component 1 --></itemInstances>
|
||||
<itemInstances><!-- Existing component 2 --></itemInstances>
|
||||
<itemInstances>
|
||||
<!-- INSERT NEW COMPONENT HERE -->
|
||||
</itemInstances>
|
||||
</flexiPageRegions>
|
||||
```
|
||||
|
||||
@ -367,52 +287,106 @@ Components like tabs, accordions, field sections require facets.
|
||||
```xml
|
||||
<!-- 1. Component in region -->
|
||||
<flexiPageRegions>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentName>flexipage:tabset2</componentName>
|
||||
<identifier>tabs_main_1</identifier>
|
||||
<componentInstanceProperties>
|
||||
<name>tabs</name>
|
||||
<value>tab1_content</value>
|
||||
<value>tab2_content</value>
|
||||
</componentInstanceProperties>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<name>main</name>
|
||||
<type>Region</type>
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentName>flexipage:tabset2</componentName>
|
||||
<identifier>tabs_main_1</identifier>
|
||||
<componentInstanceProperties>
|
||||
<name>tabs</name>
|
||||
<value>tab1_content</value>
|
||||
<value>tab2_content</value>
|
||||
</componentInstanceProperties>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
<name>main</name>
|
||||
<type>Region</type>
|
||||
</flexiPageRegions>
|
||||
|
||||
<!-- 2. Facets (siblings of region, NOT nested inside) -->
|
||||
<!-- 2. Facets (siblings of region, NOT nested inside) -->
|
||||
<flexiPageRegions>
|
||||
<itemInstances><!-- Tab 1 content --></itemInstances>
|
||||
<name>tab1_content</name>
|
||||
<type>Facet</type>
|
||||
<itemInstances><!-- Tab 1 content --></itemInstances>
|
||||
<name>tab1_content</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
|
||||
<flexiPageRegions>
|
||||
<itemInstances><!-- Tab 2 content --></itemInstances>
|
||||
<name>tab2_content</name>
|
||||
<type>Facet</type>
|
||||
<itemInstances><!-- Tab 2 content --></itemInstances>
|
||||
<name>tab2_content</name>
|
||||
<type>Facet</type>
|
||||
</flexiPageRegions>
|
||||
```
|
||||
|
||||
**Critical**: Facet regions are siblings of template regions at the same level, not nested inside them.
|
||||
---
|
||||
## Component-Specific Tips
|
||||
### dynamicHighlights (RecordPage Header)
|
||||
**Location:** Must be in `header` region.
|
||||
**Explicit Fields** (via CLI): Use the most important fields to show a summary of the record. The single primary field is used to identify the record, like a name. The secondary fields (max 12, recommended 6) are used as a summary of the record.
|
||||
```bash
|
||||
--primary-field Name
|
||||
--secondary-fields Phone,Industry,AnnualRevenue
|
||||
```
|
||||
CLI generates Facets with field references automatically.
|
||||
### fieldSection
|
||||
**Use for:** Displaying fields in columns.
|
||||
**Structure:** Three-level nesting:
|
||||
1. Template Region (Region type)
|
||||
2. Column Facets (Facet type)
|
||||
3. Field Facets (Facet type)
|
||||
**Referenced in component property:**
|
||||
```xml
|
||||
<componentInstanceProperties>
|
||||
<name>columns</name>
|
||||
<value>Facet-{uuid}</value>
|
||||
</componentInstanceProperties>
|
||||
```
|
||||
|
||||
### rich Text component
|
||||
|
||||
Component name: flexipage:richText
|
||||
|
||||
Use for: Displaying HTML-formatted rich text content with support for text formatting, headings, lists, tables, images, links, forms, and multimedia elements. Preserves styling and layout. Escape all special characters in the default text.
|
||||
|
||||
Location: Can be used in any region on any page type (Home, Record, App, Community pages).
|
||||
|
||||
|
||||
CLI generates the component directly without nested structures.
|
||||
|
||||
User: "Add a rich text component to force-app/.../Account_Record_Page.flexipage-meta.xml"
|
||||
|
||||
Structure: Single-level component (no facets):
|
||||
1. Component instance (flexipage:richText) with direct properties
|
||||
|
||||
XML Structure Example:
|
||||
```xml
|
||||
<itemInstances>
|
||||
<componentInstance>
|
||||
<componentInstanceProperties>
|
||||
<name>decorate</name>
|
||||
<value>true</value>
|
||||
</componentInstanceProperties>
|
||||
<componentName>flexipage:richText</componentName>
|
||||
<identifier>flexipage_richText</identifier>
|
||||
</componentInstance>
|
||||
</itemInstances>
|
||||
```
|
||||
|
||||
Identifier Pattern: flexipage_richText or flexipage_richText_{sequence}
|
||||
|
||||
---
|
||||
|
||||
## Required Metadata Structure
|
||||
|
||||
```xml
|
||||
<FlexiPage xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<flexiPageRegions>
|
||||
<!-- Regions and components here -->
|
||||
</flexiPageRegions>
|
||||
<masterLabel>Page Label</masterLabel>
|
||||
<template>
|
||||
<name>flexipage:recordHomeTemplateDesktop</name>
|
||||
</template>
|
||||
<type>RecordPage</type>
|
||||
<sobjectType>Object__c</sobjectType> <!-- RecordPage only -->
|
||||
<flexiPageRegions>
|
||||
<!-- Regions and components here -->
|
||||
</flexiPageRegions>
|
||||
<masterLabel>Page Label</masterLabel>
|
||||
<template>
|
||||
<name>flexipage:recordHomeTemplateDesktop</name>
|
||||
</template>
|
||||
<type>RecordPage</type>
|
||||
<sobjectType>Object__c</sobjectType> <!-- RecordPage only -->
|
||||
</FlexiPage>
|
||||
```
|
||||
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
---
|
||||
name: salesforce-flow
|
||||
description: Use this skill when users need to generate Salesforce Flows using the 3-step pipeline (fetchGroundedObjectMetadata → flowElementSelection → flowElementGeneration). Trigger when users mention creating flows, Screen Flows, Autolaunched Flows, Record-Triggered Flows, Scheduled Flows, Platform Event-Triggered Flows, flow metadata, flow automation, process automation, workflow automation, or generating flow XML. This skill guides through the mandatory 3-step MCP pipeline and ensures proper inflightMetadata formatting. Always use this skill for any flow generation or automation requests.
|
||||
description: Generate Salesforce Flows using the MCP tool execute_metadata_action. Use when the user asks to create, build, or generate a flow — including Screen, Autolaunched, Record-Triggered (before/after-save), Scheduled, or Platform Event-Triggered Flows. Also trigger for flow-like requests such as "when a record is created", "trigger daily at", "send an email when", "update the field when", "automate", "workflow", or "flow XML/metadata". This is the only skill for Salesforce Flow generation.
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Generate Salesforce Flow metadata by running the required 3-step MCP pipeline (fetchGroundedObjectMetadata → flowElementSelection → flowElementGeneration) and return the flow XML.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when you need to:
|
||||
@ -16,10 +20,10 @@ Use this skill when you need to:
|
||||
|
||||
# Flow Metadata Specification
|
||||
|
||||
## 📋 Overview
|
||||
## Overview
|
||||
Salesforce Flows are powerful automation tools that enable complex business process automation without code. Flows can collect and process data through interactive screens, execute logic and calculations, manipulate records, call external services, and trigger based on various events. Flow types include Screen Flows (user-guided), Autolaunched Flows (background processing), Record-Triggered Flows (database events), Scheduled Flows (time-based), and Platform Event-Triggered Flows (event-driven).
|
||||
|
||||
## 🎯 Purpose
|
||||
## Purpose
|
||||
- Automate complex business processes with declarative logic and branching
|
||||
- Guide users through multi-step data collection and decision workflows via Screen Flows
|
||||
- Perform CRUD operations on Salesforce records automatically
|
||||
@ -28,9 +32,16 @@ Salesforce Flows are powerful automation tools that enable complex business proc
|
||||
- Schedule recurring tasks and batch operations with Scheduled Flows
|
||||
- Create reusable, maintainable automation that admins can modify without code
|
||||
|
||||
## ⚙️ Flow Generation Pipeline
|
||||
## Flow Generation Pipeline
|
||||
|
||||
**MANDATORY: You MUST follow this exact 3-step pipeline. No exceptions. No shortcuts. No skipping steps. Do NOT manually create flow metadata XML or attempt to generate flow metadata outside of this pipeline. Do NOT attempt to use any other tool, API, or method to generate flow metadata. This pipeline is the ONLY supported way to generate flows. Any deviation will produce invalid or broken metadata.**
|
||||
|
||||
### MCP Connection Details
|
||||
|
||||
**All 3 pipeline steps MUST be called using this MCP tool:**
|
||||
- **MCP Tool Name:** `execute_metadata_action`
|
||||
- **The `action` parameter** selects which pipeline step to run: `"fetchGroundedObjectMetadata"`, `"flowElementSelection"`, or `"flowElementGeneration"`
|
||||
|
||||
**🚨 MANDATORY: You MUST follow this exact 3-step pipeline. No exceptions. No shortcuts. No skipping steps. Do NOT manually create flow metadata XML or attempt to generate flow metadata outside of this pipeline. Do NOT attempt to use any other tool, API, or method to generate flow metadata. This pipeline is the ONLY supported way to generate flows. Any deviation will produce invalid or broken metadata.**
|
||||
|
||||
Flow generation is a **strict 3-step pipeline**. ALL steps must be called in order. Every step is required. **There is no alternative approach — this is the only way to generate flow metadata:**
|
||||
|
||||
@ -67,18 +78,25 @@ Generates flow metadata element by element. This step is **mandatory** and must
|
||||
- **isComplete** (BOOLEAN): Indicates if the flow generation is complete. **You must check this value.**
|
||||
- **result** (STRING): Result of the flow element generation. Contains the final flow metadata **only when `isComplete` is `true`**.
|
||||
|
||||
**🚨 MANDATORY: Loop until complete.**
|
||||
**MANDATORY: Loop until complete. NEVER pause or ask the user to confirm continuation.**
|
||||
- A flow can have **any number of elements** (10, 15, or more). Each call generates one element at a time, so you may need **many** iterations. This is expected and normal.
|
||||
- Call `flowElementGeneration` with the `operationId` from Step 2 and `requestSource` (use `"A4V"` for XML output, empty string or other value for JSON).
|
||||
- Check the `isComplete` output after each call.
|
||||
- If `isComplete` is `false`, you **MUST** call `flowElementGeneration` again with the **same `operationId`** from Step 2.
|
||||
- **Do NOT stop** until `isComplete` is `true`.
|
||||
- Check the `isComplete` output and the `result` field after each call.
|
||||
- If `isComplete` is `false` **and no errors are returned**, you **MUST** call `flowElementGeneration` again with the **same `operationId`** from Step 2. **Do NOT ask the user if they want to continue. Do NOT pause. Do NOT summarize progress mid-loop. Just keep calling.**
|
||||
- **Do NOT stop** until `isComplete` is `true` **or** the invocable action returns errors. There is **no maximum** number of iterations — keep going regardless of how many calls it takes.
|
||||
- When `isComplete` is `true`, extract the flow metadata from the `result` field.
|
||||
- If errors are returned, stop the loop and surface the error to the user.
|
||||
|
||||
## 📦 inflightMetadata Format
|
||||
**STRICT CONSTRAINTS (CRITICAL):**
|
||||
- DO NOT modify the content, values, or child nodes inside any block.
|
||||
- DO NOT add new nodes, tags, attributes, or text (do not add missing labels, X/Y coordinates, etc.).
|
||||
- DO NOT remove any existing nodes.
|
||||
|
||||
## inflightMetadata Format
|
||||
**DATA TYPE: ARRAY (not string)**
|
||||
|
||||
**⚠️ STRICT NAMING CONVENTION - MUST FOLLOW EXACTLY:**
|
||||
| Property | Correct Name | ❌ Do NOT Use |
|
||||
**STRICT NAMING CONVENTION - MUST FOLLOW EXACTLY:**
|
||||
| Property | Correct Name | Do NOT Use |
|
||||
|----------|-------------|---------------|
|
||||
| Object API name | `apiName` | `objectApiName`, `name`, `objectName` |
|
||||
| Field API name | `apiName` | `fieldApiName`, `name`, `fieldName` |
|
||||
@ -148,7 +166,7 @@ When no custom objects needed:
|
||||
[]
|
||||
```
|
||||
|
||||
### ⚠️ MANDATORY Decision Logic for inflightMetadata (DATA TYPE: ARRAY)
|
||||
### MANDATORY Decision Logic for inflightMetadata (DATA TYPE: ARRAY)
|
||||
|
||||
1. **REQUIRED - First**: Scan the local sfdx project for custom objects and fields that are relevant to the user's flow request.
|
||||
2. **If relevant custom objects ARE found**: You MUST extract and pass them as an array of structured objects (see format above)
|
||||
@ -156,7 +174,7 @@ When no custom objects needed:
|
||||
4. **NEVER**: Pass text descriptions, instructions, or string representations in inflightMetadata
|
||||
5. **MANDATORY**: The data type MUST be ARRAY, not STRING
|
||||
|
||||
**Instructions for Cline when custom objects ARE relevant:**
|
||||
**Instructions for Vibes when custom objects ARE relevant:**
|
||||
- Extract the object metadata and map to JSON properties:
|
||||
- `apiName`: The object's API name (with `__c` suffix for custom objects)
|
||||
- `label`: The object's display label
|
||||
@ -170,28 +188,35 @@ When no custom objects needed:
|
||||
- Include only objects and fields that are relevant to the flow being generated
|
||||
|
||||
## 🎯 Mandatory Enhancement Rules
|
||||
- **userPrompt**: REQUIRED. Always use the exact user prompt without modification.
|
||||
- **userPrompt**: REQUIRED.
|
||||
- If the user requests a **single flow**: use the user's prompt as-is.
|
||||
- If the user requests **multiple flows**: you MUST **split** the request and write a **separate, focused `userPrompt` for each individual flow**. Each `userPrompt` must describe only ONE flow. Do NOT pass the entire multi-flow request as a single `userPrompt`. See the multiple flows section below for examples.
|
||||
- **inflightMetadata**: REQUIRED. Always use ARRAY data type.
|
||||
- MUST use `[]` (empty array) when no custom objects needed
|
||||
- MUST use structured array of objects when custom objects are relevant
|
||||
- NEVER use string `"[]"` - this is incorrect
|
||||
- NEVER use text descriptions - only structured object metadata
|
||||
|
||||
### 🚨 MANDATORY: Multiple Flows = Multiple Separate Pipelines
|
||||
### MANDATORY: Multiple Flows = Multiple Separate Pipelines
|
||||
|
||||
**❌ NEVER club multiple flow prompts into a single `userPrompt` field.**
|
||||
**FIRST: Before calling any pipeline step, check if the user's request contains multiple flows. If it does, you MUST split it into separate single-flow prompts. Each flow gets its own 3-step pipeline with its own `userPrompt` that describes ONLY that one flow.**
|
||||
|
||||
When the user requests multiple flows (e.g., for an app with several flows), each flow MUST be generated with a **separate 3-step pipeline** and a **separate payload**. This is mandatory and non-negotiable.
|
||||
**NEVER pass a multi-flow request as a single `userPrompt` field. NEVER club multiple flow descriptions into one `userPrompt`.**
|
||||
|
||||
**❌ WRONG - Multiple flows clubbed into one userPrompt:**
|
||||
When the user requests multiple flows (e.g., "Create flows for my app: 1) ... 2) ... 3) ..."), you MUST:
|
||||
1. **Split** the request into separate individual flow descriptions.
|
||||
2. **Run a separate 3-step pipeline for each flow**, using a `userPrompt` that describes ONLY that one flow.
|
||||
3. **Execute ALL pipelines SEQUENTIALLY** — one after another, NEVER in parallel. Do NOT stop after the first flow. Do NOT wait for the user to ask you to continue. Do NOT summarize and stop. Keep going until every requested flow has been fully generated.
|
||||
|
||||
**WRONG - Multiple flows clubbed into one userPrompt:**
|
||||
```json
|
||||
{
|
||||
"userPrompt": "Generate the following flows: 1) Screen Flow - Tenant Onboarding... 2) Autolaunched Flow - Generate Checklist... 3) Record-Triggered Flow - Sync Unit...",
|
||||
"userPrompt": "Create flows for the app: 1) Record-Triggered Flow on ResourceAllocation__c to update Resource__c. 2) Screen Flow to allocate resources. 3) Record-Triggered Flow on Supply__c to auto-flag Low_Stock__c.",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**✅ CORRECT - Separate call for EACH flow:**
|
||||
**CORRECT - Separate call for EACH flow:**
|
||||
|
||||
**Flow 1 - Step 1 (fetchGroundedObjectMetadata):**
|
||||
```json
|
||||
@ -221,11 +246,13 @@ Then call Step 2 and Step 3 for this flow.
|
||||
Then call Step 2 and Step 3 for this flow.
|
||||
|
||||
**Mandatory Rules:**
|
||||
- If there are N flows to generate, there MUST be N separate 3-step pipelines. No exceptions.
|
||||
- If there are N flows to generate, there MUST be N separate 3-step pipelines and ALL N pipelines MUST be executed. No exceptions. Do NOT stop after generating only one flow.
|
||||
- **You MUST fully complete the current flow's 3-step pipeline (including looping Step 3 until `isComplete` is `true` or errors are returned) BEFORE starting the next flow's pipeline.** Do NOT interleave or parallelize pipelines across flows. **Everything is SEQUENTIAL — NEVER parallel.**
|
||||
- After completing a flow's pipeline, **immediately start the next flow's pipeline**. Do NOT pause, summarize, or wait for user confirmation between flows.
|
||||
- For each flow, you MUST scan the local sfdx project to populate `inflightMetadata` with custom objects/fields **specific to that flow prompt**.
|
||||
- Each flow pipeline MUST have its own `inflightMetadata` containing only the objects/fields relevant to that particular flow.
|
||||
|
||||
## 🔧 Example Tool Calls
|
||||
## Example Tool Calls
|
||||
|
||||
**Example 1: Standard objects only (no custom objects)**
|
||||
|
||||
@ -253,7 +280,7 @@ Then call Step 2 and Step 3 for this flow.
|
||||
"requestSource": "A4V"
|
||||
}
|
||||
```
|
||||
Call repeatedly with the same `operationId` until `isComplete` is `true`. When `isComplete` is `true`, extract the flow metadata from the `result` field. Use `"requestSource": "A4V"` to get flow metadata in XML format.
|
||||
Call repeatedly with the same `operationId` until `isComplete` is `true` or errors are returned. A flow can have any number of elements, so expect multiple iterations. When `isComplete` is `true`, extract the flow metadata from the `result` field. Use `"requestSource": "A4V"` to get flow metadata in XML format.
|
||||
|
||||
**Example 2: With custom objects from local sfdx project**
|
||||
|
||||
@ -302,9 +329,9 @@ Call repeatedly with the same `operationId` until `isComplete` is `true`. When `
|
||||
"requestSource": "A4V"
|
||||
}
|
||||
```
|
||||
Call repeatedly with the same `operationId` until `isComplete` is `true`. When `isComplete` is `true`, extract the flow metadata from the `result` field. Use `"requestSource": "A4V"` to get flow metadata in XML format
|
||||
Call repeatedly with the same `operationId` until `isComplete` is `true` or errors are returned. A flow can have any number of elements, so expect multiple iterations. When `isComplete` is `true`, extract the flow metadata from the `result` field. Use `"requestSource": "A4V"` to get flow metadata in XML format.
|
||||
|
||||
## ✅ Mandatory Best Practices
|
||||
## Mandatory Best Practices
|
||||
- **ALWAYS** follow the 3-step pipeline: fetchGroundedObjectMetadata → flowElementSelection → flowElementGeneration. This is the ONLY way to generate flow metadata. There are no alternatives.
|
||||
- Do NOT manually create flow metadata XML, JSON, or any other format outside of this pipeline.
|
||||
- Do NOT attempt to "optimize" by skipping steps or combining steps. Each step is atomic and required.
|
||||
@ -312,20 +339,20 @@ Call repeatedly with the same `operationId` until `isComplete` is `true`. When `
|
||||
- **NEVER** try to generate flow metadata without calling all 3 steps.
|
||||
- **NEVER** deviate from this pipeline under any circumstance — even if you think you know the flow structure.
|
||||
- For single flow requests: you MUST use the user prompt as `userPrompt`.
|
||||
- For multiple flow requests: you MUST run a separate 3-step pipeline for each flow.
|
||||
- For multiple flow requests: you MUST run a separate 3-step pipeline for each flow **SEQUENTIALLY (one after another, NEVER in parallel)**, and you MUST execute ALL of them — do NOT stop after the first flow.
|
||||
- You MUST put flow requirements in `userPrompt`, NOT in `inflightMetadata`.
|
||||
- `inflightMetadata` is ONLY for custom object/field metadata from local project (see above). No exceptions.
|
||||
- Step 3 MUST be called in a loop with the same `operationId` from Step 2 until `isComplete` is `true`. Do NOT stop early.
|
||||
- Step 3 MUST be called in a loop with the same `operationId` from Step 2 until `isComplete` is `true` or errors are returned. A flow can have any number of elements — do NOT stop early, do NOT pause to ask the user if they want to continue, regardless of how many iterations it takes.
|
||||
- You MUST only extract the flow metadata from the `result` field when `isComplete` is `true`.
|
||||
|
||||
## 🚨 CRITICAL Verification Checklist (MUST VERIFY BEFORE AND AFTER EVERY FLOW GENERATION)
|
||||
## CRITICAL Verification Checklist (MUST VERIFY BEFORE AND AFTER EVERY FLOW GENERATION)
|
||||
|
||||
**Failure to follow this checklist exactly will result in broken or missing flow metadata.**
|
||||
|
||||
- [ ] **Pipeline**: ALL 3 steps are called in strict order (fetchGroundedObjectMetadata → flowElementSelection → flowElementGeneration). No step is skipped.
|
||||
- [ ] **No manual metadata**: Flow metadata is NOT manually created, modified, or generated outside of this pipeline by any means
|
||||
- [ ] **No deviation**: No alternative tools, APIs, or methods were used instead of or alongside this pipeline
|
||||
- [ ] **userPrompt** contains a single flow prompt (split if user requested multiple flows)
|
||||
- [ ] **userPrompt** contains a **single** flow prompt. If user requested multiple flows, the request was split and each pipeline received a separate `userPrompt` describing only one flow
|
||||
- [ ] **userPrompt** is passed consistently to both Step 1 and Step 2 (same value)
|
||||
- [ ] **inflightMetadata** is ARRAY data type (NOT string)
|
||||
- [ ] **inflightMetadata** is `[]` when no custom objects needed
|
||||
@ -335,5 +362,7 @@ Call repeatedly with the same `operationId` until `isComplete` is `true`. When `
|
||||
- [ ] **groundingMetadata** from Step 1 output is passed directly to Step 2 input (it is already a string — do NOT serialize it again)
|
||||
- [ ] **operationId** from Step 2 output is passed to Step 3 input
|
||||
- [ ] **requestSource** should be set to `"A4V"` always
|
||||
- [ ] **Step 3** is called in a loop with the same `operationId` from Step 2 until `isComplete` is `true`
|
||||
- [ ] **Step 3** is called in a loop with the same `operationId` from Step 2 until `isComplete` is `true` or errors are returned — **no pausing, no asking the user to continue, no matter how many iterations**
|
||||
- [ ] **Multi-flow**: Each flow's full pipeline is completed before starting the next flow's pipeline (no interleaving)
|
||||
- [ ] **result** field is used to extract the XML flow metadata only when `isComplete` is `true`
|
||||
- [ ] **No additions to XML**: NO elements, attributes, or properties were added that were not present in the original pipeline output. Nothing was inserted (no `<label>`, `<description>`, or any other node). The final XML must be identical to what the pipeline returned.
|
||||
|
||||
254
skills/salesforce-lightning-app-build/SKILL.md
Normal file
254
skills/salesforce-lightning-app-build/SKILL.md
Normal file
@ -0,0 +1,254 @@
|
||||
---
|
||||
name: salesforce-lightning-app-build
|
||||
description: Use this skill to build and orchestrate complete Salesforce Lightning Applications (LEX Apps), custom projects, or end-to-end business solutions from a natural language scenario. Triggers when a user requests a "custom app", a "business solution", or describes any scenario requiring multiple interconnected Salesforce components to be built together into a complete Lightning Experience (LEX) Application. Orchestrates the sequenced creation of Custom Objects, Relationships, Fields, Lightning Record Pages, Custom Tabs, Custom Applications, Permission Sets, and OPTIONALLY Flows and Validation Rules.
|
||||
metadata:
|
||||
category: orchestration
|
||||
related-skills: salesforce-custom-object, salesforce-custom-field, salesforce-custom-tab, salesforce-flexipage, salesforce-custom-application, salesforce-flow, salesforce-validation-rule, salesforce-list-view
|
||||
---
|
||||
|
||||
# Salesforce Lightning Application Build
|
||||
|
||||
## Overview
|
||||
|
||||
Build complete Lightning Experience applications from natural language by orchestrating multiple metadata types in proper dependency order. This skill acts as a "conductor" that invokes specialized metadata skills when available, or generates metadata directly when no skill exists.
|
||||
|
||||
## When to Use This Skill
|
||||
**Use when:**
|
||||
- User requests a "complete app", "Lightning app", or "end-to-end solution"
|
||||
- User says "build an app", "create an application", "build a [type] app" (project management, tracking, etc.)
|
||||
- Request involves 3+ metadata types working together (objects + fields + pages + security)
|
||||
- User describes multiple custom objects with relationships between them
|
||||
- User mentions custom objects AND Lightning Record Pages in the same request
|
||||
- User mentions custom objects AND permission sets/security in the same request
|
||||
- Request includes phrases like "allows users to manage/track", "management system", "tracking app"
|
||||
- Need to ensure proper sequencing (Objects → Fields → UI → Security)
|
||||
|
||||
**Examples that should trigger this skill:**
|
||||
- "Build a project management app with Tasks, Resources, and Supplies objects"
|
||||
- "Create an app to track vehicles with Lightning pages and permission sets"
|
||||
- "I need a Space Station management system with multiple objects and relationships"
|
||||
- "Build an employee onboarding app with custom Lightning Record Pages"
|
||||
|
||||
**Do NOT use when:**
|
||||
- Creating a single metadata component (use specific metadata skill instead)
|
||||
- Troubleshooting or debugging existing metadata
|
||||
- Building Salesforce Classic apps (not Lightning Experience)
|
||||
- User asks for just one object, or just one page, or just one permission set (without others)
|
||||
---
|
||||
|
||||
## Metadata Type Registry
|
||||
|
||||
This table shows which metadata types are commonly needed for LEX apps and their skill availability.
|
||||
|
||||
| Metadata Type | Skill Available? | Skill Name | Usage Rule |
|
||||
|---------------|------------------|------------|------------|
|
||||
| **Custom Object** | ✅ YES | `salesforce-custom-object` | MUST use skill |
|
||||
| **Custom Field** | ✅ YES | `salesforce-custom-field` | MUST use skill |
|
||||
| **Custom Tab** | ✅ YES | `salesforce-custom-tab` | MUST use skill |
|
||||
| **FlexiPage** | ✅ YES | `salesforce-flexipage` | MUST use skill |
|
||||
| **Custom Application** | ✅ YES | `salesforce-custom-application` | MUST use skill |
|
||||
| **List View** | ✅ YES | `salesforce-list-view` | MUST use skill |
|
||||
| **Validation Rule** | ✅ YES | `salesforce-validation-rule` | MUST use skill (if requested) |
|
||||
| **Flow** | ✅ YES | `salesforce-flow` | MUST use skill (if requested) |
|
||||
| **Permission Set** | ❌ NO | - | Generate directly using Metadata API knowledge |
|
||||
|
||||
### Skill Usage Rules
|
||||
|
||||
**CRITICAL RULE**: When a skill exists for a metadata type (✅ YES in table above), you **MUST** invoke that skill. Do NOT generate the metadata directly.
|
||||
|
||||
**FALLBACK RULE**: When NO skill exists for a metadata type (❌ NO in table above), you **MAY** generate the metadata directly using your knowledge of Salesforce Metadata API and best practices.
|
||||
|
||||
**RATIONALE**: Specialized skills contain validated patterns, error handling, and field-specific knowledge that prevent deployment failures.
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph & Build Order
|
||||
|
||||
### Phase 1: Data Model (Foundation)
|
||||
```
|
||||
Custom Objects (no dependencies)
|
||||
↓
|
||||
Custom Fields (depends on: Objects exist)
|
||||
↓
|
||||
Relationships (depends on: Both parent and child objects + fields exist)
|
||||
```
|
||||
|
||||
**Skills to invoke in order:**
|
||||
1. `salesforce-custom-object` for each object
|
||||
2. `salesforce-custom-field` for each field (including Master-Detail, Lookup, Roll-up Summary)
|
||||
|
||||
### Phase 2: Business Logic (Optional - only if requested)
|
||||
```
|
||||
Validation Rules (depends on: Fields exist)
|
||||
↓
|
||||
Flows (depends on: Objects, Fields exist)
|
||||
```
|
||||
|
||||
**Skills to invoke (only if user requested):**
|
||||
1. `salesforce-validation-rule` if validation requirements mentioned
|
||||
2. `salesforce-flow` if automation/workflow requirements mentioned
|
||||
|
||||
### Phase 3: User Interface
|
||||
```
|
||||
List Views (depends on: Objects, Fields exist)
|
||||
↓
|
||||
Custom Tabs (depends on: Objects exist)
|
||||
↓
|
||||
FlexiPages (depends on: Objects, Tabs exist)
|
||||
```
|
||||
|
||||
**Skills to invoke in order:**
|
||||
1. `salesforce-list-view` for filtered record views (if requested)
|
||||
2. `salesforce-custom-tab` for each object tab
|
||||
3. `salesforce-flexipage` for record/home/app pages
|
||||
|
||||
### Phase 4: Application Assembly
|
||||
```
|
||||
Custom Application (depends on: Tabs exist)
|
||||
```
|
||||
|
||||
**Skills to invoke:**
|
||||
1. `salesforce-custom-application` to create the Lightning App container
|
||||
|
||||
### Phase 5: Security & Access
|
||||
```
|
||||
Permission Sets (depends on: Objects, Fields, Tabs, App exist)
|
||||
```
|
||||
|
||||
**Fallback generation (no skill available):**
|
||||
1. Generate Permission Set XML directly with access to:
|
||||
- Objects (Read, Create, Edit, Delete)
|
||||
- Fields (Read, Edit)
|
||||
- Tabs (Visible)
|
||||
- Custom Application (Visible)
|
||||
|
||||
---
|
||||
|
||||
## Execution Workflow
|
||||
|
||||
### STEP 1: Requirements Analysis & Planning
|
||||
|
||||
**Actions:**
|
||||
1. Parse user's natural language request
|
||||
2. Extract business entities (become Custom Objects)
|
||||
3. Extract attributes/properties (become Custom Fields)
|
||||
4. Identify relationships (Master-Detail, Lookup)
|
||||
5. Detect validation requirements (become Validation Rules)
|
||||
6. Detect automation requirements (become Flows)
|
||||
7. Identify user personas (inform Permission Sets)
|
||||
|
||||
**Output: Build Plan**
|
||||
|
||||
Generate a structured plan listing:
|
||||
|
||||
```
|
||||
📋 Lightning App Build Plan: [App Name]
|
||||
|
||||
DATA MODEL:
|
||||
- Custom Objects: [list with object names]
|
||||
- Custom Fields: [list grouped by object]
|
||||
- Relationships: [list M-D and Lookup relationships]
|
||||
|
||||
BUSINESS LOGIC (if applicable):
|
||||
- Validation Rules: [list with object and rule name]
|
||||
- Flows: [list with flow name and type]
|
||||
|
||||
USER INTERFACE:
|
||||
- List Views: [list with object and view name]
|
||||
- Custom Tabs: [list with object]
|
||||
- FlexiPages: [list with page name and type]
|
||||
- Custom Application: [app name]
|
||||
|
||||
SECURITY:
|
||||
- Permission Sets: [list with purpose]
|
||||
|
||||
METADATA SKILLS TO INVOKE:
|
||||
- salesforce-custom-object (x N)
|
||||
- salesforce-custom-field (x N)
|
||||
- salesforce-validation-rule (x N) - if validation requirements identified
|
||||
- salesforce-flow (x N) - if automation requirements identified
|
||||
- salesforce-custom-tab (x N)
|
||||
- salesforce-flexipage (x N)
|
||||
- salesforce-custom-application (x 1)
|
||||
- [fallback] Permission Set XML generation (x N)
|
||||
|
||||
DEPENDENCY ORDER:
|
||||
1. Phase 1: Data Model (Objects → Fields)
|
||||
2. Phase 2: Business Logic (Validation Rules → Flows)
|
||||
3. Phase 3: User Interface (List Views → Tabs → Pages)
|
||||
4. Phase 4: App Assembly (Application)
|
||||
5. Phase 5: Security (Permission Sets)
|
||||
```
|
||||
|
||||
### STEP 2: Skill Invocation Sequence
|
||||
|
||||
Execute in strict dependency order. For each metadata component:
|
||||
|
||||
1. **Check Metadata Type Registry**: Does a skill exist?
|
||||
2. **If YES (✅)**: Invoke the specialized skill with required parameters
|
||||
3. **If NO (❌)**: Generate metadata directly using Metadata API knowledge
|
||||
4. **Handle Errors**: If skill invocation fails, log error and continue (don't block entire app)
|
||||
|
||||
**Invocation Pattern Example:**
|
||||
|
||||
- For Custom Object → Invoke `salesforce-custom-object`
|
||||
- For Custom Field → Invoke `salesforce-custom-field`
|
||||
- For Validation Rule → Invoke `salesforce-validation-rule`
|
||||
- For Flow → Invoke `salesforce-flow`
|
||||
- For Custom Tab → Invoke `salesforce-custom-tab`
|
||||
- For FlexiPage → Invoke `salesforce-flexipage`
|
||||
- For Custom Application → Invoke `salesforce-custom-application`
|
||||
- For Permission Sets (no skill) → Generate XML directly
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Critical Errors (Stop Execution)
|
||||
|
||||
Stop and ask user for clarification if:
|
||||
- User request is too vague to extract any objects or fields
|
||||
- Conflicting requirements detected (e.g., "make it private" + "everyone should see it")
|
||||
- Invalid Salesforce naming detected (reserved words like `Order`, `Group`)
|
||||
|
||||
### Non-Critical Errors (Continue with Warning)
|
||||
|
||||
Log warning and continue if:
|
||||
- Optional component fails (e.g., List View generation fails)
|
||||
- Skill invocation fails for non-critical metadata
|
||||
- Validation Rule or Flow has minor issues
|
||||
|
||||
**Warning Pattern:**
|
||||
```
|
||||
⚠️ Warning: [Component Type] generation encountered issue
|
||||
Component: [Name]
|
||||
Issue: [Description]
|
||||
Impact: [What won't work]
|
||||
Recommendation: [How to fix manually]
|
||||
|
||||
Continuing with remaining components...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Follow Dependency Order
|
||||
Never invoke skills out of sequence. Fields need objects, pages need tabs, apps need tabs.
|
||||
|
||||
### 2. Use Skills When Available
|
||||
Don't reinvent the wheel. Specialized skills have field-specific validation that prevents deployment errors.
|
||||
|
||||
### 3. Generate Thoughtful Defaults
|
||||
When user doesn't specify details:
|
||||
- Use Text name fields for human entities
|
||||
- Use AutoNumber for transactions
|
||||
- Enable Search and Reports for user-facing objects
|
||||
- Set sharingModel based on relationships
|
||||
|
||||
### 5. Validate Before Building
|
||||
Check for:
|
||||
- Reserved words in API names
|
||||
- Relationship limits (max 2 M-D per object)
|
||||
- Name length limits
|
||||
- Duplicate names
|
||||
@ -67,3 +67,6 @@ Validation Rules are declarative metadata components used to enforce data qualit
|
||||
2. Interpretation of "Update" Instructions. When receiving instructions to modify a formula, distinguish between a replacement and an addition:
|
||||
- "Update the formula to [Action]": Completely replace the existing formula logic with the new requirement.
|
||||
- "Update the formula to also [Action]": Keep the existing logic and append the new requirement (usually by wrapping the logic in an AND() or OR() function).
|
||||
|
||||
3. File Format Requirement
|
||||
- Validation rule files MUST always use the `.validationRule-meta.xml` extension.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user