This commit is contained in:
svc-idee-bot 2026-06-03 00:11:57 +05:30 committed by GitHub
commit 086f21b6c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
179 changed files with 34486 additions and 5876 deletions

626
package-lock.json generated
View File

@ -1,16 +1,16 @@
{
"name": "@salesforce/afv-skills",
"version": "1.6.8",
"version": "1.13.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@salesforce/afv-skills",
"version": "1.6.8",
"version": "1.13.0",
"license": "CC-BY-NC-4.0",
"devDependencies": {
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^1.120.6",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^1.120.6",
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^10.2.2",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^10.2.2",
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.117.1",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.117.1",
"@types/js-yaml": "^4.0.9",
@ -18,6 +18,115 @@
"tsx": "^4.21.0"
}
},
"node_modules/@conduit-client/command-base": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/command-base/-/command-base-3.19.6.tgz",
"integrity": "sha512-zbjMSv4d0SEPR63/HTa4lFoJtMHUMQ810GprFFR9U3OcZZ4PCyUQeyM8DIja/IsRgH7HDDEL7DnhF5mpT5UxDA==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@conduit-client/command-base/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/command-cache-control": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/command-cache-control/-/command-cache-control-3.19.6.tgz",
"integrity": "sha512-ReZdJa5S65sGqXWocq6PkUEodon9xaBHzdpPHrdhFADotjhJY2wYQ9/050tnQ7TVg31IkeUZNNrj4WPEdYwV7w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/command-base": "3.19.6",
"@conduit-client/service-cache-control": "3.19.6",
"@conduit-client/service-instrumentation": "3.19.6",
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@conduit-client/command-cache-control/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/command-http-cache-control": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/command-http-cache-control/-/command-http-cache-control-3.19.6.tgz",
"integrity": "sha512-41bjECK4JByGKpj6chojO25i1sgOnvQoklvzgW6L9BuLE5d3uzBw6JmY5srAOiaP1t0ZYHeHm+lLnuZ7R3BjRA==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/command-base": "3.19.6",
"@conduit-client/command-cache-control": "3.19.6",
"@conduit-client/service-aura-network": "3.19.6",
"@conduit-client/service-cache-control": "3.19.6",
"@conduit-client/service-fetch-network": "3.19.6",
"@conduit-client/service-pubsub": "3.19.6",
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@conduit-client/command-http-cache-control/node_modules/@conduit-client/jwt-manager": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/jwt-manager/-/jwt-manager-3.19.6.tgz",
"integrity": "sha512-tpkg0DtJxTdW/zac+nm8wCpSw2wavYtOfVDHfmAAtH/FF39PWjcam7ldyru1G95NR2JbvAxbLQZefC+Dt0n5kg==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"jwt-decode": "~3.1.2"
}
},
"node_modules/@conduit-client/command-http-cache-control/node_modules/@conduit-client/service-fetch-network": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-fetch-network/-/service-fetch-network-3.19.6.tgz",
"integrity": "sha512-yOsiRyOrM03tmX92oVbThJs7jZfjiVBQZiGkV+I+980TrJGnAwvaZlQVeG4Ncb285HtpBzkUnuc05Eol/tlCgw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/jwt-manager": "3.19.6",
"@conduit-client/service-retry": "3.19.6",
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@conduit-client/command-http-cache-control/node_modules/@conduit-client/service-retry": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-retry/-/service-retry-3.19.6.tgz",
"integrity": "sha512-Zb1V2un+X3iFFN/eiY+V+G9cfvRqK2ppHjKWVF+SXRY52fOKVbEvbRA/n/YM/xemfA4PiMcmNzOrhvHwYNImaw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@conduit-client/command-http-cache-control/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/jsonschema-validate": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/jsonschema-validate/-/jsonschema-validate-3.19.6.tgz",
"integrity": "sha512-XVEzbVc568gW3gu5YZh9CAVMZNjQ5hdTkR9M8ApsXX7GYhxy41Vl9F6Kkig9SFr6K0xDxAmfldvHSxkqHFebXw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@conduit-client/jsonschema-validate/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/jwt-manager": {
"version": "3.17.0",
"resolved": "https://registry.npmjs.org/@conduit-client/jwt-manager/-/jwt-manager-3.17.0.tgz",
@ -28,6 +137,98 @@
"jwt-decode": "~3.1.2"
}
},
"node_modules/@conduit-client/onestore-graphql-parser": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/onestore-graphql-parser/-/onestore-graphql-parser-3.19.6.tgz",
"integrity": "sha512-zz02oNZjrxWQDAt7jN3yKurCdQTp2Jz3Cp9fhkZhJ/NNUqElcQUPK/3ruUX5MhwpjbbVU4KXvNNqwjg0We7UoA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@conduit-client/jsonschema-validate": "3.19.6",
"@conduit-client/utils": "3.19.6",
"graphql": "16.11.0"
}
},
"node_modules/@conduit-client/onestore-graphql-parser/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/service-aura-network": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-aura-network/-/service-aura-network-3.19.6.tgz",
"integrity": "sha512-H8+Uq0A8e9QYtYrvVfkoha7ozQP8aCTqn9vYKt1WzehkzRI+webIZjNeCrJtFsXEW+s2Shwy2P3C3GY6tF/rUg==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/service-instrumentation": "3.19.6",
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@conduit-client/service-aura-network/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/service-cache": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-cache/-/service-cache-3.19.6.tgz",
"integrity": "sha512-WBQJgOXLZsAyWBnaOnliwgs+tx9JJsCGtLV31oqXrLbjxONk1NYNFbbpW70a8wLBJ0ZKFd4s/+nNicKwiGvRWw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/service-instrumentation": "3.19.6",
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@conduit-client/service-cache-control": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-cache-control/-/service-cache-control-3.19.6.tgz",
"integrity": "sha512-pQ2ipf9FZs2vPjG3d837kpGWQGK0fmj73spn6VUWy0jiewlZZ6L/uzZzV9smMv65yAHVWnBO6ZMi/jVp+y0YsQ==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/service-cache": "3.19.6",
"@conduit-client/service-cache-inclusion-policy": "3.19.6",
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@conduit-client/service-cache-control/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/service-cache-inclusion-policy": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-cache-inclusion-policy/-/service-cache-inclusion-policy-3.19.6.tgz",
"integrity": "sha512-q7kJguFTNdb5/RKykBXgDttWBwT7+tcS4zWwEaHXmDTlb4iTOlb5JfpSgFoEkx4mY1+QsiM179g4sP38IN+0jQ==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/service-cache": "3.19.6",
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@conduit-client/service-cache-inclusion-policy/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/service-cache/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/service-fetch-network": {
"version": "3.17.0",
"resolved": "https://registry.npmjs.org/@conduit-client/service-fetch-network/-/service-fetch-network-3.17.0.tgz",
@ -40,6 +241,152 @@
"@conduit-client/utils": "3.17.0"
}
},
"node_modules/@conduit-client/service-instrumentation": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-instrumentation/-/service-instrumentation-3.19.6.tgz",
"integrity": "sha512-o+tR7hbwc2SNMYvKMW4IT0Z2JkO2BpCXiWchz/5N7GiKW1Xuhl/8jLY7mzmm96OVZGktPp4p4FHGlUiTdlqiSQ==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/service-instrumentation-noop": "3.19.6",
"@conduit-client/service-instrumentation-o11y": "3.19.6",
"@conduit-client/service-instrumentation-types": "3.19.6",
"@conduit-client/utils": "3.19.6",
"@opentelemetry/api": "1.7.0"
}
},
"node_modules/@conduit-client/service-instrumentation-noop": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-instrumentation-noop/-/service-instrumentation-noop-3.19.6.tgz",
"integrity": "sha512-4OuCYyNJnfDqIWXlTTxfhEkMbanIruZrMr8Rzx+MBPuYSqMSJmzCRD1Imf9nh84RedmdC4OlOxdgF6FVgYRcfQ==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/service-instrumentation-types": "3.19.6",
"@conduit-client/utils": "3.19.6",
"@opentelemetry/api": "1.7.0"
}
},
"node_modules/@conduit-client/service-instrumentation-noop/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/service-instrumentation-o11y": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-instrumentation-o11y/-/service-instrumentation-o11y-3.19.6.tgz",
"integrity": "sha512-NQU9jeob5ARas8HpcyMADvhEyQk76YaBvWV6qGdQi22vPK636FCZDucysL++Q2iCSrszX5SS++0q4HEQo1mFog==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/service-instrumentation-types": "3.19.6",
"@conduit-client/utils": "3.19.6",
"@opentelemetry/api": "1.7.0",
"o11y": "252.7.0"
}
},
"node_modules/@conduit-client/service-instrumentation-o11y/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/service-instrumentation-o11y/node_modules/o11y": {
"version": "252.7.0",
"resolved": "https://registry.npmjs.org/o11y/-/o11y-252.7.0.tgz",
"integrity": "sha512-/P79vBumoxplQ4aqjW8GoVEMk7Uj50HOmZQAyNtu/FE8FuPXSh9rnkguxUDfN5l1oWI8Eyktrnu3XRs56OkQVQ==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"o11y_schema": "252.11.0",
"protobufjs": "7.2.4",
"web-vitals": "3.5.2"
}
},
"node_modules/@conduit-client/service-instrumentation-o11y/node_modules/o11y_schema": {
"version": "252.11.0",
"resolved": "https://registry.npmjs.org/o11y_schema/-/o11y_schema-252.11.0.tgz",
"integrity": "sha512-gdzht1/LRbnJZZGtR2M6ACopNLiwuUs0yP8OGnDbpCTJITwDS9S5wb7qbxUMY2kyhXkGR3oD8RY0C1S6+/1rRQ==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@conduit-client/service-instrumentation-o11y/node_modules/protobufjs": {
"version": "7.2.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz",
"integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==",
"dev": true,
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/@conduit-client/service-instrumentation-o11y/node_modules/web-vitals": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.2.tgz",
"integrity": "sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@conduit-client/service-instrumentation-types": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-instrumentation-types/-/service-instrumentation-types-3.19.6.tgz",
"integrity": "sha512-BZ6BsYa2StM2+bGWnv/cB4SXaS9fCTQX1bL9z0UqIZbPmBYhtr640/SirAl8z2pqnl0Qd5OZnYAlpyLSqJoKOg==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/utils": "3.19.6",
"@opentelemetry/api": "1.7.0"
}
},
"node_modules/@conduit-client/service-instrumentation-types/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/service-instrumentation/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/service-pubsub": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-pubsub/-/service-pubsub-3.19.6.tgz",
"integrity": "sha512-HgWWiXMX43jorZNtqEHx/8cxRImcga6ycjCS6NRPnYStsndIBZIR53GnKcXbPvvaO/v5BhA3WWiWwSYe38OF2A==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@conduit-client/service-pubsub/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@conduit-client/service-retry": {
"version": "3.17.0",
"resolved": "https://registry.npmjs.org/@conduit-client/service-retry/-/service-retry-3.17.0.tgz",
@ -641,6 +988,16 @@
"tslib": "2"
}
},
"node_modules/@opentelemetry/api": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.7.0.tgz",
"integrity": "sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@pinojs/redact": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
@ -648,6 +1005,79 @@
"dev": true,
"license": "MIT"
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
"integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
"integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@salesforce/core": {
"version": "8.27.1",
"resolved": "https://registry.npmjs.org/@salesforce/core/-/core-8.27.1.tgz",
@ -679,6 +1109,13 @@
"node": ">=18.0.0"
}
},
"node_modules/@salesforce/jsonrpc": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/@salesforce/jsonrpc/-/jsonrpc-10.2.2.tgz",
"integrity": "sha512-55ceeP49ZFuFgxHm659xLw4EssHJnDZgenropQuVR/RpyuKPSPxjBU2dVBXnxHL32Ok7yGrwxa8qbywfzq37sw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@salesforce/kit": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/@salesforce/kit/-/kit-3.2.6.tgz",
@ -689,6 +1126,68 @@
"@salesforce/ts-types": "^2.0.12"
}
},
"node_modules/@salesforce/platform-sdk": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/@salesforce/platform-sdk/-/platform-sdk-10.2.2.tgz",
"integrity": "sha512-4GMgDAoZJ0Z1yhkOtWSDoLsrJfFU+ZzHf4d34cgLH1v9POvqB36YwenNzN2zOYpLfidKeGoXuR9S/U9eEgbxCg==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/command-http-cache-control": "3.19.6",
"@conduit-client/onestore-graphql-parser": "3.19.6",
"@conduit-client/service-cache": "3.19.6",
"@conduit-client/service-cache-control": "3.19.6",
"@conduit-client/service-cache-inclusion-policy": "3.19.6",
"@conduit-client/service-fetch-network": "3.19.6",
"@conduit-client/service-pubsub": "3.19.6",
"@conduit-client/service-retry": "3.19.6",
"@conduit-client/utils": "3.19.6",
"@salesforce/jsonrpc": "^10.2.2"
},
"peerDependencies": {
"o11y": ">=260.0.0",
"o11y_schema": ">=260.63.0"
}
},
"node_modules/@salesforce/platform-sdk/node_modules/@conduit-client/jwt-manager": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/jwt-manager/-/jwt-manager-3.19.6.tgz",
"integrity": "sha512-tpkg0DtJxTdW/zac+nm8wCpSw2wavYtOfVDHfmAAtH/FF39PWjcam7ldyru1G95NR2JbvAxbLQZefC+Dt0n5kg==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"jwt-decode": "~3.1.2"
}
},
"node_modules/@salesforce/platform-sdk/node_modules/@conduit-client/service-fetch-network": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-fetch-network/-/service-fetch-network-3.19.6.tgz",
"integrity": "sha512-yOsiRyOrM03tmX92oVbThJs7jZfjiVBQZiGkV+I+980TrJGnAwvaZlQVeG4Ncb285HtpBzkUnuc05Eol/tlCgw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/jwt-manager": "3.19.6",
"@conduit-client/service-retry": "3.19.6",
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@salesforce/platform-sdk/node_modules/@conduit-client/service-retry": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/service-retry/-/service-retry-3.19.6.tgz",
"integrity": "sha512-Zb1V2un+X3iFFN/eiY+V+G9cfvRqK2ppHjKWVF+SXRY52fOKVbEvbRA/n/YM/xemfA4PiMcmNzOrhvHwYNImaw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/utils": "3.19.6"
}
},
"node_modules/@salesforce/platform-sdk/node_modules/@conduit-client/utils": {
"version": "3.19.6",
"resolved": "https://registry.npmjs.org/@conduit-client/utils/-/utils-3.19.6.tgz",
"integrity": "sha512-sEE8RIz1+LxPJJVkBsSATgQBEn91pKdiEahUqisnodmnct+uLbI+bi/k4NPXoNMSIZM7W2VgEWFQDHF2GXZ28w==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@salesforce/sdk-core": {
"version": "1.120.6",
"resolved": "https://registry.npmjs.org/@salesforce/sdk-core/-/sdk-core-1.120.6.tgz",
@ -719,14 +1218,14 @@
}
},
"node_modules/@salesforce/ui-bundle": {
"version": "1.120.6",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle/-/ui-bundle-1.120.6.tgz",
"integrity": "sha512-sIHzbSMkMwvhh7IzAO/TzTMjIdphf8dTu+A4+LHobQ0XLp9Y0dii1ckMhLTb/i97qdCDq+aeIIKVQpRedgjsTw==",
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle/-/ui-bundle-10.2.2.tgz",
"integrity": "sha512-6ljxZ/wUVwx+IeFUXzlHfUOffi/eNyVhGe8IGp8FFtA1OTf73F0zjIUHrg9JIMYjxAtn3xya1EHbb5xkX3LKxg==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@salesforce/core": "^8.23.4",
"@salesforce/sdk-data": "^1.120.6",
"@salesforce/platform-sdk": "^10.2.2",
"micromatch": "^4.0.8",
"path-to-regexp": "^8.3.0"
},
@ -735,20 +1234,20 @@
}
},
"node_modules/@salesforce/ui-bundle-template-app-react-sample-b2e": {
"version": "1.120.6",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2e/-/ui-bundle-template-app-react-sample-b2e-1.120.6.tgz",
"integrity": "sha512-/A31Oon0gRvs+kshC55T2VoaEqDNfnwsSfJNfCi8Y4Y2UeGuMNtd4tvRrahQ6+QBup4NWUdOQNIwJWaXUWDC4A==",
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2e/-/ui-bundle-template-app-react-sample-b2e-10.2.2.tgz",
"integrity": "sha512-b/0O4iPWYBulEmuzn1z+Ge0wNDtNIO4NxUE5ld3ho9rqQSomNLf9KzWPDXsK41dJRIkIERBjhhlW8DYNa87PIw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@salesforce/ui-bundle": "^1.120.6",
"@salesforce/ui-bundle": "^10.2.2",
"sonner": "^1.7.0"
}
},
"node_modules/@salesforce/ui-bundle-template-app-react-sample-b2x": {
"version": "1.120.6",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2x/-/ui-bundle-template-app-react-sample-b2x-1.120.6.tgz",
"integrity": "sha512-j0joo4RYXM9OnVpZX4vb8nI/AMw9Uxz6xzm9o8KQ8WkU79P20xBdRTKBS1H379rpWV0KcxaMFBx6qGV82jgLNA==",
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2x/-/ui-bundle-template-app-react-sample-b2x-10.2.2.tgz",
"integrity": "sha512-hFAgK3CfnRVdO2msBKLvlgBK17XLLC5Scd9p9FcWdJsf0dFw5W3VYbI001wsBslh4SyLVW+e5K4zVT2I05QFhA==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
@ -807,6 +1306,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": ">=7.24.0 <7.24.7"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@ -1542,6 +2051,16 @@
"dev": true,
"license": "ISC"
},
"node_modules/graphql": {
"version": "16.11.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz",
"integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@ -1850,6 +2369,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/lower-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
@ -2004,6 +2530,35 @@
}
}
},
"node_modules/o11y": {
"version": "264.6.0",
"resolved": "https://registry.npmjs.org/o11y/-/o11y-264.6.0.tgz",
"integrity": "sha512-v3x29+PphNaORbOrYi6ucsx391hwJk+mxf4XGGoRB6+BcI0W3/fhazaXBCBtEZOnU2YDLgpFd8B4CzCmBFiKFQ==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"o11y_schema": "260.5.0",
"protobufjs": "7.5.5",
"web-vitals": "^5.1.0"
}
},
"node_modules/o11y_schema": {
"version": "264.70.0",
"resolved": "https://registry.npmjs.org/o11y_schema/-/o11y_schema-264.70.0.tgz",
"integrity": "sha512-3FVh/krlBe1+CnQZUrZkIH1JsI7cpSxorbosSLXLOOPyE8wHNRXUT5hfaH2XlhlAQMneoK+EBM2uioRvoXZSHA==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/o11y/node_modules/o11y_schema": {
"version": "260.5.0",
"resolved": "https://registry.npmjs.org/o11y_schema/-/o11y_schema-260.5.0.tgz",
"integrity": "sha512-0qrtqE394CODoPovUBdQQ2efHls6Z2xP6jvLitEGKSEj1KcCyeBLx2dHwTUtv66zwZTKjSp27QLFF4PZhengEQ==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/on-exit-leak-free": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
@ -2275,6 +2830,32 @@
"signal-exit": "^3.0.2"
}
},
"node_modules/protobufjs": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz",
"integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==",
"dev": true,
"hasInstallScript": true,
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/proxy-from-env": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
@ -2715,6 +3296,13 @@
"node": "*"
}
},
"node_modules/undici-types": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"dev": true,
"license": "MIT"
},
"node_modules/upper-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz",
@ -2742,6 +3330,14 @@
"dev": true,
"license": "MIT"
},
"node_modules/web-vitals": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.3.0.tgz",
"integrity": "sha512-q6LWsLatGYZp5VGBIOvbTj6JBV2nOmC8KvWztXBmwJcfFAzhwKwbOxhUH306XY3CcaZDUlSmSuNPBsCn0bFu+g==",
"dev": true,
"license": "Apache-2.0",
"peer": true
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

View File

@ -11,8 +11,8 @@
"registry": "https://registry.npmjs.org"
},
"devDependencies": {
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^1.120.6",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^1.120.6",
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^10.2.2",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^10.2.2",
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.117.1",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.117.1",
"@types/js-yaml": "^4.0.9",

View File

@ -1 +1 @@
1.120.6
10.2.2

View File

@ -148,9 +148,9 @@ Do not consider a task complete until all three steps have been run successfully
**Before writing any code that connects to Salesforce, you MUST invoke the `using-ui-bundle-salesforce-data` skill. Do not write any data access code without consulting it first.**
This applies to: GraphQL queries/mutations, REST calls, SDK initialization, custom hooks that fetch data, or any code that imports from `@salesforce/sdk-data`.
This applies to: GraphQL queries/mutations, REST calls, SDK initialization, custom hooks that fetch data, or any code that imports from `@salesforce/platform-sdk`.
- **All data access uses the Data SDK** (`@salesforce/sdk-data`) via `createDataSDK()`.
- **All data access uses the Data SDK** (`@salesforce/platform-sdk`) via `createDataSDK()`.
- **Never** use `fetch()` or `axios` directly for Salesforce data.
- **GraphQL is preferred** for record operations (`sdk.graphql`). Use `sdk.fetch` only when GraphQL cannot cover the case (UI API REST, Apex REST, Connect REST, Einstein LLM).
- Use optional chaining: `sdk.graphql?.()`, `sdk.fetch?.()`.
@ -181,7 +181,7 @@ Any endpoint not listed above is not permitted.
6. **HTTP 200 does not mean success** — Salesforce returns HTTP 200 even on failure. Always check the `errors` array in the response body.
#### GraphQL inline queries
Must use the `gql` template tag from `@salesforce/sdk-data` — plain template strings bypass `@graphql-eslint` schema validation. For complex queries, use external `.graphql` files with codegen.
Must use the `gql` template tag from `@salesforce/platform-sdk` — plain template strings bypass `@graphql-eslint` schema validation. For complex queries, use external `.graphql` files with codegen.
#### Current user info
Use GraphQL (`uiapi { currentUser { Id Name { value } } }`), not Chatter (`/chatter/users/me`).

View File

@ -3,6 +3,749 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [10.2.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v10.2.1...v10.2.2) (2026-06-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [10.2.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v10.2.0...v10.2.1) (2026-06-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [10.2.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v10.1.0...v10.2.0) (2026-06-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [10.1.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v10.0.0...v10.1.0) (2026-06-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [10.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.21.0...v10.0.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.21.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.20.1...v9.21.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.20.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.20.0...v9.20.1) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.20.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.19.0...v9.20.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.19.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.18.0...v9.19.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.18.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.17.1...v9.18.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.17.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.17.0...v9.17.1) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.17.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.16.1...v9.17.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.16.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.16.0...v9.16.1) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.16.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.15.0...v9.16.0) (2026-05-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.15.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.14.0...v9.15.0) (2026-05-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.14.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.13.0...v9.14.0) (2026-05-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.13.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.12.1...v9.13.0) (2026-05-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.12.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.12.0...v9.12.1) (2026-05-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.12.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.11.0...v9.12.0) (2026-05-27)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.11.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.10.3...v9.11.0) (2026-05-27)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.10.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.10.2...v9.10.3) (2026-05-27)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.10.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.10.1...v9.10.2) (2026-05-27)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.10.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.10.0...v9.10.1) (2026-05-26)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.10.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.9.4...v9.10.0) (2026-05-26)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.9.4](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.9.3...v9.9.4) (2026-05-26)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.9.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.9.2...v9.9.3) (2026-05-25)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.9.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.9.1...v9.9.2) (2026-05-25)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.9.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.9.0...v9.9.1) (2026-05-25)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.9.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.8.2...v9.9.0) (2026-05-22)
### Features
* (template) add customApplication metadata for B2E UIBundle apps @W-22568671 ([#548](https://github.com/salesforce-experience-platform-emu/webapps/issues/548)) ([5d39634](https://github.com/salesforce-experience-platform-emu/webapps/commit/5d396340ee297f6854a91d2b6ca673e438bfb8a6))
## [9.8.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.8.1...v9.8.2) (2026-05-22)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.8.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.8.0...v9.8.1) (2026-05-22)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.8.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.7.0...v9.8.0) (2026-05-22)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.7.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.6.1...v9.7.0) (2026-05-21)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.6.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.6.0...v9.6.1) (2026-05-21)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.6.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.5.0...v9.6.0) (2026-05-21)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.5.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.4.1...v9.5.0) (2026-05-21)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.4.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.4.0...v9.4.1) (2026-05-21)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.4.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.3.0...v9.4.0) (2026-05-20)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.3.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.2.2...v9.3.0) (2026-05-20)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.2.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.2.1...v9.2.2) (2026-05-20)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.2.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.2.0...v9.2.1) (2026-05-20)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.2.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.1.0...v9.2.0) (2026-05-19)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.1.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.0.0...v9.1.0) (2026-05-18)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v8.0.1...v9.0.0) (2026-05-18)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [8.0.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v8.0.0...v8.0.1) (2026-05-18)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [8.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v7.1.0...v8.0.0) (2026-05-15)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [7.1.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v7.0.1...v7.1.0) (2026-05-14)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [7.0.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v7.0.0...v7.0.1) (2026-05-14)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [7.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v6.0.0...v7.0.0) (2026-05-12)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [6.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v5.0.0...v6.0.0) (2026-05-12)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [5.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v4.0.0...v5.0.0) (2026-05-12)
### ⚠ BREAKING CHANGES
* **sdk:** @W-22453506 flip packages/template/** to @salesforce/platform-sdk (#510)
### Features
* **sdk:** @W-22453506 flip packages/template/** to @salesforce/platform-sdk ([#510](https://github.com/salesforce-experience-platform-emu/webapps/issues/510)) ([ab56fab](https://github.com/salesforce-experience-platform-emu/webapps/commit/ab56fab3f4bae239daeaac3f5f29cf31c951be29))
## [4.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v3.1.1...v4.0.0) (2026-05-12)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [3.1.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v3.1.0...v3.1.1) (2026-05-12)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [3.1.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v3.0.0...v3.1.0) (2026-05-12)
### Features
* post tdx changes @W-22390798 ([#500](https://github.com/salesforce-experience-platform-emu/webapps/issues/500)) ([b611d9e](https://github.com/salesforce-experience-platform-emu/webapps/commit/b611d9ecbadd07d052bd7d9a4f5659dec422bf22))
## [3.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v2.2.1...v3.0.0) (2026-05-11)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [2.2.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v2.2.0...v2.2.1) (2026-05-11)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [2.2.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v2.1.0...v2.2.0) (2026-05-08)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [2.1.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v2.0.0...v2.1.0) (2026-05-08)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [2.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.135.0...v2.0.0) (2026-05-07)
### ⚠ BREAKING CHANGES
* **sdk:** @W-22351493 rename sdk-data + sdk-analytics to @salesforce/platform-sdk-* (#494)
### Features
* **sdk:** @W-22351493 rename sdk-data + sdk-analytics to @salesforce/platform-sdk-* ([#494](https://github.com/salesforce-experience-platform-emu/webapps/issues/494)) ([725a8f8](https://github.com/salesforce-experience-platform-emu/webapps/commit/725a8f8dba4ce6003b69fb334a36c5131258f161))
## [1.135.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.5...v1.135.0) (2026-05-05)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.134.5](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.4...v1.134.5) (2026-05-04)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.134.4](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.3...v1.134.4) (2026-05-04)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.134.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.2...v1.134.3) (2026-05-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.134.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.1...v1.134.2) (2026-05-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.134.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.0...v1.134.1) (2026-05-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.134.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.133.2...v1.134.0) (2026-04-30)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.133.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.133.1...v1.133.2) (2026-04-30)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.133.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.133.0...v1.133.1) (2026-04-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.133.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.132.0...v1.133.0) (2026-04-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.132.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.131.3...v1.132.0) (2026-04-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.131.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.131.2...v1.131.3) (2026-04-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.131.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.131.1...v1.131.2) (2026-04-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.131.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.131.0...v1.131.1) (2026-04-27)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.131.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.130.2...v1.131.0) (2026-04-24)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.130.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.130.1...v1.130.2) (2026-04-24)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.130.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.130.0...v1.130.1) (2026-04-24)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.130.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.129.1...v1.130.0) (2026-04-23)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.129.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.129.0...v1.129.1) (2026-04-23)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.129.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.128.0...v1.129.0) (2026-04-23)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.128.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.127.0...v1.128.0) (2026-04-23)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.127.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.126.0...v1.127.0) (2026-04-22)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.126.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.125.1...v1.126.0) (2026-04-22)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.125.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.125.0...v1.125.1) (2026-04-16)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.125.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.124.0...v1.125.0) (2026-04-15)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.124.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.123.0...v1.124.0) (2026-04-15)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.123.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.122.2...v1.123.0) (2026-04-13)
### Bug Fixes
* **template:** stop auto-starting dev server after sf-project-setup ([#429](https://github.com/salesforce-experience-platform-emu/webapps/issues/429)) ([5e7e7cf](https://github.com/salesforce-experience-platform-emu/webapps/commit/5e7e7cfb9dc1c5c000b1e825307dde9eb847697b))
## [1.122.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.122.1...v1.122.2) (2026-04-13)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.122.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.122.0...v1.122.1) (2026-04-10)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.122.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.121.0...v1.122.0) (2026-04-09)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.121.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.7...v1.121.0) (2026-04-09)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.120.7](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.6...v1.120.7) (2026-04-08)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.120.6](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.5...v1.120.6) (2026-04-07)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project

View File

@ -11,7 +11,7 @@ A property management sample React UI Bundle for the Salesforce platform. Demons
- [1. Install Dependencies](#1-install-dependencies)
- [2. Authenticate Your Org](#2-authenticate-your-org)
- [3. Deploy Metadata](#3-deploy-metadata)
- [4. Assign Permission Set](#4-assign-permission-set)
- [4. Assign Permission Sets](#4-assign-permission-sets)
- [5. Import Sample Data](#5-import-sample-data)
- [6. Generate GraphQL Types](#6-generate-graphql-types)
- [7. Rebuild the UI Bundle](#7-rebuild-the-ui-bundle)
@ -27,9 +27,9 @@ A property management sample React UI Bundle for the Salesforce platform. Demons
```
force-app/main/default/
├── uiBundles/ # React UI Bundle (source, config, tests)
├── objects/ # 17 Custom Objects (Property, Tenant, Lease, etc.)
├── objects/ # 18 Custom Objects (Property, Tenant, Lease, Agent, etc.)
├── layouts/ # Page layouts for all custom objects
├── permissionsets/ # Property_Management_Access (Full CRUD)
├── permissionsets/ # Property_Management_Access (Full CRUD) & Tenant_Maintenance_Access (Scoped)
└── data/ # Sample JSON data (use: sf data import tree)
```
@ -92,6 +92,38 @@ For a full list of options:
npm run setup -- --help
```
### Setup Configuration (`scripts/org-setup.config.json`)
The `npm run setup` script reads `scripts/org-setup.config.json` to control permission set assignment behavior. Each top-level section is **optional** — if a section is absent, the corresponding step is hidden from the interactive picker.
```json
{
"permsetAssignments": {
"defaultAssignee": "skip",
"assignments": {
"Property_Management_Access": { "assignee": "currentUser" },
"Tenant_Maintenance_Access": { "assignee": "skip" }
}
}
}
```
#### `permsetAssignments`
| Field | Description |
| ----------------- | ---------------------------------------------------------------------------- |
| `defaultAssignee` | Fallback assignee for permission sets not explicitly listed in `assignments` |
| `assignments` | Per-permission-set overrides (key = permission set API name) |
Each assignment's `assignee` value controls who it is assigned to:
| Value | Behavior |
| ---------------- | --------------------------------------------------------------------------------- |
| `"currentUser"` | Assigns to the user running the script (resolved via `sf org display`) |
| `"skip"` | Explicitly skips this permission set |
| `"guestUser"` | Auto-resolves the site's guest user (requires `siteName` field on the same entry) |
| `"user@org.com"` | Assigns to a specific user by username |
---
## Step-by-Step Setup
@ -167,18 +199,21 @@ sf project deploy start --source-dir force-app/main/default/uiBundles --target-o
Replace `<alias>` with your target org alias.
### 4. Assign Permission Set
### 4. Assign Permission Sets
After deploying the metadata, assign the `Property_Management_Access` permission set to grant access to the custom objects and fields:
After deploying the metadata, assign permission sets to grant access to the custom objects and fields:
| Permission Set | Purpose | Assign To |
| ---------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------- |
| `Property_Management_Access` | Full CRUD access to all custom objects. Intended for property managers and admin users. | Internal users managing the app |
| `Tenant_Maintenance_Access` | Scoped read/write access for tenants. Allows creating and updating their own maintenance requests only. | Tenant community users |
```bash
# Assign Property_Management_Access to the current user
sf org assign permset --name Property_Management_Access --target-org <alias>
```
To assign to a specific user:
```bash
sf org assign permset --name Property_Management_Access --on-behalf-of <username> --target-org <alias>
# Assign Tenant_Maintenance_Access to a specific tenant user
sf org assign permset --name Tenant_Maintenance_Access --on-behalf-of <username> --target-org <alias>
```
Replace `<alias>` with your target org alias.

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomApplication xmlns="http://soap.sforce.com/2006/04/metadata">
<defaultLandingTab>standard-home</defaultLandingTab>
<description>Property Rental Management Application for managing properties, tenants, leases, payments, and maintenance.</description>
<formFactors>Large</formFactors>
<isNavAutoTempTabsDisabled>false</isNavAutoTempTabsDisabled>
<isNavPersonalizationDisabled>false</isNavPersonalizationDisabled>
<isNavTabPersistenceDisabled>false</isNavTabPersistenceDisabled>
<isOmniPinnedViewEnabled>false</isOmniPinnedViewEnabled>
<label>Property Management</label>
<navType>Standard</navType>
<tabs>standard-home</tabs>
<tabs>Property__c</tabs>
<tabs>Property_Listing__c</tabs>
<tabs>Tenant__c</tabs>
<tabs>Lease__c</tabs>
<tabs>Payment__c</tabs>
<tabs>Maintenance_Request__c</tabs>
<tabs>Maintenance_Worker__c</tabs>
<tabs>Application__c</tabs>
<tabs>Property_Owner__c</tabs>
<tabs>Property_Management_Company__c</tabs>
<tabs>Property_Cost__c</tabs>
<tabs>Property_Feature__c</tabs>
<tabs>Property_Image__c</tabs>
<tabs>Property_Sale__c</tabs>
<tabs>KPI_Snapshot__c</tabs>
<tabs>Notification__c</tabs>
<tabs>Agent__c</tabs>
<tabs>standard-report</tabs>
<tabs>standard-Dashboard</tabs>
<uiType>Lightning</uiType>
</CustomApplication>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomApplication xmlns="http://soap.sforce.com/2006/04/metadata">
<brand>
<headerColor>#0070D2</headerColor>
<shouldOverrideOrgTheme>false</shouldOverrideOrgTheme>
</brand>
<formFactors>Small</formFactors>
<formFactors>Large</formFactors>
<isNavAutoTempTabsDisabled>false</isNavAutoTempTabsDisabled>
<isNavPersonalizationDisabled>false</isNavPersonalizationDisabled>
<isNavTabPersistenceDisabled>false</isNavTabPersistenceDisabled>
<isOmniPinnedViewEnabled>false</isOmniPinnedViewEnabled>
<label>propertymanagementapp</label>
<navType>Standard</navType>
<uiBundle>c__propertymanagementapp</uiBundle>
<uiType>Lightning</uiType>
</CustomApplication>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -6,7 +6,7 @@
"referenceId": "ApplicationRef1"
},
"Property__c": "@PropertyRef5",
"User__c": "@ContactRef1",
"User__c": null,
"Status__c": "Submitted",
"Start_Date__c": "2024-12-01",
"Employment__c": "TechCorp Inc. - Senior Software Engineer - $95,000/year",
@ -18,7 +18,7 @@
"referenceId": "ApplicationRef2"
},
"Property__c": "@PropertyRef8",
"User__c": "@ContactRef2",
"User__c": null,
"Status__c": "Background Check",
"Start_Date__c": "2024-12-15",
"Employment__c": "City Hospital - Registered Nurse - $72,000/year",
@ -30,7 +30,7 @@
"referenceId": "ApplicationRef3"
},
"Property__c": "@PropertyRef12",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Approved",
"Start_Date__c": "2024-11-01",
"Employment__c": "Global Consulting LLC - Management Consultant - $110,000/year",
@ -42,7 +42,7 @@
"referenceId": "ApplicationRef4"
},
"Property__c": "@PropertyRef5",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Rejected",
"Start_Date__c": "2024-12-01",
"Employment__c": "Retail Store - Assistant Manager - $32,000/year",
@ -54,7 +54,7 @@
"referenceId": "ApplicationRef5"
},
"Property__c": "@PropertyRef16",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Draft",
"Start_Date__c": "2025-01-15",
"Employment__c": "University - Graduate Student - $18,000/year stipend",
@ -66,7 +66,7 @@
"referenceId": "ApplicationRef6"
},
"Property__c": "@PropertyRef20",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Submitted",
"Start_Date__c": "2025-01-01",
"Employment__c": "Local Business - Marketing Coordinator - $48,000/year (Co-signer: Parent, $85,000/year)",
@ -78,7 +78,7 @@
"referenceId": "ApplicationRef7"
},
"Property__c": "@PropertyRef8",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Background Check",
"Start_Date__c": "2024-12-20",
"Employment__c": "Finance Corp - Financial Analyst - $78,000/year",
@ -90,7 +90,7 @@
"referenceId": "ApplicationRef8"
},
"Property__c": "@PropertyRef3",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Approved",
"Start_Date__c": "2024-10-01",
"Employment__c": "Design Studio - UX Designer - $68,000/year",
@ -102,7 +102,7 @@
"referenceId": "ApplicationRef9"
},
"Property__c": "@PropertyRef12",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Rejected",
"Start_Date__c": "2024-10-25",
"Employment__c": "Various part-time positions - $25,000/year",
@ -114,7 +114,7 @@
"referenceId": "ApplicationRef10"
},
"Property__c": "@PropertyRef5",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Submitted",
"Start_Date__c": "2024-12-10",
"Employment__c": "Teaching - High School Teacher - $55,000/year",

View File

@ -1,44 +0,0 @@
{
"records": [
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef1"
},
"FirstName": "Jennifer",
"LastName": "Anderson",
"Email": "jennifer.anderson@example.com",
"Phone": "(555) 123-4567"
},
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef2"
},
"FirstName": "David",
"LastName": "Rodriguez",
"Email": "david.rodriguez@example.com",
"Phone": "(555) 234-5678"
},
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef3"
},
"FirstName": "Maria",
"LastName": "Garcia",
"Email": "maria.garcia@example.com",
"Phone": "(555) 345-6789"
},
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef4"
},
"FirstName": "James",
"LastName": "Brown",
"Email": "james.brown@example.com",
"Phone": "(555) 456-7890"
}
]
}

View File

@ -1,10 +1,4 @@
[
{
"sobject": "Contact",
"saveRefs": true,
"resolveRefs": false,
"files": ["Contact.json"]
},
{
"sobject": "Agent__c",
"saveRefs": true,

View File

@ -2,10 +2,10 @@
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>User__c</fullName>
<deleteConstraint>SetNull</deleteConstraint>
<description>The person applying (Contact record)</description>
<description>The person applying (User record)</description>
<externalId>false</externalId>
<label>Applicant</label>
<referenceTo>Contact</referenceTo>
<referenceTo>User</referenceTo>
<relationshipLabel>Applications</relationshipLabel>
<relationshipName>Applications</relationshipName>
<required>false</required>

View File

@ -53,7 +53,7 @@
<enableSearch>true</enableSearch>
<enableSharing>true</enableSharing>
<enableStreamingApi>true</enableStreamingApi>
<externalSharingModel>Private</externalSharingModel>
<externalSharingModel>Read</externalSharingModel>
<label>Property</label>
<nameField>
<label>Property Name</label>

View File

@ -630,4 +630,80 @@
<field>Tenant__c.Status__c</field>
<readable>true</readable>
</fieldPermissions>
<!-- Application Permissions -->
<applicationVisibilities>
<application>PropertyManagement</application>
<visible>true</visible>
</applicationVisibilities>
<!-- Tab Permissions -->
<tabSettings>
<tab>Agent__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Application__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>KPI_Snapshot__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Lease__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Maintenance_Request__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Maintenance_Worker__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Notification__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Payment__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Cost__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Feature__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Image__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Listing__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Management_Company__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Owner__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Sale__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Tenant__c</tab>
<visibility>Visible</visibility>
</tabSettings>
</PermissionSet>

View File

@ -36,7 +36,7 @@
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<editable>true</editable>
<field>Maintenance_Request__c.Property__c</field>
<readable>true</readable>
</fieldPermissions>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<applicationVisibilities>
<application>propertymanagementapp</application>
<visible>true</visible>
</applicationVisibilities>
<hasActivationRequired>false</hasActivationRequired>
<label>propertymanagementapp Access</label>
</PermissionSet>

View File

@ -1,9 +0,0 @@
{
"permsetAssignments": {
"defaultAssignee": "skip",
"assignments": {
"Property_Management_Access": { "assignee": "currentUser" },
"Tenant_Maintenance_Access": { "assignee": "currentUser" }
}
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom51: People</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom20: Airplane</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom40: Radar</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom25: Handshake</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom48: Wrench</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom48: Wrench</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom31: Bell</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom18: Dollar</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom18: Dollar</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom57: Check</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom46: Camera</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom24: Building</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom24: Building</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom51: People</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom18: Dollar</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom24: Building</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom24: Building</motif>
</CustomTab>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexTrigger xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexTrigger>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexTrigger xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexTrigger>

View File

@ -18,8 +18,8 @@
"graphql:schema": "node scripts/get-graphql-schema.mjs"
},
"dependencies": {
"@salesforce/sdk-data": "^1.120.6",
"@salesforce/ui-bundle": "^1.120.6",
"@salesforce/platform-sdk": "^10.2.2",
"@salesforce/ui-bundle": "^10.2.2",
"@tailwindcss/vite": "^4.1.17",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@ -35,7 +35,7 @@
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.1.17",
"tw-animate-css": "^1.4.0",
"@salesforce/agentforce-conversation-client": "^1.116.9",
"@salesforce/agentforce-conversation-client": "^9.4.0",
"recharts": "^2.15.0"
},
"devDependencies": {
@ -46,7 +46,7 @@
"@graphql-eslint/eslint-plugin": "^4.1.0",
"@graphql-tools/utils": "^11.0.0",
"@playwright/test": "^1.49.0",
"@salesforce/vite-plugin-ui-bundle": "^1.120.6",
"@salesforce/vite-plugin-ui-bundle": "^10.2.2",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",

View File

@ -4,4 +4,5 @@
<description>A Salesforce UI Bundle.</description>
<isActive>true</isActive>
<version>1</version>
<target>CustomApplication</target>
</UIBundle>

View File

@ -1,7 +1,6 @@
import GET_APPLICATIONS from "./query/getApplications.graphql?raw";
import UPDATE_APPLICATION_STATUS from "./query/updateApplicationStatus.graphql?raw";
import APPLICATION_FOR_APPROVAL_QUERY from "./query/applicationForApproval.graphql?raw";
import USER_BY_CONTACT_QUERY from "./query/userByContact.graphql?raw";
import EXISTING_TENANT_QUERY from "./query/existingTenant.graphql?raw";
import { createRecord } from "@salesforce/ui-bundle/api";
import type {
@ -11,8 +10,6 @@ import type {
UpdateApplicationStatusMutationVariables,
ApplicationForApprovalQuery,
ApplicationForApprovalQueryVariables,
UserByContactQuery,
UserByContactQueryVariables,
ExistingTenantQuery,
ExistingTenantQueryVariables,
} from "../graphql-operations-types.js";
@ -81,20 +78,11 @@ async function ensureTenantForApprovedApplication(applicationId: string): Promis
throw new Error("Application record not found.");
}
const contactId = applicationNode.User__c?.value ?? null;
const userId = applicationNode.User__c?.value ?? null;
const propertyId = applicationNode.Property__c?.value ?? null;
const startDate = applicationNode.Start_Date__c?.value ?? null;
if (!contactId || !propertyId) {
return;
}
const userData = await executeGraphQL<UserByContactQuery, UserByContactQueryVariables>(
USER_BY_CONTACT_QUERY,
{ contactId },
);
const userId = userData.uiapi?.query?.User?.edges?.[0]?.node?.Id ?? null;
if (!userId) {
if (!userId || !propertyId) {
return;
}

View File

@ -9,10 +9,7 @@ query GetApplications {
value
}
User__r @optional {
FirstName @optional {
value
}
LastName @optional {
Name @optional {
value
}
}

View File

@ -1,25 +1,28 @@
/**
* Thin GraphQL client: createDataSDK + data.graphql with centralized error handling.
* Use with gql-tagged queries and generated operation types for type-safe calls.
* Thin GraphQL client: createDataSDK + sdk.graphql.query with centralized
* error handling. Use with gql-tagged queries and generated operation types
* for type-safe calls.
*/
import { createDataSDK } from '@salesforce/sdk-data';
import { createDataSDK } from '@salesforce/platform-sdk';
export async function executeGraphQL<TData, TVariables>(
query: string,
variables?: TVariables
): Promise<TData> {
const data = await createDataSDK();
// SDK types graphql() first param as string; at runtime it may accept gql DocumentNode too
const response = await data.graphql?.<TData, TVariables>(query, variables);
const result = await data.graphql!.query<TData, TVariables>({
query: query,
variables: variables,
});
if (!response) {
throw new Error('GraphQL response is undefined');
}
if (response?.errors?.length) {
const msg = response.errors.map(e => e.message).join('; ');
if (result.errors?.length) {
const msg = result.errors.map(e => e.message).join('; ');
throw new Error(`GraphQL Error: ${msg}`);
}
return response.data;
if (result.data == null) {
throw new Error('GraphQL response data is null');
}
return result.data;
}

View File

@ -13,38 +13,6 @@ import type {
} from "../types/conversation";
const GLOBAL_HOST_ID = "agentforce-conversation-client-global-host";
const DEFAULT_STYLE_TOKENS: NonNullable<AgentforceClientConfig["styleTokens"]> = {
containerBackground: "#fafafa",
headerBlockBackground: "#372949",
headerBlockTextColor: "#ffffff",
headerBlockIconColor: "#ffffff",
headerBlockBorderBottomColor: "#3b0764",
headerBlockFocusBorder: "#c4b5fd",
messageBlockInboundBackgroundColor: "#ffffff",
messageBlockInboundTextColor: "#1f2937",
messageBlockInboundBorder: "1px solid #e5e7eb",
messageBlockOutboundBackgroundColor: "#ede9fe",
messageBlockOutboundTextColor: "#1f2937",
messageBlockOutboundBorder: "1px solid #d8b4fe",
messageInputTextColor: "#1f2937",
messageInputTextBackgroundColor: "#ffffff",
messageInputFooterBorderColor: "#d1d5db",
messageInputFooterBorderFocusColor: "#9ca3af",
messageInputFocusShadow: "0 0 0 3px rgba(156, 163, 175, 0.25)",
messageInputFooterPlaceholderText: "#6b7280",
messageInputFooterSendButton: "#7e22ce",
messageInputFooterSendButtonHoverColor: "#6b21a8",
messageInputSendButtonIconColor: "#ffffff",
messageInputSendButtonDisabledColor: "#e5e7eb",
messageInputActionButtonFocusBorder: "#a855f7",
errorBlockBackground: "#fafafa",
};
const SINGLETON_KEY = "__agentforceConversationClientSingleton";
interface AgentforceConversationClientSingleton {
@ -56,6 +24,12 @@ interface WindowWithAgentforceSingleton extends Window {
[SINGLETON_KEY]?: AgentforceConversationClientSingleton;
}
type GlobalWithSfdcEnv = typeof globalThis & {
SFDC_ENV?: {
orgUrl?: string;
};
};
function getSingleton(): AgentforceConversationClientSingleton {
const win = window as WindowWithAgentforceSingleton;
if (!win[SINGLETON_KEY]) {
@ -77,7 +51,8 @@ function getOrCreateGlobalHost(): HTMLDivElement {
}
function getDefaultEmbedOptions(): ResolvedEmbedOptions {
return { salesforceOrigin: window.location.origin };
const sfdcEnv = (globalThis as GlobalWithSfdcEnv).SFDC_ENV;
return { salesforceOrigin: sfdcEnv?.orgUrl };
}
/**
@ -96,6 +71,8 @@ export function AgentforceConversationClient({
styleTokens,
salesforceOrigin,
frontdoorUrl,
onReady,
onError,
}: AgentforceConversationClientProps) {
const containerRef = useRef<HTMLDivElement>(null);
const normalizedAgentforceClientConfig = useMemo<AgentforceClientConfig>(() => {
@ -111,8 +88,9 @@ export function AgentforceConversationClient({
return {
...(agentId !== undefined && { agentId }),
...(agentLabel !== undefined && { agentLabel }),
styleTokens: { ...DEFAULT_STYLE_TOKENS, ...styleTokens },
...(styleTokens !== undefined && { styleTokens }),
renderingConfig,
channel: "Vibes",
};
}, [agentId, agentLabel, inlineProp, headerEnabled, showHeaderIcon, width, height, styleTokens]);
@ -146,13 +124,31 @@ export function AgentforceConversationClient({
}
const host = inline ? containerRef.current! : getOrCreateGlobalHost();
embedAgentforceClient({
container: host,
salesforceOrigin: salesforceOrigin ?? options.salesforceOrigin,
frontdoorUrl: frontdoorUrl ?? options.frontdoorUrl,
agentforceClientConfig: normalizedAgentforceClientConfig,
});
singleton.initialized = true;
try {
embedAgentforceClient({
container: host,
salesforceOrigin: salesforceOrigin ?? options.salesforceOrigin,
frontdoorUrl: frontdoorUrl ?? options.frontdoorUrl,
agentforceClientConfig: normalizedAgentforceClientConfig,
onReady,
onError,
});
singleton.initialized = true;
} catch (err) {
// Strip a partially-created LO element so the next mount can retry.
const partialEmbed = document.querySelector('lightning-out-application[data-lo="acc"]');
partialEmbed?.remove();
console.error("AgentforceConversationClient: initialization failed", err);
}
};
const safeInitialize = (options: ResolvedEmbedOptions) => {
try {
initialize(options);
} catch (initErr) {
console.error("AgentforceConversationClient: initialization failed", initErr);
singleton.initialized = false;
}
};
const shouldFetchFrontdoor = window.location.hostname === "localhost";
@ -165,7 +161,7 @@ export function AgentforceConversationClient({
return;
}
const { frontdoorUrl: resolvedFrontdoorUrl } = await res.json();
initialize({ frontdoorUrl: resolvedFrontdoorUrl });
safeInitialize({ frontdoorUrl: resolvedFrontdoorUrl });
})
.catch((err) => {
console.error("AgentforceConversationClient: failed to fetch frontdoor URL", err);
@ -176,7 +172,7 @@ export function AgentforceConversationClient({
} else {
singleton.initPromise = Promise.resolve()
.then(() => {
initialize(getDefaultEmbedOptions());
safeInitialize(getDefaultEmbedOptions());
})
.catch((err) => {
console.error("AgentforceConversationClient: failed to embed Agentforce client", err);
@ -190,7 +186,7 @@ export function AgentforceConversationClient({
// Intentionally no cleanup:
// This component guarantees a single LO initialization per window.
};
}, [salesforceOrigin, frontdoorUrl, normalizedAgentforceClientConfig, inline]);
}, [salesforceOrigin, frontdoorUrl, normalizedAgentforceClientConfig, inline, onReady, onError]);
if (!inline) {
return null;

View File

@ -1,5 +1,5 @@
import { cva, type VariantProps } from 'class-variance-authority';
import { AlertCircleIcon, CheckCircle2Icon } from 'lucide-react';
import { AlertCircleIcon, CheckCircle2Icon, InfoIcon } from 'lucide-react';
import { Alert, AlertDescription } from '../../components/ui/alert';
import { useId } from 'react';
@ -8,6 +8,7 @@ const statusAlertVariants = cva('', {
variant: {
error: '',
success: '',
info: 'text-blue-600 *:[svg]:text-current *:data-[slot=alert-description]:text-blue-600/90',
},
},
defaultVariants: {
@ -18,11 +19,11 @@ const statusAlertVariants = cva('', {
interface StatusAlertProps extends VariantProps<typeof statusAlertVariants> {
children?: React.ReactNode;
/** Alert variant type. @default "error" */
variant?: 'error' | 'success';
variant?: 'error' | 'success' | 'info';
}
/**
* Status alert component for displaying error or success messages.
* Status alert component for displaying error, success, or info messages.
* Returns null if no children are provided.
*/
export function StatusAlert({ children, variant = 'error' }: StatusAlertProps) {
@ -31,6 +32,12 @@ export function StatusAlert({ children, variant = 'error' }: StatusAlertProps) {
const isError = variant === 'error';
const icon = {
error: <AlertCircleIcon aria-hidden="true" />,
success: <CheckCircle2Icon aria-hidden="true" />,
info: <InfoIcon aria-hidden="true" />,
}[variant];
return (
<Alert
variant={isError ? 'destructive' : 'default'}
@ -38,11 +45,7 @@ export function StatusAlert({ children, variant = 'error' }: StatusAlertProps) {
aria-describedby={descriptionId}
role={isError ? 'alert' : 'status'}
>
{isError ? (
<AlertCircleIcon aria-hidden="true" />
) : (
<CheckCircle2Icon aria-hidden="true" />
)}
{icon}
<AlertDescription id={descriptionId}>{children}</AlertDescription>
</Alert>
);

View File

@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
type={type}
data-slot="input"
className={cn(
'dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
'dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground/70 placeholder:italic w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}

View File

@ -1,6 +1,6 @@
import { useState } from "react";
import { useParams, useNavigate } from "react-router";
import { createDataSDK } from "@salesforce/sdk-data";
import { createDataSDK } from "@salesforce/platform-sdk";
import { AlertCircle, ChevronDown, ChevronRight, FileQuestion } from "lucide-react";
import GET_ACCOUNT_DETAIL from "../api/query/getAccountDetail.graphql?raw";
import type {
@ -18,7 +18,7 @@ import {
} from "../../../../components/ui/collapsible";
import { Separator } from "../../../../components/ui/separator";
import { Skeleton } from "../../../../components/ui/skeleton";
import { useCachedAsyncData } from "../../hooks/useCachedAsyncData";
import { useAsyncData } from "../../hooks/useAsyncData";
import { ObjectBreadcrumb } from "../../components/ObjectBreadcrumb";
type AccountNode = NonNullable<
@ -29,16 +29,16 @@ type AccountNode = NonNullable<
async function fetchAccountDetail(recordId: string): Promise<AccountNode | null | undefined> {
const data = await createDataSDK();
const response = await data.graphql?.<GetAccountDetailQuery, GetAccountDetailQueryVariables>(
GET_ACCOUNT_DETAIL,
{ id: recordId },
);
const result = await data.graphql!.query<GetAccountDetailQuery, GetAccountDetailQueryVariables>({
query: GET_ACCOUNT_DETAIL,
variables: { id: recordId },
});
if (response?.errors?.length) {
throw new Error(response.errors.map((e) => e.message).join("; "));
if (result.errors?.length) {
throw new Error(result.errors.map((e) => e.message).join("; "));
}
return response?.data?.uiapi?.query?.Account?.edges?.[0]?.node;
return result.data?.uiapi?.query?.Account?.edges?.[0]?.node;
}
export default function AccountObjectDetail() {
@ -49,9 +49,7 @@ export default function AccountObjectDetail() {
data: account,
loading,
error,
} = useCachedAsyncData(() => fetchAccountDetail(recordId!), [recordId], {
key: `account:${recordId}`,
});
} = useAsyncData(() => fetchAccountDetail(recordId!), [recordId]);
return (
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6">

View File

@ -6,7 +6,7 @@ import {
fetchDistinctIndustries,
fetchDistinctTypes,
} from "../api/accountSearchService";
import { useCachedAsyncData } from "../../hooks/useCachedAsyncData";
import { useAsyncData } from "../../hooks/useAsyncData";
import { fieldValue } from "../../utils/fieldUtils";
import { useObjectSearchParams } from "../../hooks/useObjectSearchParams";
import { Alert, AlertTitle, AlertDescription } from "../../../../components/ui/alert";
@ -42,8 +42,8 @@ import PaginationControls from "../../components/PaginationControls";
import type { PaginationConfig } from "../../hooks/useObjectSearchParams";
const PAGINATION_CONFIG: PaginationConfig = {
defaultPageSize: 6,
validPageSizes: [6, 12, 24, 48],
defaultPageSize: 7,
validPageSizes: [7, 14, 28, 42],
};
type AccountNode = NonNullable<
@ -77,22 +77,15 @@ const ACCOUNT_SORT_CONFIGS: SortFieldConfig<keyof Account_OrderBy>[] = [
export default function AccountSearch() {
const [filtersOpen, setFiltersOpen] = useState(true);
const { data: industryOptions } = useCachedAsyncData(fetchDistinctIndustries, [], {
key: "distinctIndustries",
ttl: 300_000,
});
const { data: typeOptions } = useCachedAsyncData(fetchDistinctTypes, [], {
key: "distinctTypes",
ttl: 300_000,
});
const { data: industryOptions } = useAsyncData(fetchDistinctIndustries, []);
const { data: typeOptions } = useAsyncData(fetchDistinctTypes, []);
const { filters, sort, query, pagination, resetAll } = useObjectSearchParams<
Account_Filter,
Account_OrderBy
>(FILTER_CONFIGS, ACCOUNT_SORT_CONFIGS, PAGINATION_CONFIG);
const searchKey = `accounts:${JSON.stringify({ where: query.where, orderBy: query.orderBy, first: pagination.pageSize, after: pagination.afterCursor })}`;
const { data, loading, error } = useCachedAsyncData(
const { data, loading, error } = useAsyncData(
() =>
searchAccounts({
where: query.where,
@ -101,7 +94,6 @@ export default function AccountSearch() {
after: pagination.afterCursor,
}),
[query.where, query.orderBy, pagination.pageSize, pagination.afterCursor],
{ key: searchKey },
);
const pageInfo = data?.pageInfo;
@ -196,7 +188,7 @@ export default function AccountSearch() {
<ActiveFilters filters={filters.active} onRemove={filters.remove} />
</div>
<div className="min-h-112">
<div className="min-h-132">
{/* Loading state */}
{loading && (
<>

View File

@ -1,4 +1,4 @@
import { createDataSDK } from "@salesforce/sdk-data";
import { createDataSDK } from "@salesforce/platform-sdk";
export interface ObjectSearchOptions<TWhere, TOrderBy> {
where?: TWhere;
@ -21,21 +21,20 @@ export async function searchObjects<TResult, TQuery, TVariables>(
const { where, orderBy, first = 20, after } = options;
const data = await createDataSDK();
const response = await data.graphql?.<TQuery, TVariables>(query, {
first,
after,
where,
orderBy,
} as TVariables);
const variables = { first, after, where, orderBy } as TVariables;
const result = await data.graphql!.query<TQuery, TVariables>({
query: query,
variables: variables,
});
if (response?.errors?.length) {
throw new Error(response.errors.map((e) => e.message).join("; "));
if (result.errors?.length) {
throw new Error(result.errors.map((e) => e.message).join("; "));
}
const result = (response?.data as Record<string, unknown> | undefined)?.uiapi as
const uiapi = (result.data as Record<string, unknown> | undefined)?.uiapi as
| Record<string, unknown>
| undefined;
const queryResult = (result?.query as Record<string, unknown> | undefined)?.[objectName] as
const queryResult = (uiapi?.query as Record<string, unknown> | undefined)?.[objectName] as
| TResult
| undefined;
@ -56,17 +55,16 @@ export async function fetchDistinctValues<TQuery>(
fieldName: string,
): Promise<PicklistOption[]> {
const data = await createDataSDK();
const response = await data.graphql?.<TQuery>(query);
const errors = response?.errors;
const result = await data.graphql!.query<TQuery>({ query: query });
if (errors?.length) {
throw new Error(errors.map((e) => e.message).join("; "));
if (result.errors?.length) {
throw new Error(result.errors.map((e) => e.message).join("; "));
}
const result = (response?.data as Record<string, unknown> | undefined)?.uiapi as
const uiapi = (result.data as Record<string, unknown> | undefined)?.uiapi as
| Record<string, unknown>
| undefined;
const aggregate = (result?.aggregate as Record<string, unknown> | undefined)?.[objectName] as
const aggregate = (uiapi?.aggregate as Record<string, unknown> | undefined)?.[objectName] as
| { edges?: Array<{ node?: { aggregate?: Record<string, unknown> } }> }
| undefined;

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useState } from "react";
import { Input } from "../../../../components/ui/input";
import { useFilterField } from "../FilterContext";
@ -67,12 +67,16 @@ export function NumericRangeFilterInputs({
const externalMin = value?.min ?? "";
const externalMax = value?.max ?? "";
useEffect(() => {
const [prevExternalMin, setPrevExternalMin] = useState(externalMin);
const [prevExternalMax, setPrevExternalMax] = useState(externalMax);
if (prevExternalMin !== externalMin) {
setPrevExternalMin(externalMin);
setLocalMin(externalMin);
}, [externalMin]);
useEffect(() => {
}
if (prevExternalMax !== externalMax) {
setPrevExternalMax(externalMax);
setLocalMax(externalMax);
}, [externalMax]);
}
const isOutOfBounds = (v: string) => {
if (v === "") return false;

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useState } from "react";
import { SearchBar } from "../SearchBar";
import { useFilterField } from "../FilterContext";
@ -22,9 +22,11 @@ export function SearchFilter({
const [localValue, setLocalValue] = useState(value?.value ?? "");
const externalValue = value?.value ?? "";
useEffect(() => {
const [prevExternalValue, setPrevExternalValue] = useState(externalValue);
if (prevExternalValue !== externalValue) {
setPrevExternalValue(externalValue);
setLocalValue(externalValue);
}, [externalValue]);
}
const debouncedOnChange = useDebouncedCallback((v: string) => {
if (v) {

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useState } from "react";
import { Input } from "../../../../components/ui/input";
import { cn } from "../../../../lib/utils";
import { useFilterField } from "../FilterContext";
@ -62,9 +62,11 @@ export function TextFilterInput({
const [localValue, setLocalValue] = useState(value?.value ?? "");
const externalValue = value?.value ?? "";
useEffect(() => {
const [prevExternalValue, setPrevExternalValue] = useState(externalValue);
if (prevExternalValue !== externalValue) {
setPrevExternalValue(externalValue);
setLocalValue(externalValue);
}, [externalValue]);
}
const debouncedOnChange = useDebouncedCallback((v: string) => {
if (v) {

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
interface UseAsyncDataResult<T> {
data: T | null;
@ -22,18 +22,27 @@ export function useAsyncData<T>(
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [generation, setGeneration] = useState(0);
// Re-create the fetcher reference only when deps change.
// eslint-disable-next-line react-hooks/exhaustive-deps --- deps are explicitly managed by the caller
const memoizedFetcher = useCallback(fetcher, deps);
const fetcherRef = useRef(fetcher);
useEffect(() => {
fetcherRef.current = fetcher;
});
// Detect dep changes during render to reset loading state and bump generation
const [prevDeps, setPrevDeps] = useState(deps);
if (deps.length !== prevDeps.length || deps.some((d, i) => d !== prevDeps[i])) {
setPrevDeps(deps);
setGeneration((g) => g + 1);
if (!loading) setLoading(true);
if (error !== null) setError(null);
}
useEffect(() => {
// Guard against setting state after unmount or dep change.
let cancelled = false;
setLoading(true);
setError(null);
memoizedFetcher()
fetcherRef
.current()
.then((result) => {
if (!cancelled) setData(result);
})
@ -48,7 +57,7 @@ export function useAsyncData<T>(
return () => {
cancelled = true;
};
}, [memoizedFetcher]);
}, [generation]);
return { data, loading, error };
}

View File

@ -1,184 +0,0 @@
import { useCallback, useEffect, useState } from "react";
interface UseAsyncDataResult<T> {
data: T | null;
loading: boolean;
error: string | null;
}
interface CacheOptions {
/** Unique cache key. Used for lookups and invalidation via `clearCacheEntry`. */
key: string;
/** Time-to-live in ms. Default: 30_000 (30s) */
ttl?: number;
/** Max entries in the cache. Default: 50. Evicts oldest entry when exceeded. */
maxSize?: number;
}
interface CacheEntry {
data: unknown;
timestamp: number;
}
/**
* Module-level cache shared across all useCachedAsyncData consumers.
* Cleared automatically on page reload since it only lives in memory.
* Keys are caller-provided so different hook call-sites get independent entries.
*/
const cache = new Map<string, CacheEntry>();
/**
* Returns a cached entry if it exists and hasn't exceeded its TTL.
* Expired entries are deleted lazily here rather than on a timer,
* so there's no background cleanup overhead.
*/
function getValidEntry(key: string, ttl: number): CacheEntry | undefined {
const entry = cache.get(key);
if (!entry) return undefined;
if (Date.now() - entry.timestamp > ttl) {
cache.delete(key);
return undefined;
}
return entry;
}
/**
* Stores a result in the cache. If the cache is at capacity and the key
* is new, the oldest entry (first in Map insertion order FIFO) is evicted.
*/
function setEntry(key: string, data: unknown, maxSize: number): void {
if (!cache.has(key) && cache.size >= maxSize) {
const firstKey = cache.keys().next().value;
if (firstKey !== undefined) cache.delete(firstKey);
}
cache.set(key, { data, timestamp: Date.now() });
}
/**
* Removes a single cache entry by key. The key must match the
* `options.key` passed to `useCachedAsyncData`.
*
* @example
* // Hook:
* useCachedAsyncData(() => fetchAccountDetail(id), [id], { key: `account:${id}` });
*
* // Invalidate after a mutation:
* await updateAccount(id, fields);
* clearCacheEntry(`account:${id}`);
*/
export function clearCacheEntry(key: string): void {
cache.delete(key);
}
/**
* Removes all cache entries. Useful for global invalidation scenarios
* like user logout or after a bulk operation.
*
* @example
* async function handleLogout() {
* await logout();
* clearCache();
* navigate("/login");
* }
*/
export function clearCache(): void {
cache.clear();
}
/**
* Async data hook with in-memory caching. Works like `useAsyncData` but
* avoids redundant network calls when the same data is requested again
* (e.g. navigating away from a page and back).
*
* Cache behaviour:
* - **Key**: Provided via `options.key`. Must be unique per logical data source.
* Use the same key with `clearCacheEntry` to invalidate.
* - **Hit**: Data is returned synchronously on the initial render
* `loading` starts as `false`, so there's no flash of a loading state.
* - **Miss**: The fetcher runs, and the result is stored for future hits.
* - **TTL**: Entries expire after `options.ttl` ms (default 30 s).
* Expiry is checked lazily on read, not on a timer.
* - **Max size**: Oldest entries are evicted FIFO when the cache exceeds
* `options.maxSize` (default 50).
*
* @example
* // Cache picklist options for 5 minutes (data rarely changes)
* const { data: types } = useCachedAsyncData(fetchDistinctTypes, [], {
* key: "distinctTypes",
* ttl: 300_000,
* });
*
* // Cache search results with default 30 s TTL (back-nav returns instantly)
* const { data } = useCachedAsyncData(
* () => searchAccounts({ where, orderBy, first, after }),
* [where, orderBy, first, after],
* { key: `accounts:${JSON.stringify({ where, orderBy, first, after })}` },
* );
*
* // Invalidate a specific entry after a mutation
* await updateAccount(id, fields);
* clearCacheEntry(`account:${id}`);
*
* // Or clear everything (e.g. on logout)
* clearCache();
*/
export function useCachedAsyncData<T>(
fetcher: () => Promise<T>,
deps: React.DependencyList,
options: CacheOptions,
): UseAsyncDataResult<T> {
const ttl = options.ttl ?? 30_000;
const maxSize = options.maxSize ?? 50;
const cacheKey = options.key;
// Synchronous cache check during state initialization so a cache hit
// never triggers a loading → loaded transition (avoids UI flicker).
const cached = getValidEntry(cacheKey, ttl);
const [data, setData] = useState<T | null>((cached?.data as T) ?? null);
const [loading, setLoading] = useState(!cached);
const [error, setError] = useState<string | null>(null);
// eslint-disable-next-line react-hooks/exhaustive-deps --- deps are explicitly managed by the caller
const memoizedFetcher = useCallback(fetcher, deps);
useEffect(() => {
// Re-check the cache inside the effect because deps may have changed
// since the initial render (e.g. StrictMode double-invoke).
const entry = getValidEntry(cacheKey, ttl);
if (entry) {
setData(entry.data as T);
setLoading(false);
setError(null);
return;
}
// No cache hit — fetch from the network.
let cancelled = false;
setLoading(true);
setError(null);
memoizedFetcher()
.then((result) => {
if (!cancelled) {
setEntry(cacheKey, result, maxSize);
setData(result);
}
})
.catch((err) => {
console.error(err);
if (!cancelled) setError(err instanceof Error ? err.message : "An error occurred");
})
.finally(() => {
if (!cancelled) setLoading(false);
});
// Cleanup: if deps change or the component unmounts before the fetch
// completes, the cancelled flag prevents stale state updates.
return () => {
cancelled = true;
};
}, [memoizedFetcher, cacheKey, ttl, maxSize]);
return { data, loading, error };
}

View File

@ -113,7 +113,9 @@ export function useObjectSearchParams<TFilter, TOrderBy>(
// Snapshot ref — lets callbacks read the latest state without being
// recreated on every render (avoids infinite useCallback chains).
const stateRef = useRef({ filters, sort, pageSize, pageIndex });
stateRef.current = { filters, sort, pageSize, pageIndex };
useEffect(() => {
stateRef.current = { filters, sort, pageSize, pageIndex };
});
// Any filter/sort change resets pagination to the first page.
const resetPagination = useCallback(() => {

View File

@ -0,0 +1,67 @@
import { useEffect, useRef, useState } from 'react';
interface UseAsyncDataResult<T> {
data: T | null;
loading: boolean;
error: string | null;
}
/**
* Runs an async fetcher on mount and whenever `deps` change.
* Returns the loading/error/data state. Does not cache every call
* to the fetcher hits the source directly.
*
* A cleanup flag prevents state updates if the component unmounts
* or deps change before the fetch completes (avoids React warnings
* and stale updates from out-of-order responses).
*/
export function useAsyncData<T>(
fetcher: () => Promise<T>,
deps: React.DependencyList
): UseAsyncDataResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [generation, setGeneration] = useState(0);
const fetcherRef = useRef(fetcher);
useEffect(() => {
fetcherRef.current = fetcher;
});
// Detect dep changes during render to reset loading state and bump generation
const [prevDeps, setPrevDeps] = useState(deps);
if (
deps.length !== prevDeps.length ||
deps.some((d, i) => d !== prevDeps[i])
) {
setPrevDeps(deps);
setGeneration(g => g + 1);
if (!loading) setLoading(true);
if (error !== null) setError(null);
}
useEffect(() => {
let cancelled = false;
fetcherRef
.current()
.then(result => {
if (!cancelled) setData(result);
})
.catch(err => {
console.error(err);
if (!cancelled)
setError(err instanceof Error ? err.message : 'An error occurred');
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, [generation]);
return { data, loading, error };
}

View File

@ -5,7 +5,7 @@ import {
searchApplications,
fetchDistinctApplicationStatus,
} from "../api/applications/applicationSearchService";
import { useCachedAsyncData } from "../features/object-search/hooks/useCachedAsyncData";
import { useAsyncData } from "../hooks/useAsyncData";
import {
useObjectSearchParams,
type UseObjectSearchParamsReturn,
@ -72,11 +72,9 @@ export default function ApplicationSearch() {
const [selectedApplication, setSelectedApplication] = useState<ApplicationSearchNode | null>(
null,
);
const [refreshKey, setRefreshKey] = useState(0);
const { data: statusOptions } = useCachedAsyncData(fetchDistinctApplicationStatus, [], {
key: "distinctApplicationStatus",
ttl: 30_000,
});
const { data: statusOptions } = useAsyncData(fetchDistinctApplicationStatus, []);
const { filters, query, pagination, resetAll } = useObjectSearchParams<
Application__C_Filter,
@ -86,8 +84,7 @@ export default function ApplicationSearch() {
() => query.orderBy ?? { CreatedDate: { order: ResultOrder.Desc } },
[query.orderBy],
);
const searchKey = `applications:${JSON.stringify({ where: query.where, orderBy: effectiveOrderBy, first: pagination.pageSize, after: pagination.afterCursor })}`;
const { data, loading, error } = useCachedAsyncData(
const { data, loading, error } = useAsyncData(
() =>
searchApplications({
where: query.where,
@ -95,8 +92,7 @@ export default function ApplicationSearch() {
first: pagination.pageSize,
after: pagination.afterCursor,
}),
[query.where, effectiveOrderBy, pagination.pageSize, pagination.afterCursor],
{ key: searchKey },
[query.where, effectiveOrderBy, pagination.pageSize, pagination.afterCursor, refreshKey],
);
const validApplicationNodes = useMemo(
@ -119,6 +115,7 @@ export default function ApplicationSearch() {
if (selectedApplication?.Id === applicationId) {
setSelectedApplication({ ...selectedApplication, Status__c: { value: status } });
}
setRefreshKey((k) => k + 1);
toast.success("Status updated", {
description: "Application status has been updated successfully.",
});

View File

@ -8,7 +8,7 @@ import {
fetchDistinctMaintenanceRequestPriority,
type MaintenanceRequestSearchNode,
} from "../api/maintenanceRequests/maintenanceRequestSearchService";
import { useCachedAsyncData } from "../features/object-search/hooks/useCachedAsyncData";
import { useAsyncData } from "../hooks/useAsyncData";
import {
useObjectSearchParams,
type UseObjectSearchParamsReturn,
@ -87,19 +87,9 @@ const SORT_CONFIGS: SortFieldConfig<string>[] = [
export default function MaintenanceRequestSearch() {
const [selectedRequest, setSelectedRequest] = useState<MaintenanceRequestSearchNode | null>(null);
const { data: statusOptions } = useCachedAsyncData(fetchDistinctMaintenanceRequestStatus, [], {
key: "distinctMaintenanceRequestStatus",
ttl: 30_000,
});
const { data: typeOptions } = useCachedAsyncData(fetchDistinctMaintenanceRequestType, [], {
key: "distinctMaintenanceRequestType",
ttl: 30_000,
});
const { data: priorityOptions } = useCachedAsyncData(
fetchDistinctMaintenanceRequestPriority,
[],
{ key: "distinctMaintenanceRequestPriority", ttl: 30_000 },
);
const { data: statusOptions } = useAsyncData(fetchDistinctMaintenanceRequestStatus, []);
const { data: typeOptions } = useAsyncData(fetchDistinctMaintenanceRequestType, []);
const { data: priorityOptions } = useAsyncData(fetchDistinctMaintenanceRequestPriority, []);
const { filters, query, pagination, resetAll } = useObjectSearchParams<
Maintenance_Request__C_Filter,
@ -109,8 +99,7 @@ export default function MaintenanceRequestSearch() {
() => query.orderBy ?? { CreatedDate: { order: ResultOrder.Desc } },
[query.orderBy],
);
const searchKey = `maintenance-requests:${JSON.stringify({ where: query.where, orderBy: effectiveOrderBy, first: pagination.pageSize, after: pagination.afterCursor })}`;
const { data, loading, error } = useCachedAsyncData(
const { data, loading, error } = useAsyncData(
() =>
searchMaintenanceRequests({
where: query.where,
@ -119,7 +108,6 @@ export default function MaintenanceRequestSearch() {
after: pagination.afterCursor,
}),
[query.where, effectiveOrderBy, pagination.pageSize, pagination.afterCursor],
{ key: searchKey },
);
const validMaintenanceRequestNodes = useMemo(

View File

@ -5,7 +5,7 @@ import {
fetchDistinctMaintenanceWorkerType,
type MaintenanceWorkerSearchNode,
} from "../api/maintenanceWorkers/maintenanceWorkerSearchService";
import { useCachedAsyncData } from "../features/object-search/hooks/useCachedAsyncData";
import { useAsyncData } from "../hooks/useAsyncData";
import {
useObjectSearchParams,
type UseObjectSearchParamsReturn,
@ -67,18 +67,14 @@ const SORT_CONFIGS: SortFieldConfig<string>[] = [
export default function MaintenanceWorkerSearch() {
const [selectedWorker, setSelectedWorker] = useState<MaintenanceWorkerSearchNode | null>(null);
const { data: typeOptions } = useCachedAsyncData(fetchDistinctMaintenanceWorkerType, [], {
key: "distinctMaintenanceWorkerType",
ttl: 30_000,
});
const { data: typeOptions } = useAsyncData(fetchDistinctMaintenanceWorkerType, []);
const { filters, query, pagination, resetAll } = useObjectSearchParams<
Maintenance_Worker__C_Filter,
Maintenance_Worker__C_OrderBy
>(FILTER_CONFIGS, SORT_CONFIGS, PAGINATION_CONFIG);
const searchKey = `maintenance-workers:${JSON.stringify({ where: query.where, orderBy: query.orderBy, first: pagination.pageSize, after: pagination.afterCursor })}`;
const { data, loading, error } = useCachedAsyncData(
const { data, loading, error } = useAsyncData(
() =>
searchMaintenanceWorkers({
where: query.where,
@ -87,7 +83,6 @@ export default function MaintenanceWorkerSearch() {
after: pagination.afterCursor,
}),
[query.where, query.orderBy, pagination.pageSize, pagination.afterCursor],
{ key: searchKey },
);
const validMaintenanceWorkerNodes = useMemo(

View File

@ -6,7 +6,7 @@ import {
fetchDistinctPropertyType,
type PropertySearchNode,
} from "../api/properties/propertySearchService";
import { useCachedAsyncData } from "../features/object-search/hooks/useCachedAsyncData";
import { useAsyncData } from "../hooks/useAsyncData";
import {
useObjectSearchParams,
type UseObjectSearchParamsReturn,
@ -55,22 +55,15 @@ const PROPERTY_SORT_CONFIGS: SortFieldConfig<string>[] = [
export default function PropertySearch() {
const [selectedProperty, setSelectedProperty] = useState<PropertySearchNode | null>(null);
const { data: statusOptions } = useCachedAsyncData(fetchDistinctPropertyStatus, [], {
key: "distinctPropertyStatus",
ttl: 30_000,
});
const { data: typeOptions } = useCachedAsyncData(fetchDistinctPropertyType, [], {
key: "distinctPropertyType",
ttl: 30_000,
});
const { data: statusOptions } = useAsyncData(fetchDistinctPropertyStatus, []);
const { data: typeOptions } = useAsyncData(fetchDistinctPropertyType, []);
const { filters, query, pagination, resetAll } = useObjectSearchParams<
Property__C_Filter,
Property__C_OrderBy
>(FILTER_CONFIGS, PROPERTY_SORT_CONFIGS, PAGINATION_CONFIG);
const searchKey = `properties:${JSON.stringify({ where: query.where, orderBy: query.orderBy, first: pagination.pageSize, after: pagination.afterCursor })}`;
const { data, loading, error } = useCachedAsyncData(
const { data, loading, error } = useAsyncData(
() =>
searchProperties({
where: query.where,
@ -79,7 +72,6 @@ export default function PropertySearch() {
after: pagination.afterCursor,
}),
[query.where, query.orderBy, pagination.pageSize, pagination.afterCursor],
{ key: searchKey },
);
const validPropertyNodes = useMemo(

View File

@ -1,19 +0,0 @@
/**
* Copyright (c) 2026, Salesforce, Inc.
* All rights reserved.
* For full license text, see the LICENSE.txt file
*/
import AgentforceConversationClient from "../components/AgentforceConversationClient";
export default function TestAccPage() {
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="text-center">
<h1 className="text-4xl font-bold text-gray-900 mb-4">ACC</h1>
<p className="text-lg text-gray-600 mb-8">Welcome to your ACC application.</p>
</div>
<AgentforceConversationClient agentId="0Xx000000000000AAA" />
</div>
);
}

View File

@ -2,7 +2,6 @@ import type { RouteObject } from 'react-router';
import AppLayout from './appLayout';
import Home from './pages/Home';
import NotFound from './pages/NotFound';
import TestAccPage from "./pages/TestAccPage";
import { Navigate } from "react-router";
import PropertySearch from "./pages/PropertySearch";
import MaintenanceRequestSearch from "./pages/MaintenanceRequestSearch";
@ -24,11 +23,6 @@ export const routes: RouteObject[] = [
path: '*',
element: <NotFound />
},
{
path: "test-acc",
element: <TestAccPage />,
handle: { showInNavigation: true, label: "Test ACC" }
},
{
path: "maintenance",
children: [

View File

@ -4,6 +4,11 @@
* For full license text, see the LICENSE.txt file
*/
import type {
AgentforceErrorHandler,
AgentforceReadyHandler,
} from "@salesforce/agentforce-conversation-client";
export interface ResolvedEmbedOptions {
salesforceOrigin?: string;
frontdoorUrl?: string;
@ -32,4 +37,8 @@ export interface AgentforceConversationClientProps {
salesforceOrigin?: string;
/** Optional. If not provided, resolved internally in dev via /__lo/frontdoor. */
frontdoorUrl?: string;
/** Callback invoked when the Lightning Out application is ready. */
onReady?: AgentforceReadyHandler;
/** Callback invoked when a Lightning Out error occurs. */
onError?: AgentforceErrorHandler;
}

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) 2026, Salesforce, Inc.
* All rights reserved.
* For full license text, see the LICENSE.txt file
*/
interface SfdcEnv {
/** Salesforce org force.com URL (e.g., "https://myorg.lightning.force.com"). */
orgUrl?: string;
}
// eslint-disable-next-line no-var -- ambient global declaration
declare var SFDC_ENV: SfdcEnv | undefined;

View File

@ -21,14 +21,16 @@
"noFallthroughCasesInSwitch": true,
/* Path mapping */
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@api/*": ["./src/api/*"],
"@components/*": ["./src/components/*"],
"@utils/*": ["./src/utils/*"],
"@styles/*": ["./src/styles/*"],
"@assets/*": ["./src/assets/*"]
"@assets/*": ["./src/assets/*"],
"@salesforce/platform-sdk": [
"../../../../../../../../../../packages/sdk/platform-sdk/src/index.ts"
]
}
},
"include": [
@ -36,7 +38,8 @@
"e2e",
"vite-env.d.ts",
"vitest-env.d.ts",
"vitest.setup.ts"
"vitest.setup.ts",
"../../../../../../../../../../packages/sdk/platform-sdk/types"
],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -1 +1,4 @@
/// <reference types="vite/client" />
/** Salesforce API version injected at build time by the Vite define plugin. */
declare const __SF_API_VERSION__: string;

View File

@ -3,7 +3,7 @@
"defaultAssignee": "skip",
"assignments": {
"Property_Management_Access": { "assignee": "currentUser" },
"Tenant_Maintenance_Access": { "assignee": "currentUser" }
"Tenant_Maintenance_Access": { "assignee": "skip" }
}
}
}

View File

@ -1,12 +1,12 @@
{
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
"version": "1.120.6",
"version": "10.2.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
"version": "1.120.6",
"version": "10.2.2",
"license": "SEE LICENSE IN LICENSE.txt",
"devDependencies": {
"@lwc/eslint-plugin-lwc": "^3.3.0",

View File

@ -1,6 +1,6 @@
{
"name": "@salesforce/ui-bundle-template-base-sfdx-project",
"version": "1.120.6",
"version": "10.2.2",
"description": "Base SFDX project template",
"license": "SEE LICENSE IN LICENSE.txt",
"publishConfig": {

View File

@ -1,6 +1,9 @@
{
"permsetAssignments": {
"defaultAssignee": "currentUser",
"assignments": {}
"assignments": {
"propertymanagementapp_Access": {
"assignee": "currentUser"
}
}
}
}
}

View File

@ -22,17 +22,18 @@
* Permset assignment config (scripts/org-setup.config.json):
* {
* "permsetAssignments": {
* "defaultAssignee": "currentUser",
* "assignments": {
* "Guest_Permset": { "assignee": "guest@mysite.example.com" },
* "My_Permset": { "assignee": "currentUser" },
* "Guest_Permset": { "assignee": "guestUser", "siteName": "mysite" },
* "Internal_Only": { "assignee": "skip" }
* }
* }
* }
* Assignee values: "currentUser" (default), "skip", or a specific username.
* Assignee values: "currentUser", "skip", "guestUser" (requires siteName), or a specific username.
* Only permsets explicitly listed in assignments are assigned; unlisted permsets are skipped.
*/
import { spawnSync } from 'node:child_process';
import { spawnSync, spawn as nodeSpawn } from 'node:child_process';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { readdirSync, existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
@ -97,9 +98,11 @@ function parseArgs() {
skipLogin: false,
skipDeploy: false,
skipPermset: false,
skipRole: false,
skipData: false,
skipGraphql: false,
skipUIBundleBuild: false,
skipSelfReg: false,
skipDev: false,
};
for (let i = 0; i < args.length; i++) {
@ -112,7 +115,9 @@ function parseArgs() {
} else if (args[i] === '--skip-login') flags.skipLogin = true;
else if (args[i] === '--skip-deploy') flags.skipDeploy = true;
else if (args[i] === '--skip-permset') flags.skipPermset = true;
else if (args[i] === '--skip-role') flags.skipRole = true;
else if (args[i] === '--skip-data') flags.skipData = true;
else if (args[i] === '--skip-self-reg') flags.skipSelfReg = true;
else if (args[i] === '--skip-graphql') flags.skipGraphql = true;
else if (args[i] === '--skip-ui-bundle-build') flags.skipUIBundleBuild = true;
else if (args[i] === '--skip-dev') flags.skipDev = true;
@ -144,15 +149,15 @@ Permset config (scripts/org-setup.config.json):
Control per-permset assignment via a config file. Example:
{
"permsetAssignments": {
"defaultAssignee": "currentUser",
"assignments": {
"Guest_Permset": { "assignee": "guest@mysite.example.com" },
"My_Permset": { "assignee": "currentUser" },
"Guest_Permset": { "assignee": "guestUser", "siteName": "mysite" },
"Internal_Only": { "assignee": "skip" }
}
}
}
Assignee values: "currentUser" (default), "skip", or a specific username.
Without this file, all discovered permsets are assigned to the current user.
Assignee values: "currentUser", "skip", "guestUser" (requires siteName), or a specific username.
Only permsets explicitly listed in assignments are assigned; unlisted permsets are skipped.
`);
process.exit(0);
}
@ -213,31 +218,33 @@ function discoverPermissionSetNames() {
* Config shape:
* {
* "permsetAssignments": {
* "defaultAssignee": "currentUser", // "currentUser" (default) or "skip"
* "assignments": {
* "My_Guest_Permset": { "assignee": "guest@mysite.example.com" },
* "My_Permset": { "assignee": "currentUser" },
* "My_Guest_Permset": { "assignee": "guestUser", "siteName": "mysite" },
* "Internal_Only": { "assignee": "skip" }
* }
* }
* }
*
* Assignee values:
* "currentUser" assign to the user running the script (default)
* "currentUser" assign to the user running the script
* "skip" do not assign this permset
* "guestUser" resolve the site guest user automatically (requires siteName)
* "<username>" assign to a specific user via --on-behalf-of
*
* Returns { defaultAssignee: string, assignments: Record<string, { assignee: string }> }
* Only permsets explicitly listed in assignments are assigned; unlisted permsets are skipped.
*
* Returns { assignments: Record<string, { assignee: string, siteName?: string }> }
*/
function loadPermsetConfig() {
const configPath = resolve(__dirname, 'org-setup.config.json');
const defaults = { defaultAssignee: 'currentUser', assignments: {} };
const defaults = { assignments: {} };
if (!existsSync(configPath)) return defaults;
try {
const raw = JSON.parse(readFileSync(configPath, 'utf8'));
const section = raw?.permsetAssignments;
if (!section) return defaults;
return {
defaultAssignee: section.defaultAssignee || 'currentUser',
assignments: section.assignments || {},
};
} catch (err) {
@ -246,10 +253,375 @@ function loadPermsetConfig() {
}
}
/** Resolve the effective assignee for a given permset name. */
function resolveAssignee(permsetName, permsetConfig) {
/** Resolve the effective assignment config for a given permset name. */
function resolveAssignment(permsetName, permsetConfig) {
const override = permsetConfig.assignments[permsetName];
return override?.assignee || permsetConfig.defaultAssignee;
if (!override) return { assignee: 'skip' };
return { assignee: override.assignee || 'skip', siteName: override.siteName };
}
/**
* Load role assignment config from org-setup.config.json.
*
* Config shape:
* { "role": { "assignee": "currentUser", "roleName": "Admin" } }
*
* Returns null if no "role" section exists in config (the step is hidden).
*/
function loadRoleConfig() {
const configPath = resolve(__dirname, 'org-setup.config.json');
if (!existsSync(configPath)) return null;
try {
const raw = JSON.parse(readFileSync(configPath, 'utf8'));
const section = raw?.role;
if (!section) return null;
return {
assignee: section.assignee || 'currentUser',
roleName: section.roleName || null,
};
} catch {
return null;
}
}
/**
* Load self-registration config from org-setup.config.json.
*
* Config shape:
* {
* "selfRegistration": {
* "siteName": "myapp",
* "selfRegProfile": "myapp Profile",
* "accountName": "My Self-Reg Account"
* }
* }
*
* Returns null if no "selfRegistration" section exists in config (the step is hidden).
*/
function loadSelfRegConfig() {
const configPath = resolve(__dirname, 'org-setup.config.json');
if (!existsSync(configPath)) return null;
try {
const raw = JSON.parse(readFileSync(configPath, 'utf8'));
const section = raw?.selfRegistration;
if (!section) return null;
return {
siteName: section.siteName || null,
selfRegProfile: section.selfRegProfile || null,
accountName: section.accountName || null,
};
} catch {
return null;
}
}
/**
* Ensure the self-registration profile is listed in networkMemberGroups.
* This must happen BEFORE the initial deploy so that the profile is a recognised
* site member when subsequent steps (selfRegProfile, selfRegistration=true) are deployed.
*/
function ensureNetworkMemberProfile(selfRegConfig) {
const { siteName, selfRegProfile } = selfRegConfig;
if (!siteName || !selfRegProfile) return;
const networkXmlPath = resolve(SFDX_SOURCE, 'networks', `${siteName}.network-meta.xml`);
if (!existsSync(networkXmlPath)) {
console.log(` Network metadata not found: ${networkXmlPath}; skipping member group update.`);
return;
}
const xml = readFileSync(networkXmlPath, 'utf8');
// Check if profile is already in networkMemberGroups
const profileEscaped = selfRegProfile.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const profileRegex = new RegExp(`<profile>\\s*${profileEscaped}\\s*</profile>`);
if (profileRegex.test(xml)) {
console.log(` Profile "${selfRegProfile}" already in networkMemberGroups; no update needed.`);
return;
}
// Add the profile to networkMemberGroups
const updatedXml = xml.replace(
/(<networkMemberGroups>)/,
`$1\n <profile>${selfRegProfile}</profile>`
);
writeFileSync(networkXmlPath, updatedXml);
console.log(` Added profile "${selfRegProfile}" to networkMemberGroups in ${siteName}.network-meta.xml`);
}
/**
* Enable self-registration for an Experience Cloud network.
*
* 1. Modify the network metadata XML to set selfRegistration=true and add selfRegProfile.
* 2. Re-deploy the modified network metadata.
* 3. Create an Account record (idempotent).
* 4. Create a NetworkSelfRegistration record linking the Account to the Network (idempotent).
*/
function enableSelfRegistration(selfRegConfig, targetOrg) {
const { siteName, selfRegProfile, accountName } = selfRegConfig;
// 1. Modify network metadata XML
const networkXmlPath = resolve(SFDX_SOURCE, 'networks', `${siteName}.network-meta.xml`);
if (!existsSync(networkXmlPath)) {
console.error(` Network metadata not found: ${networkXmlPath}`);
return;
}
const xml = readFileSync(networkXmlPath, 'utf8');
// Skip network modification and deploy if self-registration is already configured
const alreadyEnabled = /<selfRegistration>true<\/selfRegistration>/.test(xml);
const alreadyHasProfile = /<selfRegProfile>/.test(xml);
if (alreadyEnabled || alreadyHasProfile) {
console.log(` Network "${siteName}" already has self-registration configured; skipping metadata update and deploy.`);
} else {
// Set selfRegistration to true and add selfRegProfile
let updatedXml = xml.replace(
/<selfRegistration>false<\/selfRegistration>/,
'<selfRegistration>true</selfRegistration>'
);
updatedXml = updatedXml.replace(
/(\s*)(<selfRegistration>)/,
`$1<selfRegProfile>${selfRegProfile}</selfRegProfile>\n$1$2`
);
writeFileSync(networkXmlPath, updatedXml);
console.log(` Updated ${siteName}.network-meta.xml: selfRegistration=true, selfRegProfile=${selfRegProfile}`);
// Re-deploy only the network file
const deployResult = spawnSync('sf', [
'project', 'deploy', 'start',
'--target-org', targetOrg,
'--source-dir', networkXmlPath,
], { cwd: ROOT, stdio: 'inherit', shell: true, timeout: 120000 });
if (deployResult.status !== 0) {
console.error(' Failed to deploy updated network metadata.');
process.exit(deployResult.status ?? 1);
}
}
// 3. Create Account (idempotent)
const acctQuery = `SELECT Id FROM Account WHERE Name = '${accountName.replace(/'/g, "\\'")}' LIMIT 1`;
const acctQueryResult = spawnSync('sf', [
'data', 'query',
'--query', acctQuery,
'--target-org', targetOrg,
'--json',
], { cwd: ROOT, encoding: 'utf8' });
let accountId = null;
if (acctQueryResult.status === 0) {
try {
const json = JSON.parse(acctQueryResult.stdout);
accountId = json.result?.records?.[0]?.Id || null;
} catch { /* proceed to create */ }
}
if (accountId) {
console.log(` Account "${accountName}" already exists (${accountId}); skipping creation.`);
} else {
const createResult = spawnSync('sf', [
'data', 'create', 'record',
'--sobject', 'Account',
'--values', `Name='${accountName}'`,
'--target-org', targetOrg,
'--json',
], { cwd: ROOT, encoding: 'utf8' });
if (createResult.status !== 0) {
console.error(` Failed to create Account "${accountName}".`);
if (createResult.stderr) console.error(createResult.stderr);
return;
}
try {
const json = JSON.parse(createResult.stdout);
accountId = json.result?.id;
console.log(` Created Account "${accountName}" (${accountId}).`);
} catch {
console.error(' Failed to parse Account creation result.');
return;
}
}
// 4. Query Network Id
const netQuery = `SELECT Id FROM Network WHERE Name = '${siteName}'`;
const netResult = spawnSync('sf', [
'data', 'query',
'--query', netQuery,
'--target-org', targetOrg,
'--json',
], { cwd: ROOT, encoding: 'utf8' });
let networkId = null;
if (netResult.status === 0) {
try {
const json = JSON.parse(netResult.stdout);
networkId = json.result?.records?.[0]?.Id || null;
} catch { /* fall through */ }
}
if (!networkId) {
console.error(` Could not find Network "${siteName}" in org.`);
return;
}
console.log(` Found Network "${siteName}" (${networkId}).`);
// 5. Create NetworkSelfRegistration (idempotent)
const nsrQuery = `SELECT Id FROM NetworkSelfRegistration WHERE NetworkId = '${networkId}'`;
const nsrResult = spawnSync('sf', [
'data', 'query',
'--query', nsrQuery,
'--target-org', targetOrg,
'--json',
], { cwd: ROOT, encoding: 'utf8' });
let nsrExists = false;
if (nsrResult.status === 0) {
try {
const json = JSON.parse(nsrResult.stdout);
nsrExists = (json.result?.records?.length || 0) > 0;
} catch { /* proceed to create */ }
}
if (nsrExists) {
console.log(' NetworkSelfRegistration record already exists; skipping.');
} else {
const tmpApex = resolve(ROOT, '.tmp-setup-selfreg.apex');
const apex = [
`Account acct = [SELECT Id FROM Account WHERE Id = '${accountId}' LIMIT 1];`,
`NetworkSelfRegistration nsr = new NetworkSelfRegistration();`,
`nsr.AccountId = acct.Id;`,
`nsr.NetworkId = '${networkId}';`,
`insert nsr;`,
`System.debug('NSR_CREATED:' + nsr.Id);`,
].join('\n');
writeFileSync(tmpApex, apex);
const apexResult = spawnSync('sf', [
'apex', 'run', '--target-org', targetOrg, '--file', tmpApex,
], { cwd: ROOT, stdio: 'pipe', shell: true, timeout: 60000 });
const apexOut = apexResult.stdout?.toString() || '';
if (existsSync(tmpApex)) unlinkSync(tmpApex);
if (apexResult.status !== 0 && !apexOut.includes('Compiled successfully')) {
console.error(' Failed to create NetworkSelfRegistration record.');
process.stderr.write(apexResult.stderr?.toString() || apexOut);
return;
}
const nsrMatch = apexOut.match(/NSR_CREATED:(\w+)/);
if (nsrMatch) {
console.log(` Created NetworkSelfRegistration (${nsrMatch[1]}).`);
} else {
console.log(' NetworkSelfRegistration creation executed.');
}
}
}
/**
* Assign a role to the current user so that Experience Cloud self-registration
* works correctly.
*/
function assignRoleToCurrentUser(roleName, targetOrg) {
const roleQuery = `SELECT Id FROM UserRole WHERE Name = '${roleName}'`;
const roleResult = spawnSync('sf', [
'data', 'query',
'--query', roleQuery,
'--target-org', targetOrg,
'--json',
], { cwd: ROOT, encoding: 'utf8' });
if (roleResult.status !== 0) {
console.error(` Failed to query role "${roleName}" in org.`);
if (roleResult.stderr) console.error(roleResult.stderr);
return;
}
let roleId;
try {
const json = JSON.parse(roleResult.stdout);
const records = json.result?.records;
if (!records || records.length === 0) {
console.error(` Role "${roleName}" not found in org; skipping.`);
return;
}
roleId = records[0].Id;
} catch {
console.error(` Failed to parse role query result for "${roleName}".`);
return;
}
const orgResult = spawnSync('sf', [
'org', 'display',
'--target-org', targetOrg,
'--json',
], { cwd: ROOT, encoding: 'utf8' });
if (orgResult.status !== 0) {
console.error(' Failed to resolve current user from org.');
return;
}
let username;
try {
const json = JSON.parse(orgResult.stdout);
username = json.result?.username;
if (!username) {
console.error(' Could not determine current username from org display.');
return;
}
} catch {
console.error(' Failed to parse org display result.');
return;
}
const userQuery = `SELECT Id, UserRoleId FROM User WHERE Username = '${username}'`;
const userResult = spawnSync('sf', [
'data', 'query',
'--query', userQuery,
'--target-org', targetOrg,
'--json',
], { cwd: ROOT, encoding: 'utf8' });
if (userResult.status === 0) {
try {
const json = JSON.parse(userResult.stdout);
const userRecord = json.result?.records?.[0];
if (userRecord?.UserRoleId) {
console.log(` User ${username} already has a role assigned; skipping to avoid overriding.`);
return;
}
} catch { /* continue */ }
}
const updateResult = spawnSync('sf', [
'data', 'update', 'record',
'--sobject', 'User',
'--where', `Username='${username}'`,
'--values', `UserRoleId='${roleId}'`,
'--target-org', targetOrg,
'--json',
], { cwd: ROOT, encoding: 'utf8' });
if (updateResult.status === 0) {
console.log(` Role "${roleName}" assigned to ${username}.`);
} else {
const out = (updateResult.stderr?.toString() || '') + (updateResult.stdout?.toString() || '');
console.error(` Failed to assign role "${roleName}" to ${username}.`);
if (out) console.error(out);
}
}
/**
* Query the org for a guest user whose profile name matches the given site name.
*/
function resolveGuestUsername(siteName, targetOrg) {
const query = `SELECT Username FROM User WHERE Profile.Name LIKE '%${siteName}%' AND UserType = 'Guest'`;
const result = spawnSync('sf', [
'data', 'query',
'--query', query,
'--target-org', targetOrg,
'--json',
], { cwd: ROOT, encoding: 'utf8' });
if (result.status !== 0) {
console.error(` Failed to query guest user for site "${siteName}".`);
if (result.stderr) console.error(result.stderr);
return null;
}
try {
const json = JSON.parse(result.stdout);
const records = json.result?.records;
if (!records || records.length === 0) {
console.error(` No guest user found for site "${siteName}".`);
return null;
}
return records[0].Username;
} catch {
console.error(` Failed to parse guest user query result for site "${siteName}".`);
return null;
}
}
function isOrgConnected(targetOrg) {
@ -312,6 +684,22 @@ async function promptSteps(steps) {
const CYAN = '\x1B[36m';
const GREEN = '\x1B[32m';
/** Strip ANSI escape sequences to get visible character count. */
function visibleLength(str) {
return str.replace(/\x1B\[[0-9;]*m/g, '').length;
}
/** Count how many terminal rows a set of lines occupies (accounting for wrapping). */
function terminalRows(lines) {
const cols = process.stdout.columns || 80;
let rows = 0;
for (const line of lines) {
const len = visibleLength(line);
rows += len === 0 ? 1 : Math.ceil(len / cols);
}
return rows;
}
function render() {
return steps.map((s, i) => {
const ptr = i === cursor ? `${CYAN}${RST}` : ' ';
@ -321,17 +709,23 @@ async function promptSteps(steps) {
});
}
let prevRows = 0;
return new Promise((resolve) => {
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdout.write('\x1B[?25l');
console.log('\nSelect steps (↑↓ move, space toggle, a all, enter confirm):\n');
process.stdout.write(render().join('\n') + '\n');
const initialLines = render();
prevRows = terminalRows(initialLines);
process.stdout.write(initialLines.join('\n') + '\n');
function redraw() {
process.stdout.write(`\x1B[${steps.length}A`);
for (const line of render()) process.stdout.write(`\x1B[2K${line}\n`);
process.stdout.write(`\x1B[${prevRows}A`);
const lines = render();
for (const line of lines) process.stdout.write(`\x1B[2K${line}\n`);
prevRows = terminalRows(lines);
}
process.stdin.on('data', (key) => {
@ -388,6 +782,37 @@ function run(name, cmd, args, opts = {}) {
return result;
}
/** Promise-based spawn for parallel execution. Always uses stdio: 'pipe'. */
function spawnAsync(cmd, args, opts = {}) {
return new Promise((resolve, reject) => {
const proc = nodeSpawn(cmd, args, {
cwd: opts.cwd || ROOT,
stdio: 'pipe',
shell: true,
...(opts.timeout && { timeout: opts.timeout }),
});
let stdout = '';
let stderr = '';
proc.stdout.on('data', (d) => { stdout += d.toString(); });
proc.stderr.on('data', (d) => { stderr += d.toString(); });
proc.on('close', (code) => resolve({ status: code, stdout, stderr }));
proc.on('error', reject);
});
}
/** Async version of run() for parallel steps. Captures output and prints on failure. */
async function runAsync(name, cmd, args, opts = {}) {
const { cwd = ROOT, optional = false } = opts;
const result = await spawnAsync(cmd, args, { cwd, ...(opts.timeout && { timeout: opts.timeout }) });
if (result.status !== 0 && !optional) {
if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);
console.error(`\nSetup failed at step: ${name}`);
process.exit(result.status ?? 1);
}
return result;
}
async function main() {
// Ensure .gitignore files exist (npm strips them from published packages).
const gitignoreTemplates = loadGitignoreTemplates();
@ -410,6 +835,8 @@ async function main() {
skipLogin: argSkipLogin,
skipDeploy: argSkipDeploy,
skipPermset: argSkipPermset,
skipRole: argSkipRole,
skipSelfReg: argSkipSelfReg,
skipData: argSkipData,
skipGraphql: argSkipGraphql,
skipUIBundleBuild: argSkipUIBundleBuild,
@ -426,12 +853,18 @@ async function main() {
: `Permset — assign ${permsetNames.length} permission sets`;
const hasDataPlan = existsSync(DATA_PLAN) && existsSync(DATA_DIR);
const roleConfig = loadRoleConfig();
const hasRoleConfig = roleConfig !== null;
const selfRegConfig = loadSelfRegConfig();
const hasSelfRegConfig = selfRegConfig !== null;
const stepDefs = [
{ key: 'login', label: 'Login — org authentication', enabled: !argSkipLogin, available: true },
{ key: 'uiBundleBuild', label: 'UI Bundle Build — npm install + build (pre-deploy)', enabled: !argSkipUIBundleBuild, available: true },
{ key: 'deploy', label: 'Deploy — sf project deploy start', enabled: !argSkipDeploy, available: true },
{ key: 'permset', label: permsetStepLabel, enabled: !argSkipPermset, available: true },
{ key: 'role', label: `Role — assign "${roleConfig?.roleName ?? '?'}" to current user`, enabled: !argSkipRole && hasRoleConfig, available: hasRoleConfig },
{ key: 'selfReg', label: `Self-Registration — enable for "${selfRegConfig?.siteName ?? '?'}"`, enabled: !argSkipSelfReg && hasSelfRegConfig, available: hasSelfRegConfig },
{ key: 'data', label: 'Data — delete + import records via Apex', enabled: !argSkipData && hasDataPlan, available: hasDataPlan },
{ key: 'graphql', label: 'GraphQL — schema introspect + codegen', enabled: !argSkipGraphql, available: true },
{ key: 'dev', label: 'Dev — launch dev server', enabled: !argSkipDev, available: true },
@ -447,6 +880,8 @@ async function main() {
const skipUIBundleBuild = !on.uiBundleBuild;
const skipDeploy = !on.deploy;
const skipPermset = !on.permset;
const skipRole = !on.role;
const skipSelfReg = !on.selfReg;
const skipData = !on.data;
const skipGraphql = !on.graphql;
const skipDev = !on.dev;
@ -457,10 +892,12 @@ async function main() {
console.log('Setup — target org:', targetOrg, '| UI bundle:', uiBundleDir ?? '(none)');
console.log(
'Steps: login=%s deploy=%s permset=%s data=%s graphql=%s uiBundle=%s dev=%s',
'Steps: login=%s deploy=%s permset=%s role=%s selfReg=%s data=%s graphql=%s uiBundle=%s dev=%s',
!skipLogin,
!skipDeploy,
!skipPermset,
!skipRole,
!skipSelfReg,
doData,
!skipGraphql,
!skipUIBundleBuild,
@ -476,6 +913,13 @@ async function main() {
}
}
// Ensure the self-reg profile is in networkMemberGroups before deploy so that
// subsequent selfRegProfile / selfRegistration updates don't fail.
if (!skipDeploy && selfRegConfig) {
console.log('\n--- Ensure network member profile (pre-deploy) ---');
ensureNetworkMemberProfile(selfRegConfig);
}
// Build all UI Bundles before deploy so dist exists for entity deployment
if (!skipDeploy && !skipUIBundleBuild) {
const allUIBundleDirs = discoverAllUIBundleDirs(uiBundleName);
@ -499,42 +943,84 @@ async function main() {
console.log('No permission sets found under permissionsets/ and none passed via --permset-name; skipping.');
} else {
console.log('\n--- Assign permission sets ---');
// Resolve assignments (guest user lookups etc.) then run all sf assign calls in parallel.
const assignmentJobs = [];
for (const permsetName of permsetNames) {
const assignee = resolveAssignee(permsetName, permsetConfig);
if (assignee === 'skip') {
const assignment = resolveAssignment(permsetName, permsetConfig);
if (assignment.assignee === 'skip') {
console.log(`Permission set "${permsetName}" — skipped (config).`);
continue;
}
const sfArgs = ['org', 'assign', 'permset', '--name', permsetName, '--target-org', targetOrg];
if (assignee !== 'currentUser') {
sfArgs.push('--on-behalf-of', assignee);
let effectiveUsername = null;
if (assignment.assignee === 'guestUser') {
if (!assignment.siteName) {
console.error(`Permission set "${permsetName}" — assignee is "guestUser" but no "siteName" configured; skipping.`);
continue;
}
effectiveUsername = resolveGuestUsername(assignment.siteName, targetOrg);
if (!effectiveUsername) {
console.error(`Permission set "${permsetName}" — could not resolve guest user for site "${assignment.siteName}"; skipping.`);
continue;
}
console.log(` Resolved guest user for site "${assignment.siteName}": ${effectiveUsername}`);
} else if (assignment.assignee !== 'currentUser') {
effectiveUsername = assignment.assignee;
}
const assigneeLabel = assignee === 'currentUser' ? 'current user' : assignee;
const permsetResult = spawnSync('sf', sfArgs, {
cwd: ROOT,
stdio: 'pipe',
shell: true,
});
if (permsetResult.status === 0) {
assignmentJobs.push({ permsetName, effectiveUsername });
}
// Run all permset assignment calls in parallel.
const assignResults = await Promise.all(assignmentJobs.map(async ({ permsetName, effectiveUsername }) => {
const sfArgs = ['org', 'assign', 'permset', '--name', permsetName, '--target-org', targetOrg];
if (effectiveUsername) {
sfArgs.push('--on-behalf-of', effectiveUsername);
}
const assigneeLabel = effectiveUsername || 'current user';
const result = await spawnAsync('sf', sfArgs);
return { permsetName, assigneeLabel, result };
}));
for (const { permsetName, assigneeLabel, result } of assignResults) {
if (result.status === 0) {
console.log(`Permission set "${permsetName}" assigned to ${assigneeLabel}.`);
} else {
const out =
(permsetResult.stderr?.toString() || '') + (permsetResult.stdout?.toString() || '');
const out = (result.stderr || '') + (result.stdout || '');
if (out.includes('Duplicate') && out.includes('PermissionSet')) {
console.log(`Permission set "${permsetName}" already assigned to ${assigneeLabel}; skipping.`);
} else if (out.includes('not found') && out.includes('target org')) {
console.log(`Permission set "${permsetName}" not in org; skipping.`);
} else {
process.stdout.write(permsetResult.stdout?.toString() || '');
process.stderr.write(permsetResult.stderr?.toString() || '');
if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);
console.error(`\nSetup failed at step: Assign permission set (${permsetName})`);
process.exit(permsetResult.status ?? 1);
process.exit(result.status ?? 1);
}
}
}
}
}
if (!skipRole) {
console.log('\n--- Assign role ---');
if (roleConfig?.assignee !== 'currentUser') {
console.error(`Role assignee "${roleConfig?.assignee}" is not supported; only "currentUser" is allowed. Skipping.`);
} else if (!roleConfig?.roleName) {
console.error('Role step enabled but no "roleName" specified in org-setup.config.json; skipping.');
} else {
assignRoleToCurrentUser(roleConfig.roleName, targetOrg);
}
}
if (!skipSelfReg) {
console.log('\n--- Enable self-registration ---');
if (!selfRegConfig?.siteName || !selfRegConfig?.selfRegProfile || !selfRegConfig?.accountName) {
console.error('Self-registration config is incomplete (need siteName, selfRegProfile, accountName); skipping.');
} else {
enableSelfRegistration(selfRegConfig, targetOrg);
}
}
if (doData) {
// Prepare data for uniqueness (run before import so repeat imports don't conflict)
const prepareScript = resolve(__dirname, 'prepare-import-unique-fields.js');
@ -652,17 +1138,15 @@ async function main() {
if (existsSync(tmpApex)) unlinkSync(tmpApex);
}
if (!skipGraphql || !skipUIBundleBuild) {
run('UI Bundle npm install', 'npm', ['install'], { cwd: uiBundleDir });
}
if (!skipGraphql) {
run('UI Bundle npm install', 'npm', ['install'], { cwd: uiBundleDir });
run('Set default org for schema', 'sf', ['config', 'set', 'target-org', targetOrg, '--global']);
run('GraphQL schema (introspect)', 'npm', ['run', 'graphql:schema'], { cwd: uiBundleDir });
run('GraphQL codegen', 'npm', ['run', 'graphql:codegen'], { cwd: uiBundleDir });
}
if (!skipUIBundleBuild) {
run('UI Bundle build (post-codegen)', 'npm', ['run', 'build'], { cwd: uiBundleDir });
} else if (!skipUIBundleBuild && skipDeploy) {
// Only build here if the pre-deploy build didn't already run
run('UI Bundle npm install', 'npm', ['install'], { cwd: uiBundleDir });
run('UI Bundle build', 'npm', ['run', 'build'], { cwd: uiBundleDir });
}

View File

@ -101,5 +101,3 @@ console.log('UI bundle directory:', uiBundleDir);
run('npm install', 'npm', ['install', '--registry', 'https://registry.npmjs.org/'], { cwd: uiBundleDir });
run('npm run build', 'npm', ['run', 'build'], { cwd: uiBundleDir });
console.log('\n--- Launching dev server (Ctrl+C to stop) ---\n');
run('npm run dev', 'npm', ['run', 'dev'], { cwd: uiBundleDir });

View File

@ -8,5 +8,5 @@
"name": "MyProject",
"namespace": "",
"sfdcLoginUrl": "https://login.salesforce.com",
"sourceApiVersion": "66.0"
"sourceApiVersion": "67.0"
}

View File

@ -1 +1 @@
1.120.6
10.2.2

View File

@ -148,9 +148,9 @@ Do not consider a task complete until all three steps have been run successfully
**Before writing any code that connects to Salesforce, you MUST invoke the `using-ui-bundle-salesforce-data` skill. Do not write any data access code without consulting it first.**
This applies to: GraphQL queries/mutations, REST calls, SDK initialization, custom hooks that fetch data, or any code that imports from `@salesforce/sdk-data`.
This applies to: GraphQL queries/mutations, REST calls, SDK initialization, custom hooks that fetch data, or any code that imports from `@salesforce/platform-sdk`.
- **All data access uses the Data SDK** (`@salesforce/sdk-data`) via `createDataSDK()`.
- **All data access uses the Data SDK** (`@salesforce/platform-sdk`) via `createDataSDK()`.
- **Never** use `fetch()` or `axios` directly for Salesforce data.
- **GraphQL is preferred** for record operations (`sdk.graphql`). Use `sdk.fetch` only when GraphQL cannot cover the case (UI API REST, Apex REST, Connect REST, Einstein LLM).
- Use optional chaining: `sdk.graphql?.()`, `sdk.fetch?.()`.
@ -181,7 +181,7 @@ Any endpoint not listed above is not permitted.
6. **HTTP 200 does not mean success** — Salesforce returns HTTP 200 even on failure. Always check the `errors` array in the response body.
#### GraphQL inline queries
Must use the `gql` template tag from `@salesforce/sdk-data` — plain template strings bypass `@graphql-eslint` schema validation. For complex queries, use external `.graphql` files with codegen.
Must use the `gql` template tag from `@salesforce/platform-sdk` — plain template strings bypass `@graphql-eslint` schema validation. For complex queries, use external `.graphql` files with codegen.
#### Current user info
Use GraphQL (`uiapi { currentUser { Id Name { value } } }`), not Chatter (`/chatter/users/me`).

View File

@ -3,6 +3,749 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [10.2.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v10.2.1...v10.2.2) (2026-06-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [10.2.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v10.2.0...v10.2.1) (2026-06-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [10.2.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v10.1.0...v10.2.0) (2026-06-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [10.1.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v10.0.0...v10.1.0) (2026-06-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [10.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.21.0...v10.0.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.21.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.20.1...v9.21.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.20.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.20.0...v9.20.1) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.20.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.19.0...v9.20.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.19.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.18.0...v9.19.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.18.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.17.1...v9.18.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.17.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.17.0...v9.17.1) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.17.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.16.1...v9.17.0) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.16.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.16.0...v9.16.1) (2026-05-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.16.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.15.0...v9.16.0) (2026-05-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.15.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.14.0...v9.15.0) (2026-05-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.14.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.13.0...v9.14.0) (2026-05-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.13.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.12.1...v9.13.0) (2026-05-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.12.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.12.0...v9.12.1) (2026-05-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.12.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.11.0...v9.12.0) (2026-05-27)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.11.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.10.3...v9.11.0) (2026-05-27)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.10.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.10.2...v9.10.3) (2026-05-27)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.10.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.10.1...v9.10.2) (2026-05-27)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.10.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.10.0...v9.10.1) (2026-05-26)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.10.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.9.4...v9.10.0) (2026-05-26)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.9.4](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.9.3...v9.9.4) (2026-05-26)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.9.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.9.2...v9.9.3) (2026-05-25)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.9.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.9.1...v9.9.2) (2026-05-25)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.9.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.9.0...v9.9.1) (2026-05-25)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.9.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.8.2...v9.9.0) (2026-05-22)
### Features
* (template) add customApplication metadata for B2E UIBundle apps @W-22568671 ([#548](https://github.com/salesforce-experience-platform-emu/webapps/issues/548)) ([5d39634](https://github.com/salesforce-experience-platform-emu/webapps/commit/5d396340ee297f6854a91d2b6ca673e438bfb8a6))
## [9.8.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.8.1...v9.8.2) (2026-05-22)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.8.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.8.0...v9.8.1) (2026-05-22)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.8.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.7.0...v9.8.0) (2026-05-22)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.7.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.6.1...v9.7.0) (2026-05-21)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.6.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.6.0...v9.6.1) (2026-05-21)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.6.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.5.0...v9.6.0) (2026-05-21)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.5.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.4.1...v9.5.0) (2026-05-21)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.4.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.4.0...v9.4.1) (2026-05-21)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.4.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.3.0...v9.4.0) (2026-05-20)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.3.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.2.2...v9.3.0) (2026-05-20)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.2.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.2.1...v9.2.2) (2026-05-20)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.2.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.2.0...v9.2.1) (2026-05-20)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.2.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.1.0...v9.2.0) (2026-05-19)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.1.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v9.0.0...v9.1.0) (2026-05-18)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [9.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v8.0.1...v9.0.0) (2026-05-18)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [8.0.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v8.0.0...v8.0.1) (2026-05-18)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [8.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v7.1.0...v8.0.0) (2026-05-15)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [7.1.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v7.0.1...v7.1.0) (2026-05-14)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [7.0.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v7.0.0...v7.0.1) (2026-05-14)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [7.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v6.0.0...v7.0.0) (2026-05-12)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [6.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v5.0.0...v6.0.0) (2026-05-12)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [5.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v4.0.0...v5.0.0) (2026-05-12)
### ⚠ BREAKING CHANGES
* **sdk:** @W-22453506 flip packages/template/** to @salesforce/platform-sdk (#510)
### Features
* **sdk:** @W-22453506 flip packages/template/** to @salesforce/platform-sdk ([#510](https://github.com/salesforce-experience-platform-emu/webapps/issues/510)) ([ab56fab](https://github.com/salesforce-experience-platform-emu/webapps/commit/ab56fab3f4bae239daeaac3f5f29cf31c951be29))
## [4.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v3.1.1...v4.0.0) (2026-05-12)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [3.1.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v3.1.0...v3.1.1) (2026-05-12)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [3.1.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v3.0.0...v3.1.0) (2026-05-12)
### Features
* post tdx changes @W-22390798 ([#500](https://github.com/salesforce-experience-platform-emu/webapps/issues/500)) ([b611d9e](https://github.com/salesforce-experience-platform-emu/webapps/commit/b611d9ecbadd07d052bd7d9a4f5659dec422bf22))
## [3.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v2.2.1...v3.0.0) (2026-05-11)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [2.2.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v2.2.0...v2.2.1) (2026-05-11)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [2.2.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v2.1.0...v2.2.0) (2026-05-08)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [2.1.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v2.0.0...v2.1.0) (2026-05-08)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [2.0.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.135.0...v2.0.0) (2026-05-07)
### ⚠ BREAKING CHANGES
* **sdk:** @W-22351493 rename sdk-data + sdk-analytics to @salesforce/platform-sdk-* (#494)
### Features
* **sdk:** @W-22351493 rename sdk-data + sdk-analytics to @salesforce/platform-sdk-* ([#494](https://github.com/salesforce-experience-platform-emu/webapps/issues/494)) ([725a8f8](https://github.com/salesforce-experience-platform-emu/webapps/commit/725a8f8dba4ce6003b69fb334a36c5131258f161))
## [1.135.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.5...v1.135.0) (2026-05-05)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.134.5](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.4...v1.134.5) (2026-05-04)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.134.4](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.3...v1.134.4) (2026-05-04)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.134.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.2...v1.134.3) (2026-05-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.134.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.1...v1.134.2) (2026-05-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.134.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.134.0...v1.134.1) (2026-05-01)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.134.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.133.2...v1.134.0) (2026-04-30)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.133.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.133.1...v1.133.2) (2026-04-30)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.133.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.133.0...v1.133.1) (2026-04-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.133.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.132.0...v1.133.0) (2026-04-29)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.132.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.131.3...v1.132.0) (2026-04-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.131.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.131.2...v1.131.3) (2026-04-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.131.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.131.1...v1.131.2) (2026-04-28)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.131.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.131.0...v1.131.1) (2026-04-27)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.131.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.130.2...v1.131.0) (2026-04-24)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.130.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.130.1...v1.130.2) (2026-04-24)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.130.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.130.0...v1.130.1) (2026-04-24)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.130.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.129.1...v1.130.0) (2026-04-23)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.129.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.129.0...v1.129.1) (2026-04-23)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.129.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.128.0...v1.129.0) (2026-04-23)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.128.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.127.0...v1.128.0) (2026-04-23)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.127.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.126.0...v1.127.0) (2026-04-22)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.126.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.125.1...v1.126.0) (2026-04-22)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.125.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.125.0...v1.125.1) (2026-04-16)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.125.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.124.0...v1.125.0) (2026-04-15)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.124.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.123.0...v1.124.0) (2026-04-15)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.123.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.122.2...v1.123.0) (2026-04-13)
### Bug Fixes
* **template:** stop auto-starting dev server after sf-project-setup ([#429](https://github.com/salesforce-experience-platform-emu/webapps/issues/429)) ([5e7e7cf](https://github.com/salesforce-experience-platform-emu/webapps/commit/5e7e7cfb9dc1c5c000b1e825307dde9eb847697b))
## [1.122.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.122.1...v1.122.2) (2026-04-13)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.122.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.122.0...v1.122.1) (2026-04-10)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.122.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.121.0...v1.122.0) (2026-04-09)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
# [1.121.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.7...v1.121.0) (2026-04-09)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.120.7](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.6...v1.120.7) (2026-04-08)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.120.6](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.5...v1.120.6) (2026-04-07)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project

View File

@ -42,7 +42,7 @@ force-app/main/default/
├── layouts/ # Page layouts for all 17 custom objects
├── networks/ # Experience Cloud network metadata
├── objects/ # 17 Custom Objects (Property, Tenant, Lease, etc.)
├── permissionsets/ # Access: Property_Management (Admin) & Tenant (Scoped)
├── permissionsets/ # Property_Rental_Guest_User_Access (guest browsing)
├── sites/ # Salesforce Sites configuration
├── triggers/ # Apex Triggers: MaintenanceRequest & Tenant
└── uiBundles/
@ -109,6 +109,8 @@ npm run setup -- --target-org <alias> --yes
| `--skip-data` | Skip data preparation and import |
| `--skip-graphql` | Skip GraphQL schema fetch and codegen |
| `--skip-ui-bundle-build` | Skip `npm install` and UI Bundle build |
| `--skip-role` | Skip role assignment to current user |
| `--skip-self-reg` | Skip Experience Cloud self-registration configuration |
| `--skip-dev` | Do not launch the dev server at the end |
| `--permset-name <name>` | Assign only a specific permission set (repeatable). Default: all sets in the project |
| `--ui-bundle-name <name>` | UI Bundle folder name under `uiBundles/` (default: auto-detected) |
@ -120,6 +122,58 @@ For a full list of options:
npm run setup -- --help
```
### Setup Configuration (`scripts/org-setup.config.json`)
The `npm run setup` script reads `scripts/org-setup.config.json` to control which steps run and how they behave. Each top-level section is **optional** — if a section is absent, the corresponding step is hidden from the interactive picker.
```json
{
"permsetAssignments": {
"assignments": {
"Property_Management_Access": { "assignee": "currentUser" },
"Tenant_Maintenance_Access": { "assignee": "skip" },
"Property_Rental_Guest_User_Access": {
"assignee": "guestUser",
"siteName": "propertyrentalapp"
}
}
},
"role": {
"assignee": "currentUser",
"roleName": "Admin"
},
"selfRegistration": {
"siteName": "propertyrentalapp",
"selfRegProfile": "Property Rental Prospect Profile",
"accountName": "Property Rental Self-Registration"
}
}
```
#### `permsetAssignments.assignments`
Each key is a permission set API name. The `assignee` value controls who it is assigned to:
| Value | Behavior |
| ---------------- | --------------------------------------------------------------------------------- |
| `"currentUser"` | Assigns to the user running the script (resolved via `sf org display`) |
| `"skip"` | Explicitly skips this permission set |
| `"guestUser"` | Auto-resolves the site's guest user (requires `siteName` field on the same entry) |
| `"user@org.com"` | Assigns to a specific user by username |
#### `role`
Assigns a role to the current user — a prerequisite for Experience Cloud self-registration. Only `"currentUser"` is supported as the `assignee`.
#### `selfRegistration`
Enables Experience Cloud self-registration by:
1. Setting `<selfRegistration>true</selfRegistration>` in the network metadata
2. Adding the specified profile to network member groups
3. Creating an Account record for self-registered users
4. Creating the `NetworkSelfRegistration` record
> **After the automated setup completes**, proceed to [Org Configuration](#org-configuration) for the manual steps that cannot be automated via CLI (profile cloning, site member configuration, guest user setup, and publishing the Experience Cloud site).
---
@ -215,12 +269,13 @@ sf project deploy start --source-dir force-app/main/default/uiBundles --target-o
### 4. Assign Permission Sets
Two permission sets are included in this project:
Three permission sets are included in this project:
| Permission Set | Purpose | Assign To |
| ---------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------- |
| `Property_Management_Access` | Full CRUD access to all custom objects. Intended for property managers and admin users. | Internal users managing the app |
| `Tenant_Maintenance_Access` | Scoped read/write access for tenants. Allows creating and updating their own maintenance requests only. | Tenant community users |
| Permission Set | Purpose | Assign To |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| `Property_Management_Access` | Full CRUD access to all custom objects. Intended for property managers and admin users. | Internal users managing the app |
| `Tenant_Maintenance_Access` | Scoped read/write access for tenants. Allows creating and updating their own maintenance requests only. | Tenant community users |
| `Property_Rental_Guest_User_Access` | Read-only access to property objects for unauthenticated browsing. Includes Apex class access for auth flows. | Site guest user |
Assign permission sets using the CLI:
@ -308,9 +363,9 @@ Community users require a profile that is based on one of the standard community
#### 2a. Clone the Base Profile
1. Go to **Setup > Users > Profiles**.
2. Locate either **Customer Community User** or **Customer Community Plus User**.
- Choose **Customer Community Plus User** if you need ownership-based record-level sharing (recommended).
3. Click **Clone** next to the profile. Give it a descriptive name such as `Property Rental Tenant`.
2. Locate either **Customer Community User** or **Customer Community Login User**.
- Choose **Customer Community Plus User** if you need ownership-based record-level sharing.
3. Click **Clone** next to the profile. Give it a descriptive name such as `Property Rental Prospect Profile`.
4. Click **Save**.
#### 2b. Edit the Cloned Profile
@ -398,7 +453,7 @@ After deploying the Experience Cloud site metadata, configure its membership and
#### Members
1. In the left navigation, click **Members**.
2. Under **Select Profiles**, add the cloned community profile (`Property Rental Tenant`) to the **Selected Profiles** list.
2. Under **Select Profiles**, add the cloned community profile (`Property Rental Prospect Profile`) to the **Selected Profiles** list.
3. Click **Save**.
#### Login & Registration
@ -406,7 +461,7 @@ After deploying the Experience Cloud site metadata, configure its membership and
1. In the left navigation, click **Login & Registration**.
2. Check **Allow customers and partners to self-register**.
3. Under **Registration**, set:
- **Profile**: Select your cloned community profile (`Property Rental Tenant`).
- **Profile**: Select your cloned community profile (`Property Rental Prospect Profile`).
- **Account**: Select or create an account record to associate self-registered users with (e.g., a generic "Portal Account").
4. Click **Save**.
@ -457,32 +512,27 @@ Add the following classes. These are required for the self-registration and logi
### Step 5: Create Criteria-Based Sharing Rules for Guest Access
By default, Salesforce does not expose any records to guest users. You must create **criteria-based sharing rules** to make available properties and listings visible to unauthenticated site visitors.
By default, Salesforce does not expose any records to guest users. A **criteria-based sharing rule** is included in the deployed metadata to make properties visible to unauthenticated site visitors.
> **Organization-Wide Defaults (OWD):** Criteria-based sharing rules for guest users only work correctly when the object's OWD is set to **Public Read Only** or **Private**. Verify your OWD settings at **Setup > Sharing Settings** before proceeding.
#### Create a Sharing Rule for Properties
#### Included Sharing Rule: Properties
The deployed metadata includes a sharing rule for `Property__c` (`Property_Rental_App_Guest_User_Access`) that grants **Read** access to the site's guest user for all properties with a non-empty Name field. This is deployed automatically with `sf project deploy start`.
#### Optional: Create Additional Sharing Rules
If you need to expose additional objects (e.g., Property Listings) to guest users, create sharing rules manually:
1. Go to **Setup > Sharing Settings**.
2. Scroll to the **Property Sharing Rules** section and click **New**.
2. Scroll to the relevant object's sharing rules section and click **New**.
3. Configure the rule:
- **Label**: `Available Properties for Guest Users`
- **Rule Type**: Select **Guest user access, based on criteria**
- **Criteria**: `Status Equals Available` (adjust the field and value to match your data model)
- **Share with**: Select **Guests of the Property Rental App site** (the guest user group for your Experience Cloud site)
- **Criteria**: Match records you want to expose (e.g., `Status Equals Active`)
- **Share with**: Select **Guests of the Property Rental App site**
- **Access Level**: `Read Only`
4. Click **Save**.
#### Create a Sharing Rule for Property Listings
Repeat the same steps in the **Property Listing Sharing Rules** section:
- **Label**: `Available Property Listings for Guest Users`
- **Rule Type**: `Guest user access, based on criteria`
- **Criteria**: Match listings you want to expose (e.g., `Status Equals Active`)
- **Share with**: `Guests of the Property Rental App site`
- **Access Level**: `Read Only`
> **Note:** The "Guests of [site name]" option only appears in the sharing rule target list after the Experience Cloud site has been created and saved. If you do not see it, confirm the site metadata has been deployed and the site exists in the org.
---

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomApplication xmlns="http://soap.sforce.com/2006/04/metadata">
<defaultLandingTab>standard-home</defaultLandingTab>
<description>Property Rental Management Application for managing properties, tenants, leases, payments, and maintenance.</description>
<formFactors>Large</formFactors>
<isNavAutoTempTabsDisabled>false</isNavAutoTempTabsDisabled>
<isNavPersonalizationDisabled>false</isNavPersonalizationDisabled>
<isNavTabPersistenceDisabled>false</isNavTabPersistenceDisabled>
<isOmniPinnedViewEnabled>false</isOmniPinnedViewEnabled>
<label>Property Management</label>
<navType>Standard</navType>
<tabs>standard-home</tabs>
<tabs>Property__c</tabs>
<tabs>Property_Listing__c</tabs>
<tabs>Tenant__c</tabs>
<tabs>Lease__c</tabs>
<tabs>Payment__c</tabs>
<tabs>Maintenance_Request__c</tabs>
<tabs>Maintenance_Worker__c</tabs>
<tabs>Application__c</tabs>
<tabs>Property_Owner__c</tabs>
<tabs>Property_Management_Company__c</tabs>
<tabs>Property_Cost__c</tabs>
<tabs>Property_Feature__c</tabs>
<tabs>Property_Image__c</tabs>
<tabs>Property_Sale__c</tabs>
<tabs>KPI_Snapshot__c</tabs>
<tabs>Notification__c</tabs>
<tabs>Agent__c</tabs>
<tabs>standard-report</tabs>
<tabs>standard-Dashboard</tabs>
<uiType>Lightning</uiType>
</CustomApplication>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<apiVersion>67.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -6,7 +6,7 @@
"referenceId": "ApplicationRef1"
},
"Property__c": "@PropertyRef5",
"User__c": "@ContactRef1",
"User__c": null,
"Status__c": "Submitted",
"Start_Date__c": "2024-12-01",
"Employment__c": "TechCorp Inc. - Senior Software Engineer - $95,000/year",
@ -18,7 +18,7 @@
"referenceId": "ApplicationRef2"
},
"Property__c": "@PropertyRef8",
"User__c": "@ContactRef2",
"User__c": null,
"Status__c": "Background Check",
"Start_Date__c": "2024-12-15",
"Employment__c": "City Hospital - Registered Nurse - $72,000/year",
@ -30,7 +30,7 @@
"referenceId": "ApplicationRef3"
},
"Property__c": "@PropertyRef12",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Approved",
"Start_Date__c": "2024-11-01",
"Employment__c": "Global Consulting LLC - Management Consultant - $110,000/year",
@ -42,7 +42,7 @@
"referenceId": "ApplicationRef4"
},
"Property__c": "@PropertyRef5",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Rejected",
"Start_Date__c": "2024-12-01",
"Employment__c": "Retail Store - Assistant Manager - $32,000/year",
@ -54,7 +54,7 @@
"referenceId": "ApplicationRef5"
},
"Property__c": "@PropertyRef16",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Draft",
"Start_Date__c": "2025-01-15",
"Employment__c": "University - Graduate Student - $18,000/year stipend",
@ -66,7 +66,7 @@
"referenceId": "ApplicationRef6"
},
"Property__c": "@PropertyRef20",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Submitted",
"Start_Date__c": "2025-01-01",
"Employment__c": "Local Business - Marketing Coordinator - $48,000/year (Co-signer: Parent, $85,000/year)",
@ -78,7 +78,7 @@
"referenceId": "ApplicationRef7"
},
"Property__c": "@PropertyRef8",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Background Check",
"Start_Date__c": "2024-12-20",
"Employment__c": "Finance Corp - Financial Analyst - $78,000/year",
@ -90,7 +90,7 @@
"referenceId": "ApplicationRef8"
},
"Property__c": "@PropertyRef3",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Approved",
"Start_Date__c": "2024-10-01",
"Employment__c": "Design Studio - UX Designer - $68,000/year",
@ -102,7 +102,7 @@
"referenceId": "ApplicationRef9"
},
"Property__c": "@PropertyRef12",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Rejected",
"Start_Date__c": "2024-10-25",
"Employment__c": "Various part-time positions - $25,000/year",
@ -114,7 +114,7 @@
"referenceId": "ApplicationRef10"
},
"Property__c": "@PropertyRef5",
"User__c": "@ContactRef3",
"User__c": null,
"Status__c": "Submitted",
"Start_Date__c": "2024-12-10",
"Employment__c": "Teaching - High School Teacher - $55,000/year",

View File

@ -1,44 +0,0 @@
{
"records": [
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef1"
},
"FirstName": "Jennifer",
"LastName": "Anderson",
"Email": "jennifer.anderson@example.com",
"Phone": "(555) 123-4567"
},
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef2"
},
"FirstName": "David",
"LastName": "Rodriguez",
"Email": "david.rodriguez@example.com",
"Phone": "(555) 234-5678"
},
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef3"
},
"FirstName": "Maria",
"LastName": "Garcia",
"Email": "maria.garcia@example.com",
"Phone": "(555) 345-6789"
},
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef4"
},
"FirstName": "James",
"LastName": "Brown",
"Email": "james.brown@example.com",
"Phone": "(555) 456-7890"
}
]
}

View File

@ -1,10 +1,4 @@
[
{
"sobject": "Contact",
"saveRefs": true,
"resolveRefs": false,
"files": ["Contact.json"]
},
{
"sobject": "Agent__c",
"saveRefs": true,

View File

@ -2,10 +2,10 @@
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>User__c</fullName>
<deleteConstraint>SetNull</deleteConstraint>
<description>The person applying (Contact record)</description>
<description>The person applying (User record)</description>
<externalId>false</externalId>
<label>Applicant</label>
<referenceTo>Contact</referenceTo>
<referenceTo>User</referenceTo>
<relationshipLabel>Applications</relationshipLabel>
<relationshipName>Applications</relationshipName>
<required>false</required>

View File

@ -53,7 +53,7 @@
<enableSearch>true</enableSearch>
<enableSharing>true</enableSharing>
<enableStreamingApi>true</enableStreamingApi>
<externalSharingModel>Private</externalSharingModel>
<externalSharingModel>Read</externalSharingModel>
<label>Property</label>
<nameField>
<label>Property Name</label>

View File

@ -16,5 +16,5 @@
<members>propertyrentalapp</members>
<name>Network</name>
</types>
<version>66.0</version>
<version>67.0</version>
</Package>

View File

@ -630,4 +630,80 @@
<field>Tenant__c.Status__c</field>
<readable>true</readable>
</fieldPermissions>
<!-- Application Permissions -->
<applicationVisibilities>
<application>PropertyManagement</application>
<visible>true</visible>
</applicationVisibilities>
<!-- Tab Permissions -->
<tabSettings>
<tab>Agent__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Application__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>KPI_Snapshot__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Lease__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Maintenance_Request__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Maintenance_Worker__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Notification__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Payment__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Cost__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Feature__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Image__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Listing__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Management_Company__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Owner__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Property_Sale__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Tenant__c</tab>
<visibility>Visible</visibility>
</tabSettings>
</PermissionSet>

View File

@ -0,0 +1,217 @@
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<label>Property Rental Guest User Access</label>
<hasActivationRequired>false</hasActivationRequired>
<classAccesses>
<apexClass>UIBundleForgotPassword</apexClass>
<enabled>true</enabled>
</classAccesses>
<classAccesses>
<apexClass>UIBundleLogin</apexClass>
<enabled>true</enabled>
</classAccesses>
<classAccesses>
<apexClass>UIBundleRegistration</apexClass>
<enabled>true</enabled>
</classAccesses>
<fieldPermissions>
<editable>false</editable>
<field>Property_Cost__c.Description__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property_Cost__c.Vendor__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property_Feature__c.Description__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property_Feature__c.Display_on_Listing__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property_Image__c.Alt_Text__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property_Image__c.Display_Order__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property_Listing__c.Display_Order__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property_Listing__c.Featured__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property_Listing__c.Marketing_Description__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property_Listing__c.Short_Description__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Address__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Agent__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Available_Date__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Bathrooms__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Bedrooms__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Coordinates__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Deposit__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Description__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Features__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Hero_Image__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Lease_Term__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Monthly_Rent__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Parking__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Pet_Friendly__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Sq_Ft__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Status__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Tour_URL__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Type__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Utilities__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>Property__c.Year_Built__c</field>
<readable>true</readable>
</fieldPermissions>
<objectPermissions>
<allowCreate>false</allowCreate>
<allowDelete>false</allowDelete>
<allowEdit>false</allowEdit>
<allowRead>true</allowRead>
<modifyAllRecords>false</modifyAllRecords>
<object>Property_Cost__c</object>
<viewAllFields>false</viewAllFields>
<viewAllRecords>false</viewAllRecords>
</objectPermissions>
<objectPermissions>
<allowCreate>false</allowCreate>
<allowDelete>false</allowDelete>
<allowEdit>false</allowEdit>
<allowRead>true</allowRead>
<modifyAllRecords>false</modifyAllRecords>
<object>Property_Feature__c</object>
<viewAllFields>false</viewAllFields>
<viewAllRecords>false</viewAllRecords>
</objectPermissions>
<objectPermissions>
<allowCreate>false</allowCreate>
<allowDelete>false</allowDelete>
<allowEdit>false</allowEdit>
<allowRead>true</allowRead>
<modifyAllRecords>false</modifyAllRecords>
<object>Property_Image__c</object>
<viewAllFields>false</viewAllFields>
<viewAllRecords>false</viewAllRecords>
</objectPermissions>
<objectPermissions>
<allowCreate>false</allowCreate>
<allowDelete>false</allowDelete>
<allowEdit>false</allowEdit>
<allowRead>true</allowRead>
<modifyAllRecords>false</modifyAllRecords>
<object>Property_Listing__c</object>
<viewAllFields>false</viewAllFields>
<viewAllRecords>false</viewAllRecords>
</objectPermissions>
<objectPermissions>
<allowCreate>false</allowCreate>
<allowDelete>false</allowDelete>
<allowEdit>false</allowEdit>
<allowRead>true</allowRead>
<modifyAllRecords>false</modifyAllRecords>
<object>Property__c</object>
<viewAllFields>false</viewAllFields>
<viewAllRecords>false</viewAllRecords>
</objectPermissions>
</PermissionSet>

View File

@ -36,7 +36,7 @@
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<editable>true</editable>
<field>Maintenance_Request__c.Property__c</field>
<readable>true</readable>
</fieldPermissions>

Some files were not shown because too many files have changed in this diff Show More