@salesforce/b2c-cli 2.3.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/bin/dev.js +8 -8
  2. package/bin/run.js +5 -7
  3. package/content/guidance/b2c/b2c-business-manager-extensions/SKILL.md +361 -0
  4. package/content/guidance/b2c/b2c-business-manager-extensions/references/EXTENSIONS-XML.md +458 -0
  5. package/content/guidance/b2c/b2c-controllers/SKILL.md +301 -0
  6. package/content/guidance/b2c/b2c-controllers/references/CLASSIC-PATTERNS.md +335 -0
  7. package/content/guidance/b2c/b2c-controllers/references/SFRA-PATTERNS.md +400 -0
  8. package/content/guidance/b2c/b2c-custom-api-development/SKILL.md +281 -0
  9. package/content/guidance/b2c/b2c-custom-api-development/references/CONTRACT.md +142 -0
  10. package/content/guidance/b2c/b2c-custom-api-development/references/IMPLEMENTATION.md +153 -0
  11. package/content/guidance/b2c/b2c-custom-api-development/references/TESTING.md +118 -0
  12. package/content/guidance/b2c/b2c-custom-caches/SKILL.md +279 -0
  13. package/content/guidance/b2c/b2c-custom-job-steps/SKILL.md +520 -0
  14. package/content/guidance/b2c/b2c-custom-job-steps/references/CHUNK-ORIENTED.md +377 -0
  15. package/content/guidance/b2c/b2c-custom-job-steps/references/JOBS-XML.md +212 -0
  16. package/content/guidance/b2c/b2c-custom-job-steps/references/STEPTYPES-JSON.md +373 -0
  17. package/content/guidance/b2c/b2c-custom-job-steps/references/TASK-ORIENTED.md +344 -0
  18. package/content/guidance/b2c/b2c-custom-objects/SKILL.md +327 -0
  19. package/content/guidance/b2c/b2c-custom-objects/references/OCAPI-SEARCH.md +298 -0
  20. package/content/guidance/b2c/b2c-forms/SKILL.md +242 -0
  21. package/content/guidance/b2c/b2c-forms/references/FORM-XML.md +409 -0
  22. package/content/guidance/b2c/b2c-hooks/SKILL.md +500 -0
  23. package/content/guidance/b2c/b2c-hooks/references/OCAPI-SCAPI-HOOKS.md +403 -0
  24. package/content/guidance/b2c/b2c-hooks/references/ORDER-HOOK-LIFECYCLE.md +169 -0
  25. package/content/guidance/b2c/b2c-hooks/references/SYSTEM-HOOKS.md +433 -0
  26. package/content/guidance/b2c/b2c-isml/SKILL.md +320 -0
  27. package/content/guidance/b2c/b2c-isml/references/EXPRESSIONS.md +366 -0
  28. package/content/guidance/b2c/b2c-isml/references/TAGS.md +443 -0
  29. package/content/guidance/b2c/b2c-localization/SKILL.md +344 -0
  30. package/content/guidance/b2c/b2c-localization/references/PATTERNS.md +407 -0
  31. package/content/guidance/b2c/b2c-logging/SKILL.md +352 -0
  32. package/content/guidance/b2c/b2c-logging/references/LOG-FILES.md +282 -0
  33. package/content/guidance/b2c/b2c-metadata/SKILL.md +406 -0
  34. package/content/guidance/b2c/b2c-metadata/references/SYSTEM-OBJECTS.md +320 -0
  35. package/content/guidance/b2c/b2c-metadata/references/XML-EXAMPLES.md +366 -0
  36. package/content/guidance/b2c/b2c-onboarding/SKILL.md +154 -0
  37. package/content/guidance/b2c/b2c-ordering/SKILL.md +391 -0
  38. package/content/guidance/b2c/b2c-page-designer/SKILL.md +410 -0
  39. package/content/guidance/b2c/b2c-page-designer/references/ATTRIBUTE-TYPES.md +436 -0
  40. package/content/guidance/b2c/b2c-page-designer/references/META-DEFINITIONS.md +340 -0
  41. package/content/guidance/b2c/b2c-querying-data/SKILL.md +289 -0
  42. package/content/guidance/b2c/b2c-querying-data/references/PERFORMANCE-APIS.md +74 -0
  43. package/content/guidance/b2c/b2c-scapi-admin/SKILL.md +53 -0
  44. package/content/guidance/b2c/b2c-scapi-admin/references/CLIENT-EXAMPLES.md +440 -0
  45. package/content/guidance/b2c/b2c-scapi-admin/references/INTEGRATION-PATTERNS.md +518 -0
  46. package/content/guidance/b2c/b2c-scapi-admin/references/OAUTH-SCOPES.md +337 -0
  47. package/content/guidance/b2c/b2c-scapi-shopper/SKILL.md +56 -0
  48. package/content/guidance/b2c/b2c-scapi-shopper/references/CHECKOUT-FLOW.md +466 -0
  49. package/content/guidance/b2c/b2c-scapi-shopper/references/CLIENT-EXAMPLES.md +351 -0
  50. package/content/guidance/b2c/b2c-scapi-shopper/references/COMMON-PATTERNS.md +390 -0
  51. package/content/guidance/b2c/b2c-scapi-shopper/references/SCOPES.md +290 -0
  52. package/content/guidance/b2c/b2c-slas-auth-patterns/SKILL.md +420 -0
  53. package/content/guidance/b2c/b2c-slas-auth-patterns/references/PASSKEYS.md +126 -0
  54. package/content/guidance/b2c/b2c-slas-auth-patterns/references/SESSION-BRIDGE.md +267 -0
  55. package/content/guidance/b2c/b2c-slas-auth-patterns/references/TOKEN-LIFECYCLE.md +367 -0
  56. package/content/guidance/b2c/b2c-webservices/SKILL.md +318 -0
  57. package/content/guidance/b2c/b2c-webservices/references/FTP-SERVICES.md +524 -0
  58. package/content/guidance/b2c/b2c-webservices/references/HTTP-SERVICES.md +578 -0
  59. package/content/guidance/b2c/b2c-webservices/references/SERVICES-XML.md +351 -0
  60. package/content/guidance/b2c/b2c-webservices/references/SOAP-SERVICES.md +587 -0
  61. package/content/guidance/b2c-cli/b2c-am/SKILL.md +277 -0
  62. package/content/guidance/b2c-cli/b2c-bm-users-roles/SKILL.md +210 -0
  63. package/content/guidance/b2c-cli/b2c-cap/SKILL.md +131 -0
  64. package/content/guidance/b2c-cli/b2c-cip/SKILL.md +116 -0
  65. package/content/guidance/b2c-cli/b2c-cip/references/KNOWN_TABLES.md +105 -0
  66. package/content/guidance/b2c-cli/b2c-cip/references/SALES_ANALYSIS.md +50 -0
  67. package/content/guidance/b2c-cli/b2c-cip/references/STARTER_QUERIES.md +147 -0
  68. package/content/guidance/b2c-cli/b2c-code/SKILL.md +146 -0
  69. package/content/guidance/b2c-cli/b2c-config/SKILL.md +481 -0
  70. package/content/guidance/b2c-cli/b2c-content/SKILL.md +176 -0
  71. package/content/guidance/b2c-cli/b2c-debug/SKILL.md +138 -0
  72. package/content/guidance/b2c-cli/b2c-docs/SKILL.md +301 -0
  73. package/content/guidance/b2c-cli/b2c-ecdn/SKILL.md +137 -0
  74. package/content/guidance/b2c-cli/b2c-ecdn/references/ADVANCED.md +97 -0
  75. package/content/guidance/b2c-cli/b2c-ecdn/references/SECURITY.md +75 -0
  76. package/content/guidance/b2c-cli/b2c-import-set-migrations/SKILL.md +265 -0
  77. package/content/guidance/b2c-cli/b2c-job/SKILL.md +70 -0
  78. package/content/guidance/b2c-cli/b2c-job/references/EXPORT.md +121 -0
  79. package/content/guidance/b2c-cli/b2c-job/references/IMPORT.md +55 -0
  80. package/content/guidance/b2c-cli/b2c-job/references/RUN-AND-MONITOR.md +148 -0
  81. package/content/guidance/b2c-cli/b2c-logs/SKILL.md +249 -0
  82. package/content/guidance/b2c-cli/b2c-metrics/SKILL.md +315 -0
  83. package/content/guidance/b2c-cli/b2c-mrt/SKILL.md +207 -0
  84. package/content/guidance/b2c-cli/b2c-mrt/references/BUNDLE-COMMANDS.md +203 -0
  85. package/content/guidance/b2c-cli/b2c-mrt/references/ENVIRONMENT-COMMANDS.md +218 -0
  86. package/content/guidance/b2c-cli/b2c-mrt/references/PROJECT-COMMANDS.md +154 -0
  87. package/content/guidance/b2c-cli/b2c-sandbox/SKILL.md +112 -0
  88. package/content/guidance/b2c-cli/b2c-scapi-custom/SKILL.md +126 -0
  89. package/content/guidance/b2c-cli/b2c-scapi-schemas/SKILL.md +70 -0
  90. package/content/guidance/b2c-cli/b2c-scapi-schemas/references/CLI-EXAMPLES.md +108 -0
  91. package/content/guidance/b2c-cli/b2c-site-import-export/SKILL.md +57 -0
  92. package/content/guidance/b2c-cli/b2c-site-import-export/references/IMPORT-OPTIONS.md +118 -0
  93. package/content/guidance/b2c-cli/b2c-site-import-export/references/METADATA-XML.md +381 -0
  94. package/content/guidance/b2c-cli/b2c-site-import-export/references/WORKFLOWS.md +182 -0
  95. package/content/guidance/b2c-cli/b2c-sites/SKILL.md +112 -0
  96. package/content/guidance/b2c-cli/b2c-slas/SKILL.md +182 -0
  97. package/content/guidance/b2c-cli/b2c-webdav/SKILL.md +186 -0
  98. package/content/guidance/b2c-ops/b2c-checkout-triage/SKILL.md +68 -0
  99. package/content/guidance/b2c-ops/b2c-edge-traffic-triage/SKILL.md +69 -0
  100. package/content/guidance/b2c-ops/b2c-job-health/SKILL.md +80 -0
  101. package/content/guidance/b2c-ops/b2c-job-health/references/job-logs.md +21 -0
  102. package/content/guidance/b2c-ops/b2c-order-failure-triage/SKILL.md +66 -0
  103. package/content/guidance/b2c-ops/b2c-order-failure-triage/references/order-evidence.md +87 -0
  104. package/content/guidance/b2c-ops/b2c-production-triage/SKILL.md +88 -0
  105. package/content/guidance/b2c-ops/b2c-production-triage/references/escalation.md +57 -0
  106. package/content/guidance/index.json +1969 -0
  107. package/content/guidance/storefront-next/sfnext-accessibility/SKILL.md +103 -0
  108. package/content/guidance/storefront-next/sfnext-accessibility/references/checklist.md +64 -0
  109. package/content/guidance/storefront-next/sfnext-analytics-consent/SKILL.md +91 -0
  110. package/content/guidance/storefront-next/sfnext-analytics-consent/references/CUSTOM-ADAPTER.md +49 -0
  111. package/content/guidance/storefront-next/sfnext-authentication/SKILL.md +127 -0
  112. package/content/guidance/storefront-next/sfnext-authentication/references/COOKIES.md +39 -0
  113. package/content/guidance/storefront-next/sfnext-authentication/references/LOGIN-FLOWS.md +59 -0
  114. package/content/guidance/storefront-next/sfnext-commerce-features/SKILL.md +67 -0
  115. package/content/guidance/storefront-next/sfnext-commerce-features/references/FEATURE-PREREQUISITES.md +37 -0
  116. package/content/guidance/storefront-next/sfnext-components/SKILL.md +153 -0
  117. package/content/guidance/storefront-next/sfnext-components/references/COMPONENT-AUTHORING.md +118 -0
  118. package/content/guidance/storefront-next/sfnext-components/references/SHAPE-TOKENS.md +51 -0
  119. package/content/guidance/storefront-next/sfnext-components/references/STORYBOOK.md +54 -0
  120. package/content/guidance/storefront-next/sfnext-components/references/TOKEN-SYSTEM.md +53 -0
  121. package/content/guidance/storefront-next/sfnext-components/references/TROUBLESHOOTING.md +19 -0
  122. package/content/guidance/storefront-next/sfnext-configuration/SKILL.md +163 -0
  123. package/content/guidance/storefront-next/sfnext-configuration/references/ENV-VARIABLES.md +55 -0
  124. package/content/guidance/storefront-next/sfnext-configuration/references/MULTI-SITE-URLS.md +137 -0
  125. package/content/guidance/storefront-next/sfnext-data-fetching/SKILL.md +140 -0
  126. package/content/guidance/storefront-next/sfnext-data-fetching/references/ACTIONS.md +53 -0
  127. package/content/guidance/storefront-next/sfnext-data-fetching/references/API-CLIENTS.md +34 -0
  128. package/content/guidance/storefront-next/sfnext-data-fetching/references/LOADERS.md +73 -0
  129. package/content/guidance/storefront-next/sfnext-data-fetching/references/SCAPI-FETCHER.md +39 -0
  130. package/content/guidance/storefront-next/sfnext-deployment/SKILL.md +127 -0
  131. package/content/guidance/storefront-next/sfnext-deployment/references/MRT-DEPLOYMENT.md +59 -0
  132. package/content/guidance/storefront-next/sfnext-extensions/SKILL.md +118 -0
  133. package/content/guidance/storefront-next/sfnext-extensions/references/ACTION-HOOKS.md +57 -0
  134. package/content/guidance/storefront-next/sfnext-extensions/references/BASE-AUDIT.md +68 -0
  135. package/content/guidance/storefront-next/sfnext-extensions/references/CLI-AND-INSTALL.md +58 -0
  136. package/content/guidance/storefront-next/sfnext-extensions/references/EXTENSION-EXAMPLES.md +79 -0
  137. package/content/guidance/storefront-next/sfnext-hybrid-storefronts/SKILL.md +93 -0
  138. package/content/guidance/storefront-next/sfnext-hybrid-storefronts/references/HYBRID-PROXY-CONFIG.md +86 -0
  139. package/content/guidance/storefront-next/sfnext-i18n/SKILL.md +152 -0
  140. package/content/guidance/storefront-next/sfnext-i18n/references/locale-config.md +35 -0
  141. package/content/guidance/storefront-next/sfnext-overview/SKILL.md +101 -0
  142. package/content/guidance/storefront-next/sfnext-page-designer/SKILL.md +202 -0
  143. package/content/guidance/storefront-next/sfnext-page-designer/references/BUSINESS-MANAGER.md +46 -0
  144. package/content/guidance/storefront-next/sfnext-page-designer/references/COMPONENT-REGISTRY.md +111 -0
  145. package/content/guidance/storefront-next/sfnext-page-designer/references/DECORATOR-PATTERNS.md +168 -0
  146. package/content/guidance/storefront-next/sfnext-page-designer/references/REVIEW-CHECKLIST.md +83 -0
  147. package/content/guidance/storefront-next/sfnext-page-designer/references/TROUBLESHOOTING.md +35 -0
  148. package/content/guidance/storefront-next/sfnext-performance/SKILL.md +102 -0
  149. package/content/guidance/storefront-next/sfnext-performance/references/PERFORMANCE-REVIEW-CHECKLIST.md +85 -0
  150. package/content/guidance/storefront-next/sfnext-performance/references/SUSPENSE-AND-STREAMING.md +58 -0
  151. package/content/guidance/storefront-next/sfnext-project-setup/SKILL.md +147 -0
  152. package/content/guidance/storefront-next/sfnext-project-setup/references/PROJECT-STRUCTURE.md +61 -0
  153. package/content/guidance/storefront-next/sfnext-project-setup/references/SCRIPTS.md +47 -0
  154. package/content/guidance/storefront-next/sfnext-project-setup/references/SFNEXT-CLI.md +63 -0
  155. package/content/guidance/storefront-next/sfnext-quality-gates/SKILL.md +70 -0
  156. package/content/guidance/storefront-next/sfnext-quality-gates/references/lint-and-budgets.md +39 -0
  157. package/content/guidance/storefront-next/sfnext-revalidation/SKILL.md +121 -0
  158. package/content/guidance/storefront-next/sfnext-revalidation/references/POLICIES-AND-TAGS.md +55 -0
  159. package/content/guidance/storefront-next/sfnext-routing/SKILL.md +123 -0
  160. package/content/guidance/storefront-next/sfnext-routing/references/ROUTE-CONVENTIONS.md +65 -0
  161. package/content/guidance/storefront-next/sfnext-routing/references/URLS-AND-SEO-ROUTES.md +22 -0
  162. package/content/guidance/storefront-next/sfnext-scapi/SKILL.md +126 -0
  163. package/content/guidance/storefront-next/sfnext-scapi/references/WORKED-EXAMPLE.md +39 -0
  164. package/content/guidance/storefront-next/sfnext-security/SKILL.md +129 -0
  165. package/content/guidance/storefront-next/sfnext-security/references/COOKIE-DOMAIN.md +39 -0
  166. package/content/guidance/storefront-next/sfnext-security/references/TURNSTILE.md +43 -0
  167. package/content/guidance/storefront-next/sfnext-seo/SKILL.md +95 -0
  168. package/content/guidance/storefront-next/sfnext-seo/references/MULTI-DOMAIN-BASE-PATH.md +17 -0
  169. package/content/guidance/storefront-next/sfnext-seo/references/SEO-ROUTES.md +31 -0
  170. package/content/guidance/storefront-next/sfnext-state-management/SKILL.md +114 -0
  171. package/content/guidance/storefront-next/sfnext-state-management/references/PATTERNS.md +45 -0
  172. package/content/guidance/storefront-next/sfnext-testing/SKILL.md +151 -0
  173. package/content/guidance/storefront-next/sfnext-testing/references/E2E.md +38 -0
  174. package/content/guidance/storefront-next/sfnext-testing/references/STORYBOOK-PATTERNS.md +97 -0
  175. package/content/guidance/storefront-next/sfnext-testing/references/UNIT-AND-ROUTE-TESTS.md +60 -0
  176. package/content/guidance/storefront-next/sfnext-theming/SKILL.md +104 -0
  177. package/content/guidance/storefront-next/sfnext-theming/references/REBRAND-CHECKLIST.md +29 -0
  178. package/dist/commands/cap/install.d.ts +1 -0
  179. package/dist/commands/cap/list.d.ts +1 -0
  180. package/dist/commands/cap/pull.d.ts +1 -0
  181. package/dist/commands/cap/tasks.d.ts +1 -0
  182. package/dist/commands/cap/uninstall.d.ts +1 -0
  183. package/dist/commands/cip/describe.d.ts +1 -0
  184. package/dist/commands/cip/query.d.ts +1 -0
  185. package/dist/commands/cip/report/bot-traffic-share.d.ts +1 -0
  186. package/dist/commands/cip/report/checkout-funnel-dropoff.d.ts +1 -0
  187. package/dist/commands/cip/report/controller-error-rate-trend.d.ts +1 -0
  188. package/dist/commands/cip/report/controller-health-scorecard.d.ts +1 -0
  189. package/dist/commands/cip/report/customer-registration-trends.d.ts +1 -0
  190. package/dist/commands/cip/report/discount-depth-breakdown.d.ts +1 -0
  191. package/dist/commands/cip/report/inventory-stockout-by-location.d.ts +1 -0
  192. package/dist/commands/cip/report/new-vs-returning-buyer-revenue.d.ts +1 -0
  193. package/dist/commands/cip/report/ocapi-client-usage.d.ts +1 -0
  194. package/dist/commands/cip/report/ocapi-requests.d.ts +1 -0
  195. package/dist/commands/cip/report/payment-method-performance.d.ts +1 -0
  196. package/dist/commands/cip/report/product-co-purchase-analysis.d.ts +1 -0
  197. package/dist/commands/cip/report/promotion-discount-analysis.d.ts +1 -0
  198. package/dist/commands/cip/report/promotion-roi-leaderboard.d.ts +1 -0
  199. package/dist/commands/cip/report/recommender-effectiveness.d.ts +1 -0
  200. package/dist/commands/cip/report/remote-include-performance.d.ts +1 -0
  201. package/dist/commands/cip/report/revenue-by-channel.d.ts +1 -0
  202. package/dist/commands/cip/report/sales-analytics.d.ts +1 -0
  203. package/dist/commands/cip/report/sales-summary.d.ts +1 -0
  204. package/dist/commands/cip/report/scapi-cache-hit-ratio.d.ts +1 -0
  205. package/dist/commands/cip/report/scapi-error-rate-by-status.d.ts +1 -0
  206. package/dist/commands/cip/report/scapi-latency-distribution.d.ts +1 -0
  207. package/dist/commands/cip/report/scapi-traffic-latency.d.ts +1 -0
  208. package/dist/commands/cip/report/search-query-performance.d.ts +1 -0
  209. package/dist/commands/cip/report/top-referrers.d.ts +1 -0
  210. package/dist/commands/cip/report/top-selling-products.d.ts +1 -0
  211. package/dist/commands/cip/report/zero-result-searches.d.ts +1 -0
  212. package/dist/commands/cip/tables.d.ts +1 -0
  213. package/dist/commands/code/activate.d.ts +1 -0
  214. package/dist/commands/code/delete.d.ts +1 -0
  215. package/dist/commands/code/deploy.d.ts +1 -0
  216. package/dist/commands/code/download.d.ts +1 -0
  217. package/dist/commands/code/watch.d.ts +1 -0
  218. package/dist/commands/commands/search.d.ts +35 -0
  219. package/dist/commands/commands/search.js +85 -0
  220. package/dist/commands/commands/search.js.map +1 -0
  221. package/dist/commands/content/export.d.ts +1 -0
  222. package/dist/commands/content/list.d.ts +1 -0
  223. package/dist/commands/content/validate.d.ts +1 -0
  224. package/dist/commands/debug/cli.d.ts +1 -0
  225. package/dist/commands/debug/index.d.ts +1 -0
  226. package/dist/commands/docs/cache.d.ts +1 -0
  227. package/dist/commands/docs/download.d.ts +1 -0
  228. package/dist/commands/docs/read.d.ts +1 -0
  229. package/dist/commands/docs/schema.d.ts +1 -0
  230. package/dist/commands/docs/search.d.ts +1 -0
  231. package/dist/commands/docs/skill.d.ts +41 -0
  232. package/dist/commands/docs/skill.js +183 -0
  233. package/dist/commands/docs/skill.js.map +1 -0
  234. package/dist/commands/ecdn/cache/purge.d.ts +1 -0
  235. package/dist/commands/ecdn/certificates/add.d.ts +1 -0
  236. package/dist/commands/ecdn/certificates/delete.d.ts +1 -0
  237. package/dist/commands/ecdn/certificates/list.d.ts +1 -0
  238. package/dist/commands/ecdn/certificates/update.d.ts +1 -0
  239. package/dist/commands/ecdn/certificates/validate.d.ts +1 -0
  240. package/dist/commands/ecdn/cipher-suites/get.d.ts +1 -0
  241. package/dist/commands/ecdn/cipher-suites/update.d.ts +1 -0
  242. package/dist/commands/ecdn/firewall/create.d.ts +1 -0
  243. package/dist/commands/ecdn/firewall/delete.d.ts +1 -0
  244. package/dist/commands/ecdn/firewall/get.d.ts +1 -0
  245. package/dist/commands/ecdn/firewall/list.d.ts +1 -0
  246. package/dist/commands/ecdn/firewall/reorder.d.ts +1 -0
  247. package/dist/commands/ecdn/firewall/update.d.ts +1 -0
  248. package/dist/commands/ecdn/logpush/jobs/create.d.ts +1 -0
  249. package/dist/commands/ecdn/logpush/jobs/delete.d.ts +1 -0
  250. package/dist/commands/ecdn/logpush/jobs/get.d.ts +1 -0
  251. package/dist/commands/ecdn/logpush/jobs/list.d.ts +1 -0
  252. package/dist/commands/ecdn/logpush/jobs/update.d.ts +1 -0
  253. package/dist/commands/ecdn/logpush/ownership.d.ts +1 -0
  254. package/dist/commands/ecdn/mrt-rules/create.d.ts +1 -0
  255. package/dist/commands/ecdn/mrt-rules/delete.d.ts +1 -0
  256. package/dist/commands/ecdn/mrt-rules/get.d.ts +1 -0
  257. package/dist/commands/ecdn/mrt-rules/rules/delete.d.ts +1 -0
  258. package/dist/commands/ecdn/mrt-rules/rules/update.d.ts +1 -0
  259. package/dist/commands/ecdn/mrt-rules/update.d.ts +1 -0
  260. package/dist/commands/ecdn/mtls/create.d.ts +1 -0
  261. package/dist/commands/ecdn/mtls/delete.d.ts +1 -0
  262. package/dist/commands/ecdn/mtls/get.d.ts +1 -0
  263. package/dist/commands/ecdn/mtls/issue.d.ts +1 -0
  264. package/dist/commands/ecdn/mtls/list.d.ts +1 -0
  265. package/dist/commands/ecdn/mtls/setup.d.ts +1 -0
  266. package/dist/commands/ecdn/origin-headers/delete.d.ts +1 -0
  267. package/dist/commands/ecdn/origin-headers/get.d.ts +1 -0
  268. package/dist/commands/ecdn/origin-headers/set.d.ts +1 -0
  269. package/dist/commands/ecdn/page-shield/notifications/create.d.ts +1 -0
  270. package/dist/commands/ecdn/page-shield/notifications/delete.d.ts +1 -0
  271. package/dist/commands/ecdn/page-shield/notifications/list.d.ts +1 -0
  272. package/dist/commands/ecdn/page-shield/policies/create.d.ts +1 -0
  273. package/dist/commands/ecdn/page-shield/policies/delete.d.ts +1 -0
  274. package/dist/commands/ecdn/page-shield/policies/get.d.ts +1 -0
  275. package/dist/commands/ecdn/page-shield/policies/list.d.ts +1 -0
  276. package/dist/commands/ecdn/page-shield/policies/update.d.ts +1 -0
  277. package/dist/commands/ecdn/page-shield/scripts/get.d.ts +1 -0
  278. package/dist/commands/ecdn/page-shield/scripts/list.d.ts +1 -0
  279. package/dist/commands/ecdn/rate-limit/create.d.ts +1 -0
  280. package/dist/commands/ecdn/rate-limit/delete.d.ts +1 -0
  281. package/dist/commands/ecdn/rate-limit/get.d.ts +1 -0
  282. package/dist/commands/ecdn/rate-limit/list.d.ts +1 -0
  283. package/dist/commands/ecdn/rate-limit/update.d.ts +1 -0
  284. package/dist/commands/ecdn/security/get.d.ts +1 -0
  285. package/dist/commands/ecdn/security/update.d.ts +1 -0
  286. package/dist/commands/ecdn/speed/get.d.ts +1 -0
  287. package/dist/commands/ecdn/speed/update.d.ts +1 -0
  288. package/dist/commands/ecdn/waf/groups/list.d.ts +1 -0
  289. package/dist/commands/ecdn/waf/groups/update.d.ts +1 -0
  290. package/dist/commands/ecdn/waf/managed-rules/list.d.ts +1 -0
  291. package/dist/commands/ecdn/waf/managed-rules/update.d.ts +1 -0
  292. package/dist/commands/ecdn/waf/migrate.d.ts +1 -0
  293. package/dist/commands/ecdn/waf/owasp/get.d.ts +1 -0
  294. package/dist/commands/ecdn/waf/owasp/update.d.ts +1 -0
  295. package/dist/commands/ecdn/waf/rules/get.d.ts +1 -0
  296. package/dist/commands/ecdn/waf/rules/list.d.ts +1 -0
  297. package/dist/commands/ecdn/waf/rules/update.d.ts +1 -0
  298. package/dist/commands/ecdn/waf/rulesets/list.d.ts +1 -0
  299. package/dist/commands/ecdn/waf/rulesets/update.d.ts +1 -0
  300. package/dist/commands/ecdn/zones/create.d.ts +1 -0
  301. package/dist/commands/ecdn/zones/list.d.ts +1 -0
  302. package/dist/commands/job/execution/delete.d.ts +1 -0
  303. package/dist/commands/job/export.d.ts +1 -0
  304. package/dist/commands/job/import-set.d.ts +1 -0
  305. package/dist/commands/job/import.d.ts +1 -0
  306. package/dist/commands/job/log.d.ts +1 -0
  307. package/dist/commands/job/run.d.ts +1 -0
  308. package/dist/commands/job/run.js +1 -1
  309. package/dist/commands/job/run.js.map +1 -1
  310. package/dist/commands/job/search.d.ts +1 -0
  311. package/dist/commands/job/wait.d.ts +1 -0
  312. package/dist/commands/logs/get.d.ts +1 -0
  313. package/dist/commands/logs/list.d.ts +1 -0
  314. package/dist/commands/logs/tail.d.ts +1 -0
  315. package/dist/commands/metrics/controller.d.ts +1 -0
  316. package/dist/commands/metrics/ecdn.d.ts +1 -0
  317. package/dist/commands/metrics/mrt.d.ts +1 -0
  318. package/dist/commands/metrics/ocapi.d.ts +1 -0
  319. package/dist/commands/metrics/overall.d.ts +1 -0
  320. package/dist/commands/metrics/sales.d.ts +1 -0
  321. package/dist/commands/metrics/scapi-hooks.d.ts +1 -0
  322. package/dist/commands/metrics/scapi.d.ts +1 -0
  323. package/dist/commands/metrics/third-party.d.ts +1 -0
  324. package/dist/commands/mrt/bundle/delete.d.ts +1 -0
  325. package/dist/commands/mrt/bundle/deploy.d.ts +1 -0
  326. package/dist/commands/mrt/bundle/download.d.ts +1 -0
  327. package/dist/commands/mrt/bundle/history.d.ts +1 -0
  328. package/dist/commands/mrt/bundle/list.d.ts +1 -0
  329. package/dist/commands/mrt/bundle/save.d.ts +1 -0
  330. package/dist/commands/mrt/bundle/upload-v2.d.ts +1 -0
  331. package/dist/commands/mrt/env/access-control/list.d.ts +1 -0
  332. package/dist/commands/mrt/env/b2c.d.ts +1 -0
  333. package/dist/commands/mrt/env/clone.d.ts +1 -0
  334. package/dist/commands/mrt/env/create.d.ts +1 -0
  335. package/dist/commands/mrt/env/delete.d.ts +1 -0
  336. package/dist/commands/mrt/env/get.d.ts +1 -0
  337. package/dist/commands/mrt/env/invalidate.d.ts +1 -0
  338. package/dist/commands/mrt/env/list.d.ts +1 -0
  339. package/dist/commands/mrt/env/redirect/clone.d.ts +1 -0
  340. package/dist/commands/mrt/env/redirect/create.d.ts +1 -0
  341. package/dist/commands/mrt/env/redirect/delete.d.ts +1 -0
  342. package/dist/commands/mrt/env/redirect/list.d.ts +1 -0
  343. package/dist/commands/mrt/env/update.d.ts +1 -0
  344. package/dist/commands/mrt/env/var/delete.d.ts +1 -0
  345. package/dist/commands/mrt/env/var/list.d.ts +1 -0
  346. package/dist/commands/mrt/env/var/push.d.ts +1 -0
  347. package/dist/commands/mrt/env/var/set.d.ts +1 -0
  348. package/dist/commands/mrt/org/b2c.d.ts +1 -0
  349. package/dist/commands/mrt/org/cert/create.d.ts +1 -0
  350. package/dist/commands/mrt/org/cert/delete.d.ts +1 -0
  351. package/dist/commands/mrt/org/cert/get.d.ts +1 -0
  352. package/dist/commands/mrt/org/cert/list.d.ts +1 -0
  353. package/dist/commands/mrt/org/cert/restart-validation.d.ts +1 -0
  354. package/dist/commands/mrt/org/list.d.ts +1 -0
  355. package/dist/commands/mrt/org/member/add.d.ts +1 -0
  356. package/dist/commands/mrt/org/member/get.d.ts +1 -0
  357. package/dist/commands/mrt/org/member/list.d.ts +1 -0
  358. package/dist/commands/mrt/org/member/remove.d.ts +1 -0
  359. package/dist/commands/mrt/org/member/update.d.ts +1 -0
  360. package/dist/commands/mrt/project/create.d.ts +1 -0
  361. package/dist/commands/mrt/project/delete.d.ts +1 -0
  362. package/dist/commands/mrt/project/get.d.ts +1 -0
  363. package/dist/commands/mrt/project/list.d.ts +1 -0
  364. package/dist/commands/mrt/project/member/add.d.ts +1 -0
  365. package/dist/commands/mrt/project/member/get.d.ts +1 -0
  366. package/dist/commands/mrt/project/member/list.d.ts +1 -0
  367. package/dist/commands/mrt/project/member/remove.d.ts +1 -0
  368. package/dist/commands/mrt/project/member/update.d.ts +1 -0
  369. package/dist/commands/mrt/project/notification/delete.d.ts +1 -0
  370. package/dist/commands/mrt/project/notification/get.d.ts +1 -0
  371. package/dist/commands/mrt/project/update.d.ts +1 -0
  372. package/dist/commands/mrt/save-credentials.d.ts +1 -0
  373. package/dist/commands/mrt/tail-logs.d.ts +1 -0
  374. package/dist/commands/mrt/user/api-key.d.ts +1 -0
  375. package/dist/commands/mrt/user/email-prefs.d.ts +1 -0
  376. package/dist/commands/mrt/user/profile.d.ts +1 -0
  377. package/dist/commands/scaffold/init.js +2 -1
  378. package/dist/commands/scaffold/init.js.map +1 -1
  379. package/dist/commands/scapi/custom/status.d.ts +1 -0
  380. package/dist/commands/scapi/schemas/get.d.ts +9 -0
  381. package/dist/commands/scapi/schemas/get.js +51 -19
  382. package/dist/commands/scapi/schemas/get.js.map +1 -1
  383. package/dist/commands/scapi/schemas/list.d.ts +6 -0
  384. package/dist/commands/scapi/schemas/list.js +28 -18
  385. package/dist/commands/scapi/schemas/list.js.map +1 -1
  386. package/dist/commands/setup/get.d.ts +36 -0
  387. package/dist/commands/setup/get.js +67 -0
  388. package/dist/commands/setup/get.js.map +1 -0
  389. package/dist/commands/setup/ide/tsserver-plugin.d.ts +1 -0
  390. package/dist/commands/setup/ide/vscode-types.d.ts +1 -0
  391. package/dist/commands/setup/index.js +4 -3
  392. package/dist/commands/setup/index.js.map +1 -1
  393. package/dist/commands/setup/inspect.d.ts +1 -0
  394. package/dist/commands/setup/inspect.js +15 -4
  395. package/dist/commands/setup/inspect.js.map +1 -1
  396. package/dist/commands/setup/instance/create.d.ts +9 -0
  397. package/dist/commands/setup/instance/create.js +22 -6
  398. package/dist/commands/setup/instance/create.js.map +1 -1
  399. package/dist/commands/setup/instance/list.d.ts +1 -0
  400. package/dist/commands/setup/instance/list.js +3 -4
  401. package/dist/commands/setup/instance/list.js.map +1 -1
  402. package/dist/commands/setup/instance/remove.d.ts +1 -0
  403. package/dist/commands/setup/instance/remove.js +5 -5
  404. package/dist/commands/setup/instance/remove.js.map +1 -1
  405. package/dist/commands/setup/instance/set-active.d.ts +6 -0
  406. package/dist/commands/setup/instance/set-active.js +20 -5
  407. package/dist/commands/setup/instance/set-active.js.map +1 -1
  408. package/dist/commands/setup/openshell.d.ts +1 -0
  409. package/dist/commands/setup/set.d.ts +37 -0
  410. package/dist/commands/setup/set.js +87 -0
  411. package/dist/commands/setup/set.js.map +1 -0
  412. package/dist/commands/setup/skills.d.ts +1 -0
  413. package/dist/commands/setup/unset.d.ts +37 -0
  414. package/dist/commands/setup/unset.js +55 -0
  415. package/dist/commands/setup/unset.js.map +1 -0
  416. package/dist/commands/slas/client/create.d.ts +1 -0
  417. package/dist/commands/slas/client/delete.d.ts +1 -0
  418. package/dist/commands/slas/client/get.d.ts +1 -0
  419. package/dist/commands/slas/client/list.d.ts +1 -0
  420. package/dist/commands/slas/client/open.d.ts +1 -0
  421. package/dist/commands/slas/client/update.d.ts +1 -0
  422. package/dist/commands/slas/token.d.ts +1 -0
  423. package/dist/commands/slas/token.js +2 -1
  424. package/dist/commands/slas/token.js.map +1 -1
  425. package/dist/commands/webdav/get.d.ts +1 -0
  426. package/dist/commands/webdav/rm.d.ts +1 -0
  427. package/dist/help.d.ts +25 -0
  428. package/dist/help.js +96 -0
  429. package/dist/help.js.map +1 -0
  430. package/dist/lib/scaffold/generate-helper.js +3 -2
  431. package/dist/lib/scaffold/generate-helper.js.map +1 -1
  432. package/dist/lib/skills.d.ts +19 -0
  433. package/dist/lib/skills.js +75 -0
  434. package/dist/lib/skills.js.map +1 -0
  435. package/dist/utils/cip/command.d.ts +1 -0
  436. package/dist/utils/ecdn/zone-command.d.ts +1 -0
  437. package/dist/utils/setup/config-field-command.d.ts +25 -0
  438. package/dist/utils/setup/config-field-command.js +41 -0
  439. package/dist/utils/setup/config-field-command.js.map +1 -0
  440. package/dist/utils/slas/client.d.ts +1 -0
  441. package/oclif.manifest.json +20802 -16968
  442. package/package.json +11 -5
@@ -0,0 +1,39 @@
1
+ # Worked Example: the shipped `sfnext-notify` custom API
2
+
3
+ Your project already contains one complete custom API, used to send login and password-reset emails. Copy its shape for your own.
4
+
5
+ | Piece | Location in your project |
6
+ |---|---|
7
+ | Endpoint mapping | `cartridges/app_storefrontnext_base/cartridge/rest-apis/sfnext-notify/api.json` |
8
+ | Contract and auth | `.../sfnext-notify/schema.yaml` (`ShopperToken` security scheme, scope `c_sfnext_notify`, `security:` on the operation) |
9
+ | Implementation | `.../sfnext-notify/Notify.js` (exports `notify`, matching `"implementation": "Notify"` and the `operationId`) |
10
+ | Storefront registration | `src/scapi/schemas/sfnext-notify-v1.yaml` + `.meta.json`, `src/scapi/generated/`, entry in `src/scapi/custom-clients.ts` |
11
+ | Server call site | `src/lib/notify/notify.server.ts` |
12
+
13
+ `api.json` maps each `operationId` to a schema file and script module:
14
+
15
+ ```json
16
+ { "endpoints": [ { "endpoint": "notify", "schema": "schema.yaml", "implementation": "Notify" } ] }
17
+ ```
18
+
19
+ Registry entry generated by `sfnext scapi add`:
20
+
21
+ ```typescript
22
+ { key: 'sfnextNotify', basePath: '/custom/sfnext-notify/v1', ops: ops0, locale: true, orgPrefix: true }
23
+ ```
24
+
25
+ Call site (server only):
26
+
27
+ ```typescript
28
+ import { createApiClients } from '@/lib/api-clients.server';
29
+
30
+ const clients = createApiClients(context);
31
+ await clients.sfnextNotify.notify({ params: {}, body }); // siteId, organizationId injected
32
+ ```
33
+
34
+ Notes:
35
+
36
+ - Authentication is enforced by the SCAPI gateway (shopper token must carry `c_sfnext_notify`), not in the script. Register the scope on the SLAS client once with `sfnext setup-base-cartridge --slas-client-id <id>`.
37
+ - `sfnextNotify` is intentionally **not** in the browser resource-route allow-list, so it cannot be invoked from `useScapiFetcher`; a test in the project asserts this.
38
+ - Being a custom client, it is left unclassified by the non-personalized-response policy, which is correct for a POST.
39
+ - To follow the pattern for your own API: write the cartridge (`b2c:b2c-custom-api-development`), `b2c code deploy <cartridge> --reload`, verify with `b2c scapi custom status --tenant-id <id>`, then `b2c sfnext scapi add custom <name> v1`.
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: sfnext-security
3
+ description: >-
4
+ Configure Storefront Next security response headers, Content Security Policy (CSP), CSP contributors, Cloudflare Turnstile bot protection, and the shared cookie domain. Use for "Refused to load the script/connect/image" CSP violations, adding a third-party origin, app.security.headers, defaultCspDirectives, csp reportOnly rollout, writing a CSP contributor under src/middlewares/csp-contributors, HSTS or Permissions-Policy, Turnstile widget or enforceTurnstile, TURNSTILE_SECRET_KEYS, log-only rollout, or app.cookies.domain across subdomains. Do not use for auth tokens, cookie names or login flows (use `storefront-next:sfnext-authentication`), SFRA proxy routing (use `storefront-next:sfnext-hybrid-storefronts`), or consent banners and tracking (use `storefront-next:sfnext-analytics-consent`).
5
+ ---
6
+
7
+ # Storefront Next Security
8
+
9
+ Security is configured under `app.security` in `config.server.ts`: `security.headers` for response headers and CSP, `security.turnstile` for bot protection. Cookie sharing is `app.cookies.domain`. Your project ships deeper docs: `docs/README-SECURITY-HEADERS.md`, `docs/README-TURNSTILE.md`, `docs/README-COOKIE-DOMAIN.md`. Read them for details; this skill is the task guide.
10
+
11
+ ## Default headers
12
+
13
+ Every storefront gets these with no opt-in, from `@salesforce/storefront-next-runtime/security` via the `securityHeadersMiddleware` in `src/middlewares/security-headers.server.ts`:
14
+
15
+ | Header | Default |
16
+ |---|---|
17
+ | `Content-Security-Policy` | Strict, nonce-based (no `'unsafe-inline'` scripts, no `'unsafe-eval'`) |
18
+ | `Strict-Transport-Security` | Sent on Managed Runtime only, suppressed locally |
19
+ | `X-Frame-Options` | `SAMEORIGIN` |
20
+ | `X-Content-Type-Options` | `nosniff` |
21
+ | `Referrer-Policy` | `strict-origin-when-cross-origin` |
22
+ | `Permissions-Policy` | `camera=(), microphone=(), geolocation=()` |
23
+
24
+ The CSP directive table (which origins `script-src`, `connect-src`, `img-src`, and `frame-src` allow by default) is in `docs/README-SECURITY-HEADERS.md`. The per-request nonce is read in `root.tsx` and must be forwarded to any inline `<script>` you render (for example the JSON-LD component takes a `nonce` prop).
25
+
26
+ ## Allow a new third-party origin
27
+
28
+ The violation message names the directive to extend ("Refused to connect to ..." means `connect-src`). Spread the defaults, because **each directive you set fully replaces the default**:
29
+
30
+ ```ts
31
+ // config.server.ts
32
+ import { defaultCspDirectives } from '@salesforce/storefront-next-runtime/security';
33
+
34
+ security: {
35
+ headers: {
36
+ csp: {
37
+ directives: {
38
+ ...defaultCspDirectives,
39
+ 'script-src': [...defaultCspDirectives['script-src']!, 'https://cdn.example.com'],
40
+ 'connect-src': [...defaultCspDirectives['connect-src']!, 'https://api.example.com'],
41
+ },
42
+ },
43
+ },
44
+ },
45
+ ```
46
+
47
+ Use exact origins, not wildcards. Do not load third-party scripts synchronously in the document head; load them after interaction or idle (see `storefront-next:sfnext-performance`).
48
+
49
+ Env-var override is awkward for directives (names contain hyphens, and a JSON override replaces the whole map). Use `config.server.ts` for directives and env vars for toggles:
50
+
51
+ ```bash
52
+ PUBLIC__app__security__headers__csp__reportOnly=true
53
+ PUBLIC__app__security__headers__hsts=false
54
+ ```
55
+
56
+ ## Roll out or relax safely
57
+
58
+ - Migrating or adding many origins: set `csp: { reportOnly: true }`, watch DevTools violations, extend the directives, then remove `reportOnly`. A startup warning is logged while it is on.
59
+ - Disable one header with `hsts: false` or `permissionsPolicy: false`. `headers: { enabled: false }` disables everything; use it for debugging only. A startup warning is logged whenever any header is disabled.
60
+
61
+ ## CSP contributors
62
+
63
+ A contributor lets a feature add its own exact origins to the CSP at boot instead of hand-editing directives. Shipped examples in `src/middlewares/csp-contributors/`: `cimulate.ts` (Shopper Agent widget, active when enabled in config), `data360.ts` (`connect-src` for the tenant ingestion host), and `openstreetmap.ts` (a deliberate no-op; replace it to permit map tiles).
64
+
65
+ Shape (see `data360.ts` for the smallest real one):
66
+
67
+ ```ts
68
+ import type { CspContributor, CspContribution } from '@salesforce/storefront-next-runtime/security';
69
+ import { toCspOrigin } from './to-csp-origin.js';
70
+
71
+ export function createMyFeatureCspContributor(cfg: { enabled?: boolean; apiUrl?: string } | undefined): CspContributor {
72
+ const origin = cfg?.apiUrl ? toCspOrigin(cfg.apiUrl) : null;
73
+ return {
74
+ id: 'my-feature',
75
+ isActive: () => cfg?.enabled === true && origin !== null,
76
+ contribute: (): CspContribution => (origin ? { 'connect-src': [origin] } : {}),
77
+ };
78
+ }
79
+ ```
80
+
81
+ Then add it to the `contributors` array in `src/middlewares/security-headers.server.ts`. Rules:
82
+
83
+ - Derive origins from config you already have; `toCspOrigin` returns `null` for unsafe values.
84
+ - The runtime validates contributors at boot: https only, no wildcards, no credentials, no whitespace.
85
+ - Contribute only while the feature is active, so disabled features never widen the policy.
86
+ - Add a colocated `*.test.ts` like the shipped ones.
87
+
88
+ Use a contributor for a feature toggled by config; use the `defaultCspDirectives` spread for one-off origins.
89
+
90
+ ## Turnstile bot protection
91
+
92
+ Cloudflare Turnstile guards passwordless email, checkout registration, and order lookup. It is **fail-open** on Cloudflare outages (so a Turnstile incident never blocks checkout) and fail-closed on real bot signals (forged, replayed, or missing tokens).
93
+
94
+ Config (`config.server.ts`, overridable with `PUBLIC__` env vars): `security.turnstile.enabled`, `mode` (`managed`, `non-interactive`, `invisible`), `sites` (per-site site keys), and `verification.mode` (`enforce`, `log-only`, `disabled`).
95
+
96
+ | Variable | Purpose |
97
+ |---|---|
98
+ | `TURNSTILE_SECRET_KEYS` | JSON map of site key to secret key. Server-only. Required for `enforce` and `log-only` |
99
+ | `PUBLIC__app__security__turnstile__enabled` | Master switch; when false the widget never renders |
100
+ | `PUBLIC__app__security__turnstile__sites` | JSON per-site keys |
101
+ | `PUBLIC__app__security__turnstile__verification__mode` | `enforce`, `log-only`, `disabled` |
102
+
103
+ Rollout: deploy with `verification.mode` set to `log-only`, read the logs for `would_block`, then switch to `enforce`. Shoppers start without a `cc-tv_<siteId>` attestation cookie after the switch, which is expected.
104
+
105
+ Add protection to a new server action by rendering `TurnstileWidget` (`src/components/security/turnstile-widget.tsx`) in the form and calling `enforceTurnstile` (`src/lib/turnstile/enforce.server.ts`) in the action before doing work. Pass `request`, `config`, `turnstileToken`, `logger`, `actionName`, `email`, and `turnstileCookieName`; set the `cc-tv` cookie from the returned `cookieValue` when it is non-null. Copy an existing caller such as `src/routes/action.authorize-passwordless-email.ts`. Cloudflare test keys and a manual test matrix: [references/TURNSTILE.md](references/TURNSTILE.md).
106
+
107
+ ## Cookie domain
108
+
109
+ Cookies are host-only by default. To share them across subdomains or with SFRA, set `app.cookies.domain` (or `commerce.sites[].cookies.domain` per site; env `PUBLIC__app__cookies__domain=.example.com`) **and** match Business Manager: Site Preferences, Hybrid Auth Settings, cookie-domain level `0` for host-only, `2` for the first-level parent domain. Level `1` is rejected. A mismatch silently breaks sessions. Full checklist: [references/COOKIE-DOMAIN.md](references/COOKIE-DOMAIN.md).
110
+
111
+ ## Pitfalls
112
+
113
+ | Symptom | Cause / fix |
114
+ |---|---|
115
+ | Added origin to `script-src` and other scripts broke | You replaced the directive. Spread `defaultCspDirectives['script-src']` first |
116
+ | Inline script blocked | Missing `nonce`. Read it from root loader data and pass it on |
117
+ | CSS/JS fail on local Safari after enabling your own `upgrade-insecure-requests` | The default suppresses it locally for a reason; keep the default |
118
+ | Login does not stick after setting `cookies.domain` | Domain is not a parent of the serving host, or Business Manager level mismatches |
119
+ | Turnstile widget never appears | Expected when Cloudflare says no challenge is needed (`interaction-only`); check network tab and `cc-tv_<siteId>` |
120
+ | Everything blocked in Turnstile tests | Secret keys missing in `TURNSTILE_SECRET_KEYS` for the configured site key |
121
+
122
+ ## Related Skills
123
+
124
+ - `storefront-next:sfnext-authentication` - cookies, sessions, login flows that Turnstile protects
125
+ - `storefront-next:sfnext-hybrid-storefronts` - SFRA proxy and `dwsid` session bridge
126
+ - `storefront-next:sfnext-configuration` - `config.server.ts` and `PUBLIC__` overrides
127
+ - `storefront-next:sfnext-performance` - loading third-party scripts safely
128
+ - `storefront-next:sfnext-analytics-consent` - tracking consent and analytics adapters
129
+ - `storefront-next:sfnext-deployment` - Managed Runtime environment variables
@@ -0,0 +1,39 @@
1
+ # Cookie domain reference
2
+
3
+ Full guide: `docs/README-COOKIE-DOMAIN.md` in your project.
4
+
5
+ ## Resolution order
6
+
7
+ 1. `commerce.sites[].cookies.domain` (per site)
8
+ 2. `app.cookies.domain` (global; env `PUBLIC__app__cookies__domain`)
9
+ 3. Unset: host-only cookies
10
+
11
+ The domain is applied to every cookie the storefront writes (auth, session, site, locale, currency). It must be a parent of the serving host or the browser silently drops the cookies. A leading dot is optional.
12
+
13
+ ## Business Manager match
14
+
15
+ Business Manager, Merchant Tools, Site Preferences, Hybrid Auth Settings:
16
+
17
+ | Storefront `cookies.domain` | BM level | Result |
18
+ |---|---|---|
19
+ | unset | 0 | OK, host-only everywhere |
20
+ | `.example.com` | 2 | OK, shared across subdomains |
21
+ | `.example.com` | 0 | Broken cross-subdomain session |
22
+ | unset | 2 | Duplicate host-only vs domain-scoped cookies |
23
+
24
+ Level 1 is rejected. Salesforce-managed sites need a request to the team managing Business Manager.
25
+
26
+ ## Verify
27
+
28
+ ```bash
29
+ curl -sI https://www.example.com/ | grep -i '^set-cookie:'
30
+ ```
31
+
32
+ - Every cookie carries the same `Domain=`.
33
+ - No same-name duplicates (one host-only, one domain-scoped).
34
+ - A session persists between `www.` and `account.` hosts; in hybrid mode `dwsid` is shared with SFRA pages.
35
+ - Logout clears the domain-scoped cookies.
36
+
37
+ ## Rollout
38
+
39
+ Changing the domain on a live site leaves old cookies next to new ones until they expire, and a host-only cookie shadows a domain-scoped one of the same name. Expect transient session glitches; roll out in a low-traffic window. A per-site empty string inherits the global domain; it cannot force host-only.
@@ -0,0 +1,43 @@
1
+ # Turnstile reference
2
+
3
+ Deep design lives in `docs/README-TURNSTILE.md` in your project. Quick facts:
4
+
5
+ ## Modules
6
+
7
+ - `src/components/security/turnstile-widget.tsx` - client widget (`siteKey`, `onSuccess`, `onError`, `onExpire`, `onTimeout`, `onBypass`). Mounts with `appearance: 'interaction-only'`, so it is hidden unless Cloudflare needs shopper input.
8
+ - `src/lib/turnstile/enforce.server.ts` - `enforceTurnstile` and `resolveVerificationMode`.
9
+ - `src/lib/turnstile/constants.ts` - cookie base name `cc-tv` (written as `cc-tv_<siteId>`, 30 minute TTL).
10
+
11
+ ## Enforcement points
12
+
13
+ `action.authorize-passwordless-email`, `action.initiate-checkout-registration`, the order-lookup request-code action, and the passwordless submit and OTP resend on the login route. The OTP verification actions rely on the earlier challenge plus the code itself.
14
+
15
+ ## Fail-open vs fail-closed
16
+
17
+ Open (request allowed, logged at warn): siteverify network failure or 5xx, `internal-error`, sustained elevated failure rate, CDN probe failure, client script timeout. Closed: `invalid-input-response`, `timeout-or-duplicate`, missing token while Cloudflare looks healthy, origin mismatch, misconfigured site key.
18
+
19
+ ## Verification mode
20
+
21
+ | Value | Behavior |
22
+ |---|---|
23
+ | `enforce` | Blocks failures |
24
+ | `log-only` | Runs full verification, only logs `would_block`; no shopper blocked, no `cc-tv` cookie issued |
25
+ | `disabled` | Skips verification |
26
+
27
+ `TURNSTILE_VERIFICATION_ENABLED` is deprecated; use `PUBLIC__app__security__turnstile__verification__mode`. `TURNSTILE_CDN_PROBE_URL` overrides the health probe URL for tests.
28
+
29
+ ## Cloudflare test keys (local development)
30
+
31
+ | Site key | Secret key | Behavior |
32
+ |---|---|---|
33
+ | `1x00000000000000000000AA` | `1x0000000000000000000000000000000AA` | Always passes, silent |
34
+ | `2x00000000000000000000AB` | `2x0000000000000000000000000000000AA` | Always blocks, silent |
35
+ | `3x00000000000000000000FF` | `1x0000000000000000000000000000000AA` | Forces an interactive challenge (only visible widget) |
36
+
37
+ Set the site key in `security.turnstile.sites` and `TURNSTILE_SECRET_KEYS` to a JSON map of site key to secret key. The passwordless login form only renders when the `emailVerificationEnabled` site preference is on (Business Manager, Storefront Login Preferences); see `docs/README-TURNSTILE.md` for seeding it locally.
38
+
39
+ ## Manual checks
40
+
41
+ - Interactive key `3x...FF` with a passing secret: widget appears, then the OTP modal opens.
42
+ - Always-block key: no widget, a generic "We couldn't verify your information" alert, no OTP modal.
43
+ - Block `challenges.cloudflare.com` locally: no widget, flow continues (graceful degradation).
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: sfnext-seo
3
+ description: >-
4
+ Storefront Next SEO and AEO/GEO: the SeoMeta component (title, description, noIndex, Open Graph, X cards), JsonLd structured data, canonical URLs and the query-parameter allowlist, hreflang, configurable product/category URLs via url.seoRoutes, semantic URL builders (createProductUrl, createCategoryUrl, useSeoUrlContext), redirectToCanonicalPath, crawler rendering and pagination, and how multi-domain and base path affect generated URLs. Use when adding meta tags or JSON-LD to a route, enabling or changing seoRoutes, fixing duplicate or wrong canonical or og:url URLs, or when a build fails for a missing site in seoRoutes. Do not use for route file naming (use `storefront-next:sfnext-routing`), site/locale config in general (use `storefront-next:sfnext-configuration`), or translations (use `storefront-next:sfnext-i18n`).
5
+ ---
6
+
7
+ # Storefront Next SEO
8
+
9
+ In-project references: `docs/README-SEO.md`, `docs/README-AEO-GEO.md`, `docs/README-MULTI-SITE.md` (URL Config), `docs/README-MULTI-DOMAIN.md`, `docs/README-BASE-PATH.md`, `docs/migrations/seo-url-rules/README.md`. Read them for depth; this skill is the task map.
10
+
11
+ ## Page metadata: SeoMeta
12
+
13
+ ```tsx
14
+ import { SeoMeta } from '@/components/seo-meta';
15
+
16
+ <SeoMeta title={t('meta.title', { defaultValue: 'My Page' })} description={t('meta.description', { defaultValue: '...' })} />
17
+ <SeoMeta title="Checkout" noIndex /> {/* private/transactional pages */}
18
+ <SeoMeta rawTitle title="Store Name" /> {/* no " | Site Name" suffix */}
19
+ <SeoMeta title={name} openGraph={{ type: 'product', url: pageUrl, image }} />
20
+ ```
21
+
22
+ Props (verified in `src/components/seo-meta/index.tsx`): `title`, `rawTitle`, `description`, `noIndex`, `siteName`, `twitter`, `openGraph`. Open Graph input auto-derives X card tags unless `twitter` is set. Use `noIndex` for cart, checkout, account, and other non-public pages. Root-level canonical and hreflang descriptors come from `src/utils/seo.ts` and `src/utils/canonical-url.ts`; routes do not add them.
23
+
24
+ ## Structured data: JsonLd
25
+
26
+ ```tsx
27
+ import { JsonLd } from '@/components/json-ld';
28
+
29
+ <JsonLd data={productSchema} id="product-schema" nonce={nonce} />
30
+ ```
31
+
32
+ Pass the CSP nonce so the inline script is allowed. Builders live in `src/utils/product-schema.ts`, `category-schema.ts`, `schema-url.ts`. PDP emits `Product`; PLP emits `CollectionPage` + `ItemList` (see `docs/README-AEO-GEO.md`).
33
+
34
+ ## One URL everywhere
35
+
36
+ Canonical `<link>`, `og:url` and JSON-LD `url` must agree. In loaders:
37
+
38
+ ```ts
39
+ import { buildSeoPageUrl } from '@/lib/seo/page-url.server';
40
+ import { redirectToCanonicalPath } from '@/lib/seo/canonical-redirect.server';
41
+
42
+ redirectToCanonicalPath(requestUrl); // 301 for trailing slash
43
+ const pageUrl = buildSeoPageUrl(context, requestUrl); // origin + canonical path/query
44
+ ```
45
+
46
+ Only allowlisted query params survive in canonicals (`CONTENT_PARAMS` in `src/utils/canonical-url.ts`: `q`, `offset`, `sort`, `refine`, `pid` by default). Add a param only if it changes main page content, and add a test next to `canonical-url.test.ts`.
47
+
48
+ ## Configurable URLs: url.seoRoutes
49
+
50
+ ```ts
51
+ // config.server.ts
52
+ url: {
53
+ prefix: '/:siteId/:localeId',
54
+ seoRoutes: {
55
+ RefArchGlobal: { product: { prefix: 'p' }, category: { prefix: 'c', mode: 'id-suffix' } },
56
+ },
57
+ }
58
+ ```
59
+
60
+ Key rules (full playbook in [references/SEO-ROUTES.md](references/SEO-ROUTES.md)):
61
+
62
+ - Build-time: changing `seoRoutes` requires a rebuild and redeploy (restart the dev server locally).
63
+ - Every active site needs an entry, or URL generation throws / the build fails.
64
+ - Business Manager URL settings are the source of truth; `seoRoutes` is a manual copy that must be kept in sync.
65
+ - Do not rename route files. Routes are already generic; the config maps prefixes.
66
+ - Build links with the semantic builders, never hardcoded `/product/...` strings:
67
+
68
+ ```tsx
69
+ import { createProductUrl, createCategoryUrl } from '@/route-paths';
70
+ import { useSeoUrlContext } from '@/hooks/use-seo-url-context';
71
+
72
+ const ctx = useSeoUrlContext();
73
+ <Link to={createProductUrl({ productId, slug }, ctx)} />
74
+ ```
75
+
76
+ `seoFallback.sites` in config supplies per-site fallback behavior when a SEO URL cannot be resolved; see `docs/README-MULTI-SITE.md`.
77
+
78
+ ## Multi-domain and base path
79
+
80
+ - Origin for canonicals comes from `getAppOrigin` (honors forwarded host, falls back to `EXTERNAL_DOMAIN_NAME`). Each custom domain must be attached to the Managed Runtime environment. See [references/MULTI-DOMAIN-BASE-PATH.md](references/MULTI-DOMAIN-BASE-PATH.md).
81
+ - Mapping domains to sites uses an `X-Site-Id` header set by your CDN.
82
+ - A base path (`MRT_ENV_BASE_PATH`, set from the push config) prefixes asset and route URLs; use `getBasePath()` rather than hardcoding.
83
+
84
+ ## Crawlers
85
+
86
+ Crawlers receive full HTML rendering; category pages stay crawlable through a `?page=N` parameter with rel prev/next links, while the canonical stays the base category URL. Verify with `curl` and a crawler user agent. Check `docs/README-SEO.md` "Crawler Rendering and Pagination" before changing streaming behavior.
87
+
88
+ ## Related Skills
89
+
90
+ - `storefront-next:sfnext-routing` - route modules and URL patterns
91
+ - `storefront-next:sfnext-configuration` - config.server.ts and env overrides
92
+ - `storefront-next:sfnext-i18n` - locales, hreflang inputs
93
+ - `storefront-next:sfnext-hybrid-storefronts` - URLs owned by another storefront
94
+ - `storefront-next:sfnext-security` - CSP nonce for JSON-LD
95
+ - `storefront-next:sfnext-performance` - streaming vs crawler rendering
@@ -0,0 +1,17 @@
1
+ # Multi-domain and base path effects on URLs
2
+
3
+ In-project docs: `docs/README-MULTI-DOMAIN.md`, `docs/README-BASE-PATH.md`, `docs/README-MULTI-SITE.md`.
4
+
5
+ ## Multiple domains, one environment
6
+
7
+ - Attach every domain to the Managed Runtime environment (custom domains in the MRT project; see `b2c-cli:b2c-mrt`).
8
+ - The request host is read from the forwarded host header; `getAppOrigin` uses it for canonical, hreflang, and og URLs. `EXTERNAL_DOMAIN_NAME` is a fallback only.
9
+ - To choose the Commerce site per domain, have the CDN send an `X-Site-Id` header (for example `site-a.shop.com` -> `RefArch`).
10
+ - Only trust forwarded host behind your CDN; the doc has a section on trusting the forwarded host.
11
+ - Images on custom domains need `realmHostMappings`; cookies are host-only by default (see `docs/README-COOKIE-DOMAIN.md`).
12
+
13
+ ## Base path
14
+
15
+ - `MRT_ENV_BASE_PATH` is set by Managed Runtime from the push configuration (`ssrParameters.envBasePath`).
16
+ - Server code reads `process.env.MRT_ENV_BASE_PATH`; client code derives it from the bundle path. Use `getBasePath()` (defined in your project's `src/lib/utils.ts`) when building URLs by hand.
17
+ - Canonical URLs must include the base path; use `buildSeoPageUrl` instead of concatenating strings.
@@ -0,0 +1,31 @@
1
+ # Adopting url.seoRoutes
2
+
3
+ Source: `docs/migrations/seo-url-rules/README.md` and `docs/README-MULTI-SITE.md` (URL Config) in your project. Trust the code if they differ; verify `apply-seo-url-config` behavior in `@salesforce/storefront-next-dev`.
4
+
5
+ ## What it changes
6
+
7
+ Without `seoRoutes` the storefront serves the built-in `/product/{id}` and `/category/{id}` grammar. With it, each site gets its own product and category prefix (for example `/p/{slug}/{id}`), matching Business Manager URL settings.
8
+
9
+ ## Checklist before enabling
10
+
11
+ 1. Runtime and dev packages are on a release that supports `seoRoutes` (see `docs/COMPATIBILITY.md`).
12
+ 2. Every active site has an entry in `url.seoRoutes`; a missing site fails.
13
+ 3. Decide the category mode per site: `id-suffix` (slug plus trailing category id) or slug-path (pure slug hierarchy).
14
+ 4. Protect routes that must not be captured by a product/category prefix with `protectedPaths` (build-time option described in the URL Config section).
15
+ 5. Leaf routes vs segments: check how the build validates overlapping prefixes.
16
+ 6. Keep Business Manager URL settings identical; there is no automatic sync.
17
+
18
+ ## Avoid breaking indexed URLs
19
+
20
+ - Keep old URL shapes redirecting to the new canonical ones; `redirectToCanonicalPath` only normalizes trailing slashes, so test old `/product/{id}` URLs explicitly.
21
+ - Audit hardcoded links; replace them with `createProductUrl` / `createCategoryUrl` and `useSeoUrlContext()`.
22
+ - Legacy category paths can be converted with `createCategoryUrlFromLegacyPath` in `@/route-paths`.
23
+ - Deploy, then crawl canonical, hreflang, and JSON-LD URLs for consistency.
24
+
25
+ ## Hybrid deployments
26
+
27
+ If part of the catalog is served by another storefront, the storefront only owns the URLs routed to it; see `storefront-next:sfnext-hybrid-storefronts` and `docs/README-HYBRID-PROXY.md`.
28
+
29
+ ## Verifying
30
+
31
+ Run `pnpm typecheck && pnpm test`, then request a product and category URL for each site and check the canonical `<link>`.
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: sfnext-state-management
3
+ description: >-
4
+ Choose the right state mechanism in a Storefront Next project: loader and action data, useRouteLoaderData, URL state with useSearchParams, cookies and sessions, optimistic UI (fetcher.formData, useNavigation, useOptimistic), React context providers (BasketProvider, AuthProvider, wishlist, product-context), and small useSyncExternalStore stores such as the mini-cart store. Use for "where should this state live", "useBasket", "useAuth", "share state between components", "optimistic add to cart", "global client state", "hydration mismatch from a store", or "do I need Zustand". Do not use for fetching or mutating data (use `storefront-next:sfnext-data-fetching`), controlling loader re-runs (use `storefront-next:sfnext-revalidation`), or login flows and sessions (use `storefront-next:sfnext-authentication`).
5
+ ---
6
+
7
+ # Storefront Next State Management
8
+
9
+ Server state lives in loaders and actions. Client state is small and local. There is no global client store library in the template (Zustand was removed): use the tools below in this order. `docs/README-STATE.md` and `docs/README-DATA.md` in your project cover each in depth.
10
+
11
+ ## Decision table
12
+
13
+ | State | Use |
14
+ |---|---|
15
+ | Data from SCAPI | `loader` + `loaderData`; `action` for writes |
16
+ | Another route's loader data | `useRouteLoaderData('routes/_app')` (route id = file path without extension) |
17
+ | Filters, sort, pagination, tab | URL: `useSearchParams` / `useParams` (shareable, SSR-friendly) |
18
+ | Must survive requests, read by server | cookies (`createCookie` from `@/lib/cookie-utils.server`) or session helpers |
19
+ | Pending or optimistic result | `fetcher.formData`, `useNavigation`, `useOptimistic` |
20
+ | Form fields, toggles, open/close in one component | `useState` / `useReducer` |
21
+ | Shared by a subtree | React context with a provider at the owning layout |
22
+ | Shared app-wide and read outside React, or high-frequency | module-level `useSyncExternalStore` store |
23
+ | Request-scoped server values (site, auth, basket) | router `context` (middleware), read with `context.get(...)` |
24
+
25
+ Prefer the earliest row that works. Never copy loader data into `useState` to "cache" it.
26
+
27
+ ## Existing providers and stores
28
+
29
+ | Need | API |
30
+ |---|---|
31
+ | Basket | `BasketProvider` in `src/providers/basket.tsx`: `useBasket({ autoLoad })`, `useBasketSnapshot`, `useBasketHydrated`, `useBasketError`, `useBasketUpdater`, `useBasketReset`, `useBasketLoader`, `useBasketReconcile`; `BasketCookieReconciler` syncs the cookie |
32
+ | Session info | `useAuth()` from `@/providers/auth` |
33
+ | Product / variant selection | `@/providers/product-context`, `@/providers/product-view` |
34
+ | Image widths | `DynamicImageProvider` (`@/providers/dynamic-image`) |
35
+ | Wishlist | `src/providers/wishlist.ts` (module-level store) |
36
+ | Mini-cart open state | `useMiniCartStore`, `setMiniCartOpen` in `src/hooks/mini-cart-store.ts` |
37
+ | Cart toast | `src/hooks/cart-mutation-toast-store.ts` |
38
+ | Shopper context | `useShopperContext` (`@/hooks/use-shopper-context`) |
39
+
40
+ The basket is loaded through `useScapiFetcher('shopperBasketsV2', 'getBasket')` and is updated from action results via `updateBasketResource` (server side, `@/middlewares/basket.server`), so after a basket action components simply re-render from `useBasket()`. The `__sfdc_basket` cookie carries the basket id; `__sfdc_usertype` is the user-type hint. Do not read these directly in components; use the hooks. Server reads: `getBasket`, `getBasketSnapshot`, `ensureBasketId`, `destroyBasket`, and `getAuth`, `updateAuth`, `destroyAuth` from `@/middlewares/auth.server`.
41
+
42
+ ## Optimistic UI
43
+
44
+ ```tsx
45
+ const fetcher = useFetcher<typeof action>();
46
+ const removing = fetcher.formData?.get('itemId') === item.itemId;
47
+ return <li hidden={removing}>...</li>;
48
+ ```
49
+
50
+ Use `useOptimistic` for values the server will confirm, and `useNavigation` for URL-driven pending states. Roll back by reading the confirmed `loaderData`/basket value; never hand-maintain a parallel copy.
51
+
52
+ ## A small external store
53
+
54
+ Use when state is global, changes often, or must be set outside React. Follow `src/hooks/mini-cart-store.ts`:
55
+
56
+ ```ts
57
+ import { useSyncExternalStore } from 'react';
58
+
59
+ interface State { open: boolean }
60
+ let state: State = { open: false };
61
+ const SERVER_STATE: State = Object.freeze({ open: false });
62
+ const listeners = new Set<() => void>();
63
+
64
+ const subscribe = (l: () => void) => { listeners.add(l); return () => void listeners.delete(l); };
65
+ export const setOpen = (open: boolean) => {
66
+ if (open === state.open) return; // skip no-op writes
67
+ state = { ...state, open };
68
+ listeners.forEach((l) => l());
69
+ };
70
+ export function useDrawerStore<T>(select: (s: State) => T): T {
71
+ return useSyncExternalStore(subscribe, () => select(state), () => select(SERVER_STATE));
72
+ }
73
+ ```
74
+
75
+ Rules:
76
+
77
+ - `getSnapshot` must return a referentially stable value when nothing changed. Select primitives; a selector that allocates per call loops forever.
78
+ - Provide a server snapshot and mutate only from events or effects so SSR and the first client render match (no hydration warnings).
79
+ - Split stores by independent writers so each consumer subscribes to a slice.
80
+ - For an instance-based store (one per widget), create it in a factory and provide it by context, as `createStoreLocatorStore` does in the store-locator extension.
81
+
82
+ ## Context providers
83
+
84
+ - Put the provider at the lowest layout that contains all consumers.
85
+ - Memoize the `value` (`useMemo`, `useCallback`) and split read-only state from updaters into separate contexts so updater-only consumers do not re-render.
86
+ - Use a selector pattern for large contexts (see README-STATE "Context Selector Pattern").
87
+ - Values used only inside callbacks belong in `useRef`, not context state.
88
+ - Compose providers with `src/providers/compose-providers.tsx`.
89
+
90
+ More patterns and anti-patterns in [PATTERNS.md](references/PATTERNS.md).
91
+
92
+ ## Common mistakes
93
+
94
+ | Mistake | Instead |
95
+ |---|---|
96
+ | `useEffect` that fetches on mount and stores in state | loader (or streamed promise) |
97
+ | Copying `loaderData` or basket into `useState` | read directly; derive with plain expressions |
98
+ | Filters in `useState` | `useSearchParams` |
99
+ | Reading cookies in components | loader returns the value, or use the provider hook |
100
+ | New context for one component's state | `useState` in that component |
101
+ | Adding Zustand or Redux | the mechanisms above; check `storefront-next:sfnext-overview` for conventions |
102
+
103
+ ## Finding more
104
+
105
+ `AGENTS.md` ("Key Documentation"), `docs/README-STATE.md`, `docs/README-DATA.md`. `b2c docs search "storefront next state"` or the `docs_search` MCP tool.
106
+
107
+ ## Related Skills
108
+
109
+ - `storefront-next:sfnext-data-fetching` - loaders, actions, fetchers
110
+ - `storefront-next:sfnext-revalidation` - when providers make a re-run unnecessary
111
+ - `storefront-next:sfnext-performance` - hydration stability and render scope
112
+ - `storefront-next:sfnext-authentication` - `useAuth` and session lifecycle
113
+ - `storefront-next:sfnext-commerce-features` - cart, wishlist, checkout features
114
+ - `storefront-next:sfnext-components` - component conventions
@@ -0,0 +1,45 @@
1
+ # State patterns and anti-patterns
2
+
3
+ ## Render scope should not exceed data-change scope
4
+
5
+ If a value changes often but only event handlers read it, do not store it in state or Context. Keep it in a `useRef` or read it at call time (for example `window.matchMedia(...).matches` inside the handler). Every state change re-renders the component and all consumers of a Context value.
6
+
7
+ Hidden subscribers: a component that subscribes to a store but renders nothing visible (an invisible drawer) still re-renders. Return `null` early or move the subscription to the component that renders the content.
8
+
9
+ ## Provider value stability
10
+
11
+ ```tsx
12
+ const value = useMemo(() => ({ items, isOpen }), [items, isOpen]);
13
+ const actions = useMemo(() => ({ open, close }), [open, close]); // useCallback inside
14
+ return (
15
+ <StateContext.Provider value={value}>
16
+ <ActionsContext.Provider value={actions}>{children}</ActionsContext.Provider>
17
+ </StateContext.Provider>
18
+ );
19
+ ```
20
+
21
+ Check each field read during render: if a consumer uses only `isOpen`, it still re-renders when `items` changes with one combined context. Split or use a selector.
22
+
23
+ ## Manager components
24
+
25
+ A component that only subscribes to data to perform a side effect (for example syncing the basket cookie, like `BasketCookieReconciler`) should render `null` and live as a leaf so the parent is not re-rendered.
26
+
27
+ ## Server versus client snapshots
28
+
29
+ For `useSyncExternalStore`, `getServerSnapshot` must equal what the first client render will read, or React warns and re-renders. Read browser-only data (storage, matchMedia) after mount in an effect and then write it into the store.
30
+
31
+ ## URL state
32
+
33
+ Prefer URL state for anything a shopper might share or reload. Changing search params triggers navigation and loader re-run; see `storefront-next:sfnext-revalidation` to skip re-runs for client-only params. Use `setSearchParams(next, { replace: true, preventScrollReset: true })` for high-frequency updates such as typing.
34
+
35
+ ## Cookies
36
+
37
+ Cookies written by the server are set from `action` or middleware responses. Use the helpers in `src/lib/cookie-utils.server.ts` (site-scoped names, domain resolution, `createCookie`). Do not store personal data or tokens in cookies readable by scripts. Consent state is covered by `storefront-next:sfnext-analytics-consent`.
38
+
39
+ ## Derived state
40
+
41
+ Compute derived values during render. `useMemo` is for expensive pure computations of UI-derived values; it does not fix transforming loader data in render (do that in the loader) and must not wrap promises.
42
+
43
+ ## Testing state
44
+
45
+ Render with the real provider in tests, or mock the hook module (`vi.mock('@/providers/basket')`). For stores, call the exported setters inside `act` and reset module state between tests. See `storefront-next:sfnext-testing`.