@tokenoftrust/storefront-runner 1.4.2-rc.0 → 2.0.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 (363) hide show
  1. package/apps/storefront/astro.config.mjs +3 -3
  2. package/apps/storefront/env.d.ts +54 -5
  3. package/apps/storefront/integrations/materialize-guard.mjs +12 -14
  4. package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
  5. package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
  6. package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
  7. package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
  8. package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
  9. package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
  10. package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
  11. package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
  12. package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
  13. package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
  14. package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
  15. package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
  16. package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
  17. package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
  18. package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
  19. package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
  20. package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
  21. package/apps/storefront/public/shared/commerce-chrome.css +20 -2
  22. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
  23. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
  24. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
  25. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
  26. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
  27. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
  28. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
  29. package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
  30. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
  31. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
  32. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
  33. package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
  34. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
  35. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
  36. package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
  37. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
  38. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
  39. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
  40. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
  41. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
  42. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
  43. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
  44. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
  45. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
  46. package/apps/storefront/src/components/Img.astro +1 -1
  47. package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
  48. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
  49. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
  50. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  51. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
  52. package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
  53. package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
  54. package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
  55. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
  56. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
  57. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
  58. package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
  59. package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
  60. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
  61. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
  62. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
  63. package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
  64. package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
  65. package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
  66. package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
  67. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
  68. package/apps/storefront/src/components/membership/TierCards.astro +2 -2
  69. package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
  70. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
  71. package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
  72. package/apps/storefront/src/config/devTenantSeed.ts +9 -9
  73. package/apps/storefront/src/config/resolver.ts +4 -4
  74. package/apps/storefront/src/layouts/Layout.astro +100 -20
  75. package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
  76. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
  77. package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
  78. package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
  79. package/apps/storefront/src/lib/admin/envelope.ts +1 -2
  80. package/apps/storefront/src/lib/apps/adminService.ts +10 -0
  81. package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
  82. package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
  83. package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
  84. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
  85. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
  86. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
  87. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
  88. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
  89. package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
  90. package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
  91. package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
  92. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
  93. package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
  94. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
  95. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
  96. package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
  97. package/apps/storefront/src/lib/apps/registryService.ts +264 -10
  98. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
  99. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
  100. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
  101. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
  102. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
  103. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
  104. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
  105. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
  106. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
  107. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
  108. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
  109. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
  110. package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
  111. package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
  112. package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
  113. package/apps/storefront/src/lib/blog/collection.ts +97 -10
  114. package/apps/storefront/src/lib/blog/markdown.ts +1 -1
  115. package/apps/storefront/src/lib/blog/provider.ts +57 -3
  116. package/apps/storefront/src/lib/blog/types.ts +9 -9
  117. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
  118. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
  119. package/apps/storefront/src/lib/chrome/model.ts +48 -253
  120. package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
  121. package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
  122. package/apps/storefront/src/lib/chrome/variant.ts +39 -0
  123. package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
  124. package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
  125. package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
  126. package/apps/storefront/src/lib/content/index.ts +5 -5
  127. package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
  128. package/apps/storefront/src/lib/content-edit/route.ts +1 -1
  129. package/apps/storefront/src/lib/d1/catalog.ts +12 -0
  130. package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
  131. package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
  132. package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
  133. package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
  134. package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
  135. package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
  136. package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
  137. package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
  138. package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
  139. package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
  140. package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
  141. package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
  142. package/apps/storefront/src/lib/edgeCache.ts +18 -6
  143. package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
  144. package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
  145. package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
  146. package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
  147. package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
  148. package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
  149. package/apps/storefront/src/lib/membership/model.ts +1 -2
  150. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
  151. package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
  152. package/apps/storefront/src/lib/newsletter/send.ts +3 -3
  153. package/apps/storefront/src/lib/previewReload.ts +57 -1
  154. package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
  155. package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
  156. package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
  157. package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
  158. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
  159. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
  160. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
  161. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
  162. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
  163. package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
  164. package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
  165. package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
  166. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
  167. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
  168. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
  169. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
  170. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
  171. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
  172. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
  173. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
  174. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
  175. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
  176. package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
  177. package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
  178. package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
  179. package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
  180. package/apps/storefront/src/lib/rawChrome.ts +125 -47
  181. package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
  182. package/apps/storefront/src/lib/seo/alternates.ts +1 -1
  183. package/apps/storefront/src/lib/social/notify.ts +4 -4
  184. package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
  185. package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
  186. package/apps/storefront/src/lib/subscription/model.ts +4 -4
  187. package/apps/storefront/src/lib/the-build/provider.ts +9 -4
  188. package/apps/storefront/src/middleware/index.ts +273 -51
  189. package/apps/storefront/src/pages/[...slug].astro +9 -4
  190. package/apps/storefront/src/pages/admin/review-release.astro +265 -0
  191. package/apps/storefront/src/pages/admin/select.astro +96 -0
  192. package/apps/storefront/src/pages/admin.astro +16 -2
  193. package/apps/storefront/src/pages/api/activity.ts +8 -8
  194. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
  195. package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
  196. package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
  197. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
  198. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
  199. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
  200. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
  201. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
  202. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
  203. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
  204. package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
  205. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
  206. package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
  207. package/apps/storefront/src/pages/blog/[slug].astro +5 -5
  208. package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
  209. package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
  210. package/apps/storefront/src/pages/blog/index.astro +2 -2
  211. package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
  212. package/apps/storefront/src/pages/cockpit.astro +61 -1362
  213. package/apps/storefront/src/pages/index.astro +10 -5
  214. package/apps/storefront/src/pages/llms.txt.ts +1 -1
  215. package/apps/storefront/src/pages/products/[handle].astro +7 -6
  216. package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
  217. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
  218. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
  219. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
  220. package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
  221. package/apps/storefront/src/pages/the-build/index.astro +2 -2
  222. package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
  223. package/apps/storefront/src/styles/admin.css +27 -1
  224. package/apps/storefront/tsconfig.json +1 -1
  225. package/package.json +1 -1
  226. package/packages/public-runtime/package.json +2 -1
  227. package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
  228. package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
  229. package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
  230. package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
  231. package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
  232. package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
  233. package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
  234. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
  235. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
  236. package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
  237. package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
  238. package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
  239. package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
  240. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
  241. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
  242. package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
  243. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
  244. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
  245. package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
  246. package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
  247. package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
  248. package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
  249. package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
  250. package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
  251. package/packages/public-runtime/src/activity/catalog.ts +41 -37
  252. package/packages/public-runtime/src/activity/event.ts +6 -6
  253. package/packages/public-runtime/src/activity/index.ts +2 -2
  254. package/packages/public-runtime/src/activity/redaction.ts +4 -4
  255. package/packages/public-runtime/src/candidate-index.ts +42 -8
  256. package/packages/public-runtime/src/checkout-orders.ts +2 -2
  257. package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
  258. package/packages/public-runtime/src/checkout-style.ts +7 -7
  259. package/packages/public-runtime/src/checkout.ts +3 -3
  260. package/packages/public-runtime/src/chrome.ts +777 -0
  261. package/packages/public-runtime/src/compliance-obligations.ts +310 -0
  262. package/packages/public-runtime/src/csp.ts +99 -0
  263. package/packages/public-runtime/src/customization-runtime.ts +32 -1
  264. package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
  265. package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
  266. package/packages/public-runtime/src/embed-catalog.mjs +40 -0
  267. package/packages/public-runtime/src/evidence.ts +288 -0
  268. package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
  269. package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
  270. package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
  271. package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
  272. package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
  273. package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
  274. package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
  275. package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
  276. package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
  277. package/packages/public-runtime/src/index.ts +16 -1
  278. package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
  279. package/packages/public-runtime/src/integration/assurance.ts +228 -0
  280. package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
  281. package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
  282. package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
  283. package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
  284. package/packages/public-runtime/src/integration/consent-config.ts +260 -0
  285. package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
  286. package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
  287. package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
  288. package/packages/public-runtime/src/integration/csp.ts +105 -0
  289. package/packages/public-runtime/src/integration/digest.ts +43 -0
  290. package/packages/public-runtime/src/integration/environment.ts +228 -0
  291. package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
  292. package/packages/public-runtime/src/integration/events.ts +701 -0
  293. package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
  294. package/packages/public-runtime/src/integration/index.ts +90 -0
  295. package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
  296. package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
  297. package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
  298. package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
  299. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
  300. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
  301. package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
  302. package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
  303. package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
  304. package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
  305. package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
  306. package/packages/public-runtime/src/integration/manifest.ts +470 -0
  307. package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
  308. package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
  309. package/packages/public-runtime/src/integration/observability.ts +451 -0
  310. package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
  311. package/packages/public-runtime/src/integration/phases.ts +182 -0
  312. package/packages/public-runtime/src/integration/plan.ts +112 -0
  313. package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
  314. package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
  315. package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
  316. package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
  317. package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
  318. package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
  319. package/packages/public-runtime/src/integration/render-phases.ts +267 -0
  320. package/packages/public-runtime/src/integration/resolve.ts +465 -0
  321. package/packages/public-runtime/src/integration/result.ts +239 -0
  322. package/packages/public-runtime/src/integration/secrets.ts +92 -0
  323. package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
  324. package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
  325. package/packages/public-runtime/src/integration/version.ts +92 -0
  326. package/packages/public-runtime/src/integration-capabilities.ts +520 -0
  327. package/packages/public-runtime/src/membership.ts +2 -2
  328. package/packages/public-runtime/src/raw-html-policy.ts +2 -4
  329. package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
  330. package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
  331. package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
  332. package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
  333. package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
  334. package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
  335. package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
  336. package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
  337. package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
  338. package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
  339. package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
  340. package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
  341. package/packages/public-runtime/src/static-bundle.ts +5 -5
  342. package/packages/public-runtime/src/tenant-assets.ts +143 -4
  343. package/packages/public-runtime/src/tenant.ts +45 -9
  344. package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
  345. package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
  346. package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
  347. package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
  348. package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
  349. package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
  350. package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
  351. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
  352. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
  353. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
  354. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
  355. package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
  356. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
  357. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
  358. package/scripts/dev/file-browser.mjs +2 -2
  359. package/scripts/dev/file-writer.mjs +2 -2
  360. package/scripts/dev/integration-guardrails.mjs +498 -0
  361. package/scripts/dev/safe-path.mjs +2 -2
  362. package/scripts/dev/tenant-source-root.mjs +1 -1
  363. package/scripts/tot-dev.mjs +2 -2
@@ -0,0 +1,119 @@
1
+ /**
2
+ * The shared "buffer-or-stream on `Accept`" plumbing for a long-running admin
3
+ * route. ONE emit-driven work function serves BOTH transports:
4
+ *
5
+ * - a non-streaming caller (the CLI, any `Accept: application/json` client) gets
6
+ * the buffered terminal JSON body + status the work resolved — `emit` is a no-op;
7
+ * - a streaming caller (`Accept: text/event-stream`, the admin Publish tab) gets
8
+ * each `emit(event, data)` as an SSE frame AS IT HAPPENS, then a terminal
9
+ * `result` event carrying the SAME payload the JSON body would have, then close.
10
+ *
11
+ * The work runs ONCE either way; the terminal payload + status are IDENTICAL. Only
12
+ * the delivery differs. This is the generalized form of the pattern
13
+ * `POST /api/preview/build` first shipped inline — extracted here so every
14
+ * long-running route (build / integrate / rebuild / ship) streams the same way
15
+ * without re-implementing the ReadableStream/encoder/close dance.
16
+ */
17
+
18
+ /**
19
+ * A buffered JSON Response — the non-streaming terminal body. Inlined (not imported
20
+ * from the change/grants controlplane) so this generic transport helper stays in the
21
+ * `lib/publish` render layer, respecting the renderer-no-controlplane boundary.
22
+ */
23
+ function jsonResponse(body: unknown, status = 200): Response {
24
+ return new Response(JSON.stringify(body), {
25
+ status,
26
+ headers: { "content-type": "application/json", "cache-control": "no-store" },
27
+ });
28
+ }
29
+
30
+ /** Emit one SSE event. A no-op in buffered (JSON) mode. */
31
+ export type SseEmit = (event: string, data: Record<string, unknown>) => void;
32
+
33
+ /** The terminal outcome of the work: the HTTP status + the JSON/`result` payload. */
34
+ export interface StreamWorkResult {
35
+ status: number;
36
+ payload: Record<string, unknown>;
37
+ }
38
+
39
+ /** True when the caller asked for an SSE stream (`Accept: text/event-stream`). */
40
+ export function wantsEventStream(request: Request): boolean {
41
+ return (request.headers.get("accept") || "").includes("text/event-stream");
42
+ }
43
+
44
+ /**
45
+ * Run `work` and deliver its terminal payload as either a buffered JSON Response
46
+ * (default) or a streamed SSE Response (when the caller sent
47
+ * `Accept: text/event-stream`). In stream mode each `emit` becomes an
48
+ * `event: <name>\ndata: <json>\n\n` frame; the terminal payload is sent as a
49
+ * `result` event and the stream closes. `onError` maps an UNEXPECTED throw (the
50
+ * work is expected to be fail-safe and return its own error payload) to a terminal
51
+ * result — in JSON mode it becomes the response body+status, in SSE mode the
52
+ * `result` event.
53
+ */
54
+ export async function respondBufferedOrStreamed(
55
+ request: Request,
56
+ work: (emit: SseEmit) => Promise<StreamWorkResult>,
57
+ opts?: { onError?: (error: unknown) => StreamWorkResult },
58
+ ): Promise<Response> {
59
+ const onError =
60
+ opts?.onError ??
61
+ ((error: unknown): StreamWorkResult => ({
62
+ status: 500,
63
+ payload: {
64
+ ok: false,
65
+ outcome: "failed",
66
+ errors: [`operation failed: ${error instanceof Error ? error.message : String(error)}`],
67
+ },
68
+ }));
69
+
70
+ // Buffered JSON mode — the CLI / any non-streaming caller. `emit` is a no-op; the
71
+ // work's own status + payload are returned verbatim (same contract as before).
72
+ if (!wantsEventStream(request)) {
73
+ try {
74
+ const { status, payload } = await work(() => {});
75
+ return jsonResponse(payload, status);
76
+ } catch (error) {
77
+ const { status, payload } = onError(error);
78
+ return jsonResponse(payload, status);
79
+ }
80
+ }
81
+
82
+ // SSE mode — stream each phase as it happens, then a terminal `result` event
83
+ // carrying the SAME payload the JSON body would have, then close. Workers stream a
84
+ // ReadableStream body natively — no infra.
85
+ const encoder = new TextEncoder();
86
+ const stream = new ReadableStream<Uint8Array>({
87
+ start(controller) {
88
+ const send: SseEmit = (event, data) => {
89
+ try {
90
+ controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
91
+ } catch {
92
+ /* controller already closed (client disconnected) — nothing to do */
93
+ }
94
+ };
95
+ void (async () => {
96
+ try {
97
+ const { payload } = await work(send);
98
+ send("result", payload);
99
+ } catch (error) {
100
+ send("result", onError(error).payload);
101
+ } finally {
102
+ try {
103
+ controller.close();
104
+ } catch {
105
+ /* already closed */
106
+ }
107
+ }
108
+ })();
109
+ },
110
+ });
111
+ return new Response(stream, {
112
+ status: 200,
113
+ headers: {
114
+ "content-type": "text/event-stream; charset=utf-8",
115
+ "cache-control": "no-cache, no-transform",
116
+ "x-accel-buffering": "no",
117
+ },
118
+ });
119
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Pure formatting for the admin Publish tab's shared SCROLLING LIVE-LOG console —
3
+ * the "CI build log" panel reused across ALL FOUR long-running flows (build /
4
+ * integrate / rebuild / ship). Where `buildStream.ts` owns the build-flight
5
+ * stage-RAIL (checkmarks), this owns the LOG: one human-readable, timestamped line
6
+ * appended per server-pushed `phase` event, then a terminal ✓/✗ line.
7
+ *
8
+ * It is transport-agnostic (it consumes already-parsed {@link SseEvent}s from
9
+ * `parseSseChunk`) and flow-agnostic: ONE phase→label map covers every phase any of
10
+ * the four routes emits, so the SAME console renders an integrate merge, a ship
11
+ * promote, or a build read with no per-flow branching. The terminal-line extractor
12
+ * reads the union of the four routes' honest result shapes (build's
13
+ * `ok`/`outcome`/`errors`, the queue's `ok`/`queueState`/`statusMessage`/`reason`,
14
+ * ship's `ok`/`state`/`message`) without any one flow's assumptions leaking in.
15
+ *
16
+ * The DOM wiring that appends these to a scrollable `<div>` and auto-scrolls is
17
+ * verified on a live preview, per this codebase's client-island convention (see
18
+ * `reviewReleaseWorkspace/controller.ts`). Only this pure formatting is unit-tested.
19
+ */
20
+ import type { SseEvent } from "./buildStream.js";
21
+
22
+ /**
23
+ * The human label for every `phase` a streamed route emits, across all four flows.
24
+ * Deliberately honest-but-neutral wording so the SAME line reads true whether the
25
+ * phase came from a candidate build or an aggregate rebuild.
26
+ */
27
+ export const STREAM_PHASE_LABEL: Record<string, string> = {
28
+ // Accept-to-preview (integrate) — the queue's merge boundary.
29
+ merging: "Merging your change into the shared preview…",
30
+ // Build (candidate) — head resolution.
31
+ resolving: "Locating the pull request's latest commit…",
32
+ // Shared reconcile boundaries (build candidate + aggregate integrate/rebuild).
33
+ reading: "Reading and validating your store's files…",
34
+ publishing: "Building and publishing this version…",
35
+ checking: "Re-running the combined evidence checks…",
36
+ // Publish-live (ship) — the orchestrator's post-gate boundaries.
37
+ pinning: "Pinning the exact reviewed build…",
38
+ promoting: "Promoting the reviewed build to your live channel…",
39
+ verifying: "Verifying your live store serves the new build…",
40
+ recording: "Recording the release in your ledger…",
41
+ };
42
+
43
+ /**
44
+ * The label for one phase: a server-supplied `label` wins (a route may send a more
45
+ * specific line, e.g. build's "Locating the latest commit for PR #12…"), else the
46
+ * shared map, else `null` for an unknown phase (the caller ignores it — never
47
+ * fabricates a line for a phase it doesn't understand).
48
+ */
49
+ export function phaseLogLabel(phase: string, serverLabel?: string | null): string | null {
50
+ if (serverLabel && serverLabel.trim()) return serverLabel.trim();
51
+ return STREAM_PHASE_LABEL[phase] ?? null;
52
+ }
53
+
54
+ /** `m:ss` elapsed, matching the dialog's own elapsed-clock format. Clamps negatives to 0. */
55
+ export function formatElapsed(ms: number): string {
56
+ const s = Math.max(0, Math.floor(ms / 1000));
57
+ return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
58
+ }
59
+
60
+ /**
61
+ * The log line for a `phase` event, or `null` to skip (unknown phase / not a phase
62
+ * event). Shape: `[m:ss] <label>`. Pure — the console appends the returned string.
63
+ */
64
+ export function logLineForPhase(
65
+ ev: SseEvent,
66
+ elapsedMs: number,
67
+ ): string | null {
68
+ if (ev.event !== "phase") return null;
69
+ let parsed: unknown = null;
70
+ try {
71
+ parsed = JSON.parse(ev.data);
72
+ } catch {
73
+ return null;
74
+ }
75
+ const phase = parsed && typeof (parsed as { phase?: unknown }).phase === "string"
76
+ ? (parsed as { phase: string }).phase
77
+ : null;
78
+ if (!phase) return null;
79
+ const serverLabel =
80
+ parsed && typeof (parsed as { label?: unknown }).label === "string"
81
+ ? (parsed as { label: string }).label
82
+ : null;
83
+ const label = phaseLogLabel(phase, serverLabel);
84
+ if (!label) return null;
85
+ return `[${formatElapsed(elapsedMs)}] ${label}`;
86
+ }
87
+
88
+ /** A terminal result across the four flows — the union of their honest shapes. */
89
+ export interface StreamTerminalResult {
90
+ ok?: boolean;
91
+ /** build */
92
+ outcome?: string | null;
93
+ errors?: string[] | null;
94
+ error?: string | null;
95
+ /** integrate/rebuild */
96
+ queueState?: string | null;
97
+ statusMessage?: string | null;
98
+ reason?: string | null;
99
+ /** ship */
100
+ state?: string | null;
101
+ message?: string | null;
102
+ }
103
+
104
+ /** True iff the terminal result is a clean success, reading each flow's own success signal. */
105
+ export function isStreamResultOk(result: StreamTerminalResult): boolean {
106
+ if (result.state !== undefined && result.state !== null) return result.state === "shipped";
107
+ if (result.ok === true) return true;
108
+ return result.outcome === "ok";
109
+ }
110
+
111
+ /** A SHORT one-line reason for a non-ok terminal (the full detail lives in the outcome phase). */
112
+ function streamResultReason(result: StreamTerminalResult): string {
113
+ const first = (v: string[] | null | undefined) => (v && v.length ? v[0] : "");
114
+ const candidate =
115
+ result.statusMessage ||
116
+ result.message ||
117
+ first(result.errors) ||
118
+ result.error ||
119
+ (result.outcome === "blocked" ? "a check blocked this build" : "") ||
120
+ result.reason ||
121
+ result.queueState ||
122
+ result.state ||
123
+ "it did not complete";
124
+ const line = String(candidate).replace(/\s+/g, " ").trim();
125
+ return line.length > 160 ? line.slice(0, 157) + "…" : line;
126
+ }
127
+
128
+ /**
129
+ * The terminal log line for a `result` event: `{ ok, line }`. `line` is
130
+ * `[m:ss] ✓ Done.` on success, `[m:ss] ✗ Stopped — <short reason>` otherwise. Pure.
131
+ */
132
+ export function terminalLogLine(
133
+ result: StreamTerminalResult,
134
+ elapsedMs: number,
135
+ ): { ok: boolean; line: string } {
136
+ const ok = isStreamResultOk(result);
137
+ const stamp = `[${formatElapsed(elapsedMs)}]`;
138
+ return {
139
+ ok,
140
+ line: ok ? `${stamp} ✓ Done.` : `${stamp} ✗ Stopped — ${streamResultReason(result)}`,
141
+ };
142
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The merchant-facing version counter — a monotonic, human "v1, v2, …" label for
3
+ * the tenant's published versions, DERIVED from the live-promotion history so it is
4
+ * never fabricated and never the raw SHA (which is meaningless to a merchant).
5
+ *
6
+ * A version's number is its rank by FIRST promotion to `live`: the earliest-ever
7
+ * promoted sha is v1, the next distinct sha is v2, and so on. This has the two
8
+ * properties merchants expect:
9
+ * - monotonic increasing — a newer publish always gets a higher number; and
10
+ * - stable for a given sha — a rollback re-points `live` at a prior version (per
11
+ * `PromotionEvent`'s contract a plain rollback is not even recorded, and a
12
+ * re-promote keeps the sha's ORIGINAL first-promotion rank), so "rolled back to
13
+ * v3" reads honestly and a subsequent fresh publish becomes v(N+1), never
14
+ * reusing a retired number.
15
+ *
16
+ * Pure (no I/O) so it unit-tests without a Worker/KV, and shared by both the
17
+ * live-version resolver (the "Live version" metric) and the versions-panel view so
18
+ * the two surfaces always agree on a version's number.
19
+ */
20
+ import type { PromotionEvent } from "@tot/public-runtime";
21
+
22
+ /**
23
+ * Map each promoted versionId → its 1-based version number, ranked by earliest
24
+ * promotion time. Input order is irrelevant (the rank is computed from the `at`
25
+ * timestamps), so it accepts the newest-first list `listPromotions` returns as-is.
26
+ * A tie on `at` between two distinct shas is broken by versionId for determinism.
27
+ */
28
+ export function assignVersionNumbers(promotions: PromotionEvent[]): Map<string, number> {
29
+ const earliest = new Map<string, string>();
30
+ for (const p of promotions) {
31
+ const seen = earliest.get(p.versionId);
32
+ if (seen == null || p.at < seen) earliest.set(p.versionId, p.at);
33
+ }
34
+ const ordered = [...earliest.entries()].sort((a, b) =>
35
+ a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0,
36
+ );
37
+ const numbers = new Map<string, number>();
38
+ ordered.forEach(([versionId], i) => numbers.set(versionId, i + 1));
39
+ return numbers;
40
+ }
41
+
42
+ /**
43
+ * The version number for one versionId, or `null` when nothing in the promotion
44
+ * history matches (an unpromoted/unknown sha — the label falls back to the raw SHA).
45
+ */
46
+ export function versionNumberFor(
47
+ promotions: PromotionEvent[],
48
+ versionId: string | null | undefined,
49
+ ): number | null {
50
+ if (!versionId) return null;
51
+ return assignVersionNumbers(promotions).get(versionId) ?? null;
52
+ }
@@ -1,22 +1,22 @@
1
1
  /**
2
- * Canonical RAW chrome renderer (unit F3).
2
+ * Canonical RAW chrome renderer.
3
3
  *
4
4
  * ONE platform-shipped chrome wrapper, driven entirely by a tenant's `chrome.json`
5
- * (typed `TenantChrome`, lib/chrome/model.ts) — CONTENT ONLY. It replaces the three
5
+ * (typed `ChromeConfig`, lib/chrome/model.ts) — CONTENT ONLY. It replaces the three
6
6
  * divergent, hand-authored `content/<tenant>/chrome.html` documents with a single
7
7
  * generator, so chrome lives in ONE place and a tenant supplies only data. The
8
8
  * output is a full wrapper document carrying the splice markers the rawMarketingHtml
9
9
  * pipeline understands (`<!--PAGE_HEAD-->` / `<!--PAGE_BODY-->`), so body-only page
10
10
  * fragments get wrapped exactly as before (see lib/rawMarketingHtml.ts,
11
- * wrapFragmentInChrome). Per decisions:
12
- * - raw-chrome-single-stylesheet: the wrapper LINKS the platform stylesheet
13
- * (/shared/commerce-chrome.css, unit F2) and emits NO inline chrome CSS — every
11
+ * wrapFragmentInChrome). Two governing constraints:
12
+ * - Single stylesheet: the wrapper LINKS the platform stylesheet
13
+ * (/shared/commerce-chrome.css) and emits NO inline chrome CSS — every
14
14
  * color/space/type comes from theme tokens the tenant's linked *-theme.css sets.
15
- * - materialization-compat: this is a deterministic server-side string; the spliced
15
+ * - Materialization-compatible: this is a deterministic server-side string; the spliced
16
16
  * document is a stable, self-contained artifact SEOMaterialize can capture (the
17
17
  * behavior JS is progressive enhancement over already-rendered markup).
18
18
  *
19
- * The markup mirrors, class-for-class, the semantic markup the F2 stylesheet styles
19
+ * The markup mirrors, class-for-class, the semantic markup the shared chrome stylesheet styles
20
20
  * (`.site-header`, `.header-row`, `.primary-nav`, `.mega`, `.site-footer`, …) — the
21
21
  * SAME DOM the retired tenant chrome.html files carried, now generated from config.
22
22
  *
@@ -25,17 +25,23 @@
25
25
  * defense-in-depth, not a sanitizer for untrusted input.
26
26
  */
27
27
  import { CHROME_BODY_MARKER, CHROME_HEAD_MARKER } from "./rawMarketingHtml.js";
28
+ import { isCurrentNavHref } from "./chrome/navCurrent.js";
28
29
  import type {
29
30
  ChromeAgeGate,
30
31
  ChromeAnnouncement,
32
+ ChromeAssignments,
31
33
  ChromeBrand,
34
+ ChromeConfig,
32
35
  ChromeFooter,
36
+ ChromeHeader,
33
37
  ChromeLink,
34
38
  ChromeNavItem,
35
39
  ChromeUtilityNav,
40
+ HeaderVariant,
36
41
  SocialLink,
37
- TenantChrome,
38
42
  } from "./chrome/model.js";
43
+ import { resolveChromeAssignment } from "./chrome/model.js";
44
+ import { resolveHeaderVariant } from "./chrome/variant.js";
39
45
 
40
46
  /** HTML-escape text/attribute content. Covers the five significant characters. */
41
47
  function esc(value: string): string {
@@ -177,8 +183,18 @@ function renderUtilityNav(u: ChromeUtilityNav): string {
177
183
  );
178
184
  }
179
185
 
180
- /** A primary-nav item — flat link, or a mega panel when it has columns/children. */
181
- function renderNavItem(item: ChromeNavItem): string {
186
+ /**
187
+ * A primary-nav item flat link, or a mega panel when it has columns/children.
188
+ *
189
+ * `currentPath`, when given, is the tenant-relative request path (e.g. "/" or
190
+ * "/collections/all") in the SAME unprefixed form the config's own `href`
191
+ * values are authored in — basePath prefixing happens later, as a single
192
+ * pass over the whole composed document (rawMarketingHtml.ts,
193
+ * prefixTenantLinks), so comparing here before that pass keeps both sides
194
+ * unprefixed and exact-match-only (see navCurrent.ts). Omitted ⇒ no item is
195
+ * ever marked current (callers that don't know the request path, e.g. tests).
196
+ */
197
+ function renderNavItem(item: ChromeNavItem, currentPath?: string): string {
182
198
  const columns = item.columns?.length
183
199
  ? item.columns
184
200
  : item.children?.length
@@ -189,14 +205,23 @@ function renderNavItem(item: ChromeNavItem): string {
189
205
  const badge = item.badge ? `<span class="new-badge">${esc(item.badge)}</span>` : "";
190
206
  const caret = hasPanel ? `<span class="caret" aria-hidden="true">▾</span>` : "";
191
207
  const haspopup = hasPanel ? ' aria-haspopup="true"' : "";
192
- const link = `<a${linkClass} href="${esc(item.href)}"${haspopup}>${esc(item.label)}${badge}${caret}</a>`;
208
+ const itemCurrent = currentPath !== undefined && isCurrentNavHref(currentPath, item.href);
209
+ const ariaCurrent = itemCurrent ? ' aria-current="page"' : "";
210
+ const link = `<a${linkClass} href="${esc(item.href)}"${haspopup}${ariaCurrent}>${esc(item.label)}${badge}${caret}</a>`;
193
211
  if (!hasPanel) return `<li class="nav-item">${link}</li>`;
194
212
 
195
213
  const wide = columns.length > 1 ? " mega--wide" : "";
196
214
  const cols = columns
197
215
  .map((c) => {
198
216
  const heading = c.title ? `<h4>${esc(c.title)}</h4>` : "";
199
- const links = c.links.map((l) => `<a href="${esc(l.href)}"${relFor(l)}>${esc(l.label)}</a>`).join("");
217
+ const links = c.links
218
+ .map((l) => {
219
+ const childCurrent =
220
+ currentPath !== undefined && isCurrentNavHref(currentPath, l.href, l.external);
221
+ const childAriaCurrent = childCurrent ? ' aria-current="page"' : "";
222
+ return `<a href="${esc(l.href)}"${relFor(l)}${childAriaCurrent}>${esc(l.label)}</a>`;
223
+ })
224
+ .join("");
200
225
  return `<div class="mega__col">${heading}${links}</div>`;
201
226
  })
202
227
  .join("");
@@ -204,46 +229,73 @@ function renderNavItem(item: ChromeNavItem): string {
204
229
  return `<li class="nav-item">${link}${mega}</li>`;
205
230
  }
206
231
 
207
- function renderHeader(chrome: TenantChrome): string {
208
- const search = chrome.search
209
- ? `<form class="header-search" action="${esc(chrome.search.action ?? "/search")}" method="get" role="search">\n` +
210
- ` <label class="sr-only" for="hdr-q">${esc(chrome.search.label ?? "Search")}</label>\n` +
211
- ` <input id="hdr-q" type="search" name="q" placeholder="${esc(chrome.search.placeholder ?? "Search")}" autocomplete="off">\n` +
232
+ /**
233
+ * `variant` is the RESOLVED header variant for this page — the page-assignment
234
+ * override applied over `header.variant` by the caller. It is passed in rather
235
+ * than read off `header` so the rendered markup and its `data-variant` can
236
+ * never disagree with the variant the assignment actually decided.
237
+ */
238
+ function renderHeader(header: ChromeHeader, variant: HeaderVariant, currentPath?: string): string {
239
+ const search = header.search
240
+ ? `<form class="header-search" action="${esc(header.search.action ?? "/search")}" method="get" role="search">\n` +
241
+ ` <label class="sr-only" for="hdr-q">${esc(header.search.label ?? "Search")}</label>\n` +
242
+ ` <input id="hdr-q" type="search" name="q" placeholder="${esc(header.search.placeholder ?? "Search")}" autocomplete="off">\n` +
212
243
  ` <button type="submit" aria-label="Search">${ICON_SEARCH}</button>\n` +
213
244
  ` </form>`
214
245
  : "";
215
- const memberCue = chrome.memberCue
216
- ? `<a class="member-cue" href="${esc(chrome.memberCue.href)}" aria-label="${esc(chrome.memberCue.label)}">${esc(chrome.memberCue.label)}</a>`
246
+ const memberCue = header.memberCue
247
+ ? `<a class="member-cue" href="${esc(header.memberCue.href)}" aria-label="${esc(header.memberCue.label)}">${esc(header.memberCue.label)}</a>`
217
248
  : "";
218
- const account = chrome.accountHref
219
- ? `<a href="${esc(chrome.accountHref)}" aria-label="Account">${ICON_ACCOUNT}</a>`
249
+ const account = header.accountHref
250
+ ? `<a href="${esc(header.accountHref)}" aria-label="Account">${ICON_ACCOUNT}</a>`
220
251
  : "";
221
- const cart = chrome.cartHref
222
- ? `<a class="cart" href="${esc(chrome.cartHref)}" aria-label="Cart">${ICON_CART}<span class="count">0</span></a>`
252
+ const cart = header.cartHref
253
+ ? `<a class="cart" href="${esc(header.cartHref)}" aria-label="Cart">${ICON_CART}<span class="count">0</span></a>`
223
254
  : "";
224
- const hamburger =
225
- `<button class="hamburger" id="hamburger" aria-label="Toggle navigation menu" aria-expanded="false" aria-controls="primaryNav"><span></span><span></span><span></span></button>`;
226
- const navItems = chrome.nav.map(renderNavItem).join("\n ");
255
+ // "minimal" (e.g. checkout/policy pages): brand + a reduced action set only —
256
+ // no primary nav bar, no search, no mobile-menu toggle. "primary": the full header.
257
+ const minimal = variant === "minimal";
258
+ // Header CTAs, on the same `!minimal` rule the Astro SiteHeader applies, so the
259
+ // two surfaces render the same governed config identically. Each carries its
260
+ // stable `id` as `data-cta-id`, so the governed identity is present in the
261
+ // served DOM rather than implied by label or href.
262
+ const ctas =
263
+ minimal || !header.ctas?.length
264
+ ? ""
265
+ : header.ctas
266
+ .map(
267
+ (c) =>
268
+ `<a class="header-cta header-cta--${esc(c.variant ?? "primary")}" ` +
269
+ `data-cta-id="${esc(c.id)}" href="${esc(c.href)}">${esc(c.label)}</a>`,
270
+ )
271
+ .join("");
272
+ const hamburger = minimal
273
+ ? ""
274
+ : `<button class="hamburger" id="hamburger" aria-label="Toggle navigation menu" aria-expanded="false" aria-controls="primaryNav"><span></span><span></span><span></span></button>`;
275
+ const navItems = header.nav.map((item) => renderNavItem(item, currentPath)).join("\n ");
276
+ const navBar = minimal
277
+ ? ""
278
+ : ` <nav class="primary-nav-bar" aria-label="Primary">\n` +
279
+ ` <div class="container">\n` +
280
+ ` <nav class="primary-nav" id="primaryNav">\n` +
281
+ ` <ul>\n ${navItems}\n </ul>\n` +
282
+ ` </nav>\n` +
283
+ ` </div>\n` +
284
+ ` </nav>\n`;
227
285
  return (
228
- `<header class="site-header">\n` +
286
+ `<header class="site-header" data-variant="${esc(variant)}">\n` +
229
287
  ` <div class="container header-row">\n` +
230
- ` ${renderBrand(chrome.brand)}\n` +
231
- (search ? ` ${search}\n` : "") +
232
- ` <div class="header-actions">${memberCue}${account}${cart}${hamburger}</div>\n` +
288
+ ` ${renderBrand(header.brand)}\n` +
289
+ (!minimal && search ? ` ${search}\n` : "") +
290
+ ` <div class="header-actions">${memberCue}${ctas}${account}${cart}${hamburger}</div>\n` +
233
291
  ` </div>\n` +
234
- ` <nav class="primary-nav-bar" aria-label="Primary">\n` +
235
- ` <div class="container">\n` +
236
- ` <nav class="primary-nav" id="primaryNav">\n` +
237
- ` <ul>\n ${navItems}\n </ul>\n` +
238
- ` </nav>\n` +
239
- ` </div>\n` +
240
- ` </nav>\n` +
292
+ navBar +
241
293
  `</header>`
242
294
  );
243
295
  }
244
296
 
245
297
  /**
246
- * Age-gate config, embedded as JSON (unit C1). The CHROME_BEHAVIOR_JS mechanism
298
+ * Age-gate config, embedded as JSON. The CHROME_BEHAVIOR_JS mechanism
247
299
  * reads this and builds the dialog itself inside a closed shadow root — the same
248
300
  * tamper-resistance pattern components/islands/IsolatedAgeGate.tsx uses for
249
301
  * component pages (see its header comment). `<` is escaped so an authored value
@@ -335,9 +387,16 @@ function renderFooter(footer: ChromeFooter): string {
335
387
  * — a single canonical source, never copy-pasted per tenant. Each block is guarded on
336
388
  * the presence of its elements, so a wrapper that omits the announce bar / age gate
337
389
  * runs the remaining behavior harmlessly. Progressive enhancement: the markup already
338
- * works without it (nav is visible, gate defaults open only when JS enables it).
390
+ * works without it (nav is a normal in-flow stacked list at mobile widths — see the
391
+ * `.js-nav` gate in commerce-chrome.css's max-width:860px block — and the age gate
392
+ * defaults open only when JS enables it).
339
393
  */
340
394
  export const CHROME_BEHAVIOR_JS = `(function () {
395
+ // Marks the document as JS-enabled so commerce-chrome.css can turn the
396
+ // always-reachable stacked mobile nav into an off-canvas drawer (progressive
397
+ // enhancement, not a requirement — see the .js-nav rules there).
398
+ document.documentElement.classList.add('js-nav');
399
+
341
400
  // Mobile nav drawer
342
401
  var burger = document.getElementById('hamburger');
343
402
  var nav = document.getElementById('primaryNav');
@@ -367,7 +426,7 @@ export const CHROME_BEHAVIOR_JS = `(function () {
367
426
  }
368
427
  }
369
428
 
370
- // 21+ age gate — shadow-isolated (unit C1). Only the host <div> lives in light
429
+ // 21+ age gate — shadow-isolated. Only the host <div> lives in light
371
430
  // DOM (data-tot-compliance, the same marker components/islands/IsolatedAgeGate.tsx
372
431
  // uses); its contents sit behind a closed shadow root, so tenant CSS/JS can't
373
432
  // select, restyle, or hide the dialog. A MutationObserver re-asserts the host the
@@ -444,11 +503,11 @@ export const CHROME_BEHAVIOR_JS = `(function () {
444
503
  }
445
504
  })();`;
446
505
 
447
- /** The platform-shipped chrome stylesheet every rendered wrapper links (unit F2). */
506
+ /** The platform-shipped chrome stylesheet every rendered wrapper links. */
448
507
  export const SHARED_CHROME_STYLESHEET = "/shared/commerce-chrome.css";
449
508
 
450
509
  /**
451
- * The platform-shipped RAW-PAGE-BODY marketing stylesheet (unit F5). Linked only
510
+ * The platform-shipped RAW-PAGE-BODY marketing stylesheet. Linked only
452
511
  * when a tenant opts in (chrome.sharedMarketingCss) and AFTER the chrome sheet, so
453
512
  * its page-level variants (e.g. .sec-head) win over chrome's home-section ones.
454
513
  */
@@ -480,8 +539,22 @@ function faviconMime(href: string): string {
480
539
  * `chromeWrapper` so a body-only fragment is spliced in and the whole thing is
481
540
  * nonce-stamped + base-path-prefixed. Optional sections (age gate, announcement,
482
541
  * utility nav, member cue, footer extras) render only when their config is present.
542
+ *
543
+ * `currentPath`, when given, is the tenant-relative request path in the same
544
+ * unprefixed form nav `href`s are authored in (see renderNavItem) — drives
545
+ * `aria-current="page"` on the primary-nav link that exactly matches it.
546
+ *
547
+ * `assignments`, when given, is the tenant's page-assignment manifest: the
548
+ * entry matching `currentPath` overrides `chrome.header.variant` for THIS page
549
+ * only. Resolution happens once, here, through the same fail-closed
550
+ * resolveHeaderVariant the Layout.astro prop path uses, and the resolved value
551
+ * is what reaches the markup.
483
552
  */
484
- export function renderRawChrome(chrome: TenantChrome): string {
553
+ export function renderRawChrome(
554
+ chrome: ChromeConfig,
555
+ currentPath?: string,
556
+ assignments?: ChromeAssignments | null,
557
+ ): string {
485
558
  const lang = esc(chrome.lang ?? "en");
486
559
  const head = chrome.head ?? {};
487
560
  const headLinks = [
@@ -491,20 +564,25 @@ export function renderRawChrome(chrome: TenantChrome): string {
491
564
  : "",
492
565
  ...(head.themeStylesheets ?? []).map((h) => `<link rel="stylesheet" href="${esc(h)}">`),
493
566
  `<link rel="stylesheet" href="${SHARED_CHROME_STYLESHEET}">`,
494
- // Opt-in raw-page-body layer, after the chrome sheet so its variants win (F5).
567
+ // Opt-in raw-page-body layer, after the chrome sheet so its variants win.
495
568
  chrome.sharedMarketingCss ? `<link rel="stylesheet" href="${SHARED_MARKETING_STYLESHEET}">` : "",
496
569
  ]
497
570
  .filter(Boolean)
498
571
  .join("\n");
499
572
 
573
+ const headerVariant = resolveHeaderVariant(
574
+ resolveChromeAssignment(assignments, currentPath)?.headerVariant,
575
+ chrome.header.variant,
576
+ );
577
+
500
578
  const skipHref = esc(chrome.skipLink?.href ?? "#main");
501
579
  const skipLabel = esc(chrome.skipLink?.label ?? "Skip to main content");
502
580
 
503
581
  const sections = [
504
582
  chrome.warningBar ? `<div class="topbar-warning"><p>${esc(chrome.warningBar)}</p></div>` : "",
505
- chrome.announcement ? renderAnnouncement(chrome.announcement) : "",
506
- chrome.utilityNav ? renderUtilityNav(chrome.utilityNav) : "",
507
- renderHeader(chrome),
583
+ chrome.header.announcement ? renderAnnouncement(chrome.header.announcement) : "",
584
+ chrome.header.utilityNav ? renderUtilityNav(chrome.header.utilityNav) : "",
585
+ renderHeader(chrome.header, headerVariant, currentPath),
508
586
  ].filter(Boolean);
509
587
 
510
588
  const trailing = [