@tokenoftrust/storefront-runner 1.4.2-rc.0 → 2.0.1

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 (370) hide show
  1. package/apps/storefront/astro.config.mjs +3 -3
  2. package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +8 -3
  3. package/apps/storefront/env.d.ts +54 -5
  4. package/apps/storefront/integrations/materialize-guard.mjs +12 -14
  5. package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
  6. package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
  7. package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
  8. package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
  9. package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
  10. package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
  11. package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
  12. package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
  13. package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
  14. package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
  15. package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
  16. package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
  17. package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
  18. package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
  19. package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
  20. package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
  21. package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
  22. package/apps/storefront/public/shared/commerce-chrome.css +20 -2
  23. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
  24. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
  25. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
  26. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
  27. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
  28. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
  29. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
  30. package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
  31. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
  32. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
  33. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
  34. package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
  35. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
  36. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
  37. package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
  38. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
  39. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
  40. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
  41. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
  42. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
  43. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
  44. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
  45. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
  46. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
  47. package/apps/storefront/src/components/Img.astro +1 -1
  48. package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
  49. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
  50. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
  51. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  52. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
  53. package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
  54. package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
  55. package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
  56. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
  57. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
  58. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
  59. package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
  60. package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
  61. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
  62. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
  63. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
  64. package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
  65. package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
  66. package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
  67. package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
  68. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
  69. package/apps/storefront/src/components/membership/TierCards.astro +2 -2
  70. package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
  71. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
  72. package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
  73. package/apps/storefront/src/config/devTenantSeed.ts +9 -9
  74. package/apps/storefront/src/config/resolver.ts +18 -10
  75. package/apps/storefront/src/layouts/Layout.astro +100 -20
  76. package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
  77. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
  78. package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
  79. package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
  80. package/apps/storefront/src/lib/admin/envelope.ts +1 -2
  81. package/apps/storefront/src/lib/apps/adminService.ts +10 -0
  82. package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
  83. package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
  84. package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
  85. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
  86. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
  87. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
  88. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
  89. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
  90. package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
  91. package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
  92. package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
  93. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
  94. package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
  95. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
  96. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
  97. package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
  98. package/apps/storefront/src/lib/apps/registryService.ts +264 -10
  99. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
  100. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
  101. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
  102. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
  103. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
  104. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
  105. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
  106. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
  107. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
  108. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
  109. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
  110. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
  111. package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
  112. package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
  113. package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
  114. package/apps/storefront/src/lib/blog/collection.ts +114 -17
  115. package/apps/storefront/src/lib/blog/markdown.ts +1 -1
  116. package/apps/storefront/src/lib/blog/provider.ts +76 -9
  117. package/apps/storefront/src/lib/blog/types.ts +9 -9
  118. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
  119. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
  120. package/apps/storefront/src/lib/chrome/model.ts +48 -253
  121. package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
  122. package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
  123. package/apps/storefront/src/lib/chrome/variant.ts +39 -0
  124. package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
  125. package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
  126. package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
  127. package/apps/storefront/src/lib/content/index.ts +5 -5
  128. package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
  129. package/apps/storefront/src/lib/content-edit/route.ts +1 -1
  130. package/apps/storefront/src/lib/d1/catalog.ts +12 -0
  131. package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
  132. package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
  133. package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
  134. package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
  135. package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
  136. package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
  137. package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
  138. package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
  139. package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
  140. package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
  141. package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
  142. package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
  143. package/apps/storefront/src/lib/edgeCache.ts +18 -6
  144. package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
  145. package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
  146. package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
  147. package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
  148. package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
  149. package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
  150. package/apps/storefront/src/lib/membership/model.ts +1 -2
  151. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
  152. package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
  153. package/apps/storefront/src/lib/newsletter/send.ts +3 -3
  154. package/apps/storefront/src/lib/previewReload.ts +57 -1
  155. package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
  156. package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
  157. package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
  158. package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
  159. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
  160. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
  161. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
  162. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
  163. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
  164. package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
  165. package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
  166. package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
  167. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
  168. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
  169. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
  170. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
  171. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
  172. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
  173. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
  174. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
  175. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
  176. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
  177. package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
  178. package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
  179. package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
  180. package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
  181. package/apps/storefront/src/lib/rawChrome.ts +125 -47
  182. package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
  183. package/apps/storefront/src/lib/seo/alternates.ts +1 -1
  184. package/apps/storefront/src/lib/social/notify.ts +4 -4
  185. package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
  186. package/apps/storefront/src/lib/storyblok/provider.ts +203 -69
  187. package/apps/storefront/src/lib/subscription/model.ts +4 -4
  188. package/apps/storefront/src/lib/the-build/provider.ts +9 -4
  189. package/apps/storefront/src/middleware/index.ts +273 -51
  190. package/apps/storefront/src/pages/[...slug].astro +9 -4
  191. package/apps/storefront/src/pages/admin/review-release.astro +265 -0
  192. package/apps/storefront/src/pages/admin/select.astro +96 -0
  193. package/apps/storefront/src/pages/admin.astro +16 -2
  194. package/apps/storefront/src/pages/api/activity.ts +8 -8
  195. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
  196. package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
  197. package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
  198. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
  199. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
  200. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
  201. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
  202. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
  203. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
  204. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
  205. package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
  206. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
  207. package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
  208. package/apps/storefront/src/pages/blog/[slug].astro +5 -5
  209. package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
  210. package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
  211. package/apps/storefront/src/pages/blog/index.astro +2 -2
  212. package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
  213. package/apps/storefront/src/pages/cockpit.astro +61 -1362
  214. package/apps/storefront/src/pages/index.astro +10 -5
  215. package/apps/storefront/src/pages/llms.txt.ts +1 -1
  216. package/apps/storefront/src/pages/products/[handle].astro +7 -6
  217. package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
  218. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +42 -31
  219. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +8 -3
  220. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +6 -2
  221. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +6 -2
  222. package/apps/storefront/src/pages/style-guide/index.astro +19 -5
  223. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
  224. package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
  225. package/apps/storefront/src/pages/the-build/index.astro +2 -2
  226. package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
  227. package/apps/storefront/src/styles/admin.css +27 -1
  228. package/apps/storefront/src/themes/tenants/index.ts +19 -7
  229. package/apps/storefront/tsconfig.json +1 -1
  230. package/package.json +1 -1
  231. package/packages/public-runtime/package.json +2 -1
  232. package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
  233. package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
  234. package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
  235. package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
  236. package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
  237. package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
  238. package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
  239. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
  240. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
  241. package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
  242. package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
  243. package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
  244. package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
  245. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
  246. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
  247. package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
  248. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
  249. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
  250. package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
  251. package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
  252. package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
  253. package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
  254. package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
  255. package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
  256. package/packages/public-runtime/src/activity/catalog.ts +41 -37
  257. package/packages/public-runtime/src/activity/event.ts +6 -6
  258. package/packages/public-runtime/src/activity/index.ts +2 -2
  259. package/packages/public-runtime/src/activity/redaction.ts +4 -4
  260. package/packages/public-runtime/src/candidate-index.ts +42 -8
  261. package/packages/public-runtime/src/checkout-orders.ts +2 -2
  262. package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
  263. package/packages/public-runtime/src/checkout-style.ts +7 -7
  264. package/packages/public-runtime/src/checkout.ts +3 -3
  265. package/packages/public-runtime/src/chrome.ts +777 -0
  266. package/packages/public-runtime/src/compliance-obligations.ts +310 -0
  267. package/packages/public-runtime/src/csp.ts +99 -0
  268. package/packages/public-runtime/src/customization-runtime.ts +32 -1
  269. package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
  270. package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
  271. package/packages/public-runtime/src/embed-catalog.mjs +40 -0
  272. package/packages/public-runtime/src/evidence.ts +288 -0
  273. package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
  274. package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
  275. package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
  276. package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
  277. package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
  278. package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
  279. package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
  280. package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
  281. package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
  282. package/packages/public-runtime/src/index.ts +16 -1
  283. package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
  284. package/packages/public-runtime/src/integration/assurance.ts +228 -0
  285. package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
  286. package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
  287. package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
  288. package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
  289. package/packages/public-runtime/src/integration/consent-config.ts +260 -0
  290. package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
  291. package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
  292. package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
  293. package/packages/public-runtime/src/integration/csp.ts +105 -0
  294. package/packages/public-runtime/src/integration/digest.ts +43 -0
  295. package/packages/public-runtime/src/integration/environment.ts +228 -0
  296. package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
  297. package/packages/public-runtime/src/integration/events.ts +701 -0
  298. package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
  299. package/packages/public-runtime/src/integration/index.ts +90 -0
  300. package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
  301. package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
  302. package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
  303. package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
  304. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
  305. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
  306. package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
  307. package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
  308. package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
  309. package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
  310. package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
  311. package/packages/public-runtime/src/integration/manifest.ts +470 -0
  312. package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
  313. package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
  314. package/packages/public-runtime/src/integration/observability.ts +451 -0
  315. package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
  316. package/packages/public-runtime/src/integration/phases.ts +182 -0
  317. package/packages/public-runtime/src/integration/plan.ts +112 -0
  318. package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
  319. package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
  320. package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
  321. package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
  322. package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
  323. package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
  324. package/packages/public-runtime/src/integration/render-phases.ts +267 -0
  325. package/packages/public-runtime/src/integration/resolve.ts +465 -0
  326. package/packages/public-runtime/src/integration/result.ts +239 -0
  327. package/packages/public-runtime/src/integration/secrets.ts +92 -0
  328. package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
  329. package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
  330. package/packages/public-runtime/src/integration/version.ts +92 -0
  331. package/packages/public-runtime/src/integration-capabilities.ts +520 -0
  332. package/packages/public-runtime/src/membership.ts +2 -2
  333. package/packages/public-runtime/src/raw-html-policy.ts +2 -4
  334. package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
  335. package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
  336. package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
  337. package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
  338. package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
  339. package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
  340. package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
  341. package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
  342. package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
  343. package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
  344. package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
  345. package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
  346. package/packages/public-runtime/src/static-bundle.ts +5 -5
  347. package/packages/public-runtime/src/tenant-assets.ts +143 -4
  348. package/packages/public-runtime/src/tenant.ts +136 -9
  349. package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
  350. package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
  351. package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
  352. package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
  353. package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
  354. package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
  355. package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
  356. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
  357. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
  358. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
  359. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
  360. package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
  361. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
  362. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
  363. package/scripts/build/copy-tenant-assets.mjs +12 -5
  364. package/scripts/dev/ai-edit.mjs +2 -1
  365. package/scripts/dev/file-browser.mjs +2 -2
  366. package/scripts/dev/file-writer.mjs +2 -2
  367. package/scripts/dev/integration-guardrails.mjs +498 -0
  368. package/scripts/dev/safe-path.mjs +2 -2
  369. package/scripts/dev/tenant-source-root.mjs +3 -2
  370. package/scripts/tot-dev.mjs +5 -4
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * - OPEN CHANGE QUEUE from GET /api/changes ({ current, changes }) with
8
8
  * per-change reconcile/compliance state, preview link, head sha, and age.
9
- * - ACCEPT TO PREVIEW (POST /api/changes/integrate — b11's accept-to-preview
9
+ * - ACCEPT TO PREVIEW (POST /api/changes/integrate — the accept-to-preview
10
10
  * split; advances the shared `preview` aggregate, does NOT go live) and
11
11
  * REJECT (with a note) per change. Going live is the aggregate card's
12
12
  * exact-digest PUBLISH (POST /api/changes/ship), never a per-change accept.
@@ -23,7 +23,7 @@
23
23
  * OWNER_CAPABILITIES) — others see the queue read-only with a sign-in note.
24
24
  * - Non-premium tenants see the go-live control as a PAYWALL UPSELL — never
25
25
  * hidden, never "coming soon" (recorded product direction). The
26
- * entitlement signal is the existing premium model (D4): a custom-domain
26
+ * entitlement signal is the existing premium model: a custom-domain
27
27
  * tenant is premium/pilot; a `<label>.tokenoftrust.store` self-serve store
28
28
  * is not. TODO(paywall workstream): replace with the real billing
29
29
  * entitlement when one exists.
@@ -32,9 +32,10 @@ import { readViewerSession } from "@/lib/auth/route";
32
32
  import { withBase } from "@/lib/basePath";
33
33
  import { readEnv } from "@/lib/env";
34
34
  import { resolveViewerShipCapability } from "@/lib/change";
35
- import { resolveLiveVersion } from "@/lib/change/liveVersion";
36
- import { isPublicSiteCurrent } from "@/lib/change/liveDigest";
35
+ import { resolveLiveVersion } from "@/lib/change/views/live-version";
36
+ import { isPublicSiteCurrent, staticLiveManifestUrl } from "@/lib/change/views/live-digest";
37
37
  import { isHostedStoreDomain, isInternalTestTenant } from "@tot/public-runtime";
38
+ import { mintPreviewShareToken } from "@/lib/preview/previewShareLink";
38
39
 
39
40
  const { envelope } = Astro.props;
40
41
  const { tenant, basePath } = Astro.locals;
@@ -51,7 +52,7 @@ const viewerCapability =
51
52
  // A signed-in DEVELOPER (not owner/admin) may hold an explicit, owner-revocable
52
53
  // ship-on-behalf grant. Resolve it LIVE from the MCP subject-capability store —
53
54
  // NEVER the coarse role, NEVER a client header — so a ship-granted developer sees
54
- // the queue + Go-live/Reject/Retire. This MIRRORS the server (resolveShipSession /
55
+ // the queue + Go-live/Reject. This MIRRORS the server (resolveShipSession /
55
56
  // effectiveShipCapability); the server re-authorizes every action. UX-only.
56
57
  let viewerCanShip = viewerCapability === "owner" || viewerCapability === "admin";
57
58
  if (!viewerCanShip && Astro.locals.viewer?.email) {
@@ -77,13 +78,13 @@ const internalTestTenant = isInternalTestTenant(tenant);
77
78
  const staticPublishEnabled = Boolean(
78
79
  tenant.staticPublish && tenant.staticPublish.enabled !== false,
79
80
  );
80
- // Presence-only signal for the T5 dispatch sink (never the value).
81
+ // Presence-only signal for the dispatch sink (never the value).
81
82
  const dispatchConfigured = Boolean((await readEnv("WWW_PUBLISH_DISPATCH_TOKEN"))?.trim());
82
83
 
83
84
  // "What does the public site actually serve right now?" — the read-back half of
84
85
  // the static-publish seam (resolveLiveVersion's staticLiveDigest), computed here
85
86
  // so it's ALWAYS visible rather than only living in GET /api/changes JSON. This is
86
- // the exact gap that let tokenoftrust.com's public site (preview.tokenoftrust.com)
87
+ // the exact gap that can let tokenoftrust.com's published public site
87
88
  // sit two days stale behind several accepted-but-unpublished changes with nothing
88
89
  // in the console calling it out — going live and staying live for the PUBLIC
89
90
  // static target are two different facts, and this card makes the second one
@@ -103,43 +104,37 @@ const liveVersion = staticPublishEnabled
103
104
  : null;
104
105
  const publicSiteDigest = liveVersion?.staticLiveDigest ?? null;
105
106
  // The actual verdict: does the public target's manifest sha match what was last
106
- // promoted to "live" here? Both are real tenant Gitea shas (dg5) — a direct,
107
+ // promoted to "live" here? Both are real tenant Gitea shas — a direct,
107
108
  // honest comparison, not a heuristic. See liveDigest.ts's isPublicSiteCurrent.
108
109
  const publicSiteVerdict = publicSiteDigest
109
110
  ? isPublicSiteCurrent(liveVersion?.versionId ?? null, publicSiteDigest)
110
111
  : null;
111
112
 
112
113
  const changesEndpoint = withBase(basePath || "", "/api/changes");
113
- // b11 — the branch-integration lifecycle SPLIT of accept from publish:
114
- // - integrateEndpoint = b08's "accept = integrate into the protected preview
114
+ // The branch-integration lifecycle SPLIT of accept from publish:
115
+ // - integrateEndpoint = "accept = integrate into the protected preview
115
116
  // aggregate" (POST /api/changes/integrate → TenantIntegrationQueue.enqueue).
116
117
  // This is the candidate-queue "Accept to preview" action — it advances the
117
118
  // shared aggregate, it does NOT go live. Returns an IntegrateOutcome
118
119
  // (queueState/runState/statusMessage), never a bare merged:true.
119
- // - shipEndpoint = b10's exact-digest go-live (POST /api/changes/ship →
120
- // b09 AggregateShipOrchestrator.plan/ship). The aggregate card's "Publish"
120
+ // - shipEndpoint = the exact-digest go-live (POST /api/changes/ship →
121
+ // AggregateShipOrchestrator.plan/ship). The aggregate card's "Publish"
121
122
  // action: it ships the reviewed pinned SHA + digest, double-gated
122
123
  // (paywall + human confirm). The read-only plan is served on GET /api/changes
123
124
  // (`plan`), so the confirm surface never has to POST to preview it.
124
- // If b08/b10's exact paths differ at fold, the integrator reconciles here —
125
- // these are the agreed contract paths (see the unit brief).
126
125
  const integrateEndpoint = withBase(basePath || "", "/api/changes/integrate");
127
126
  const shipEndpoint = withBase(basePath || "", "/api/changes/ship");
128
- // U7 retire/GC: evict a candidate's ReviewEnvironment + version (rebuildable),
129
- // DISTINCT from reject (which closes the change). Gated server-side by retireGate.
130
- const retireEndpoint = withBase(basePath || "", "/api/preview/retire");
131
- // rux5 — build-on-demand for a synthetic not-built PR row: materialize the PR's
127
+ // Build-on-demand for a synthetic not-built PR row: materialize the PR's
132
128
  // hosted preview (INERT — flips no live channel). Gated server-side by buildGate
133
129
  // (the ship-on-behalf floor); the endpoint resolves the head sha via forge PR-read
134
130
  // when the row supplies only the PR number.
135
131
  const buildEndpoint = withBase(basePath || "", "/api/preview/build");
136
- // dg7 — the ship-operations workspace woven into this tab: versions/promotion
132
+ // The ship-operations workspace woven into this tab: versions/promotion
137
133
  // history, per-candidate diff, and rollback (through the ONE rollback
138
134
  // orchestrator). Read endpoints mirror the change-queue's auth/scope.
139
135
  const versionsEndpoint = withBase(basePath || "", "/api/changes/versions");
140
136
  const rollbackEndpoint = withBase(basePath || "", "/api/changes/rollback");
141
- // b17 — branch retention: GET dry-run classification / POST guarded delete.
142
- // DISTINCT endpoint from retireEndpoint above (branch delete vs artifact evict).
137
+ // Branch retention: GET dry-run classification / POST guarded delete.
143
138
  const branchesEndpoint = withBase(basePath || "", "/api/changes/branches");
144
139
  // The client builds `${diffEndpointBase}/<changeId>/diff` per candidate.
145
140
  const diffEndpointBase = changesEndpoint;
@@ -147,9 +142,47 @@ const diffEndpointBase = changesEndpoint;
147
142
  // digest actually serves. Rendered into "already live" copy + the publish
148
143
  // ceremony's "View your live store" CTA.
149
144
  const liveHref = withBase(basePath || "", "/");
145
+
146
+ // LIVE-SITE ENTRY POINTS — surfaced explicitly so "where does my store actually
147
+ // serve?" is never a mystery (it was). Two public URLs, always shown + clickable:
148
+ // 1. Hosted store — THIS worker at the request host + tenant base path
149
+ // (e.g. storefront.tokenoftrust.store/tokenoftrust.com/).
150
+ // 2. Public site — the static target's canonical URL, derived from the SAME
151
+ // manifest URL the freshness read-back probes (WWW_PUBLISH_VERIFY_URL or
152
+ // https://<canonicalDomain>/manifest.json), minus the /manifest.json tail
153
+ // (e.g. published.tokenoftrust.com). Only for static-publish tenants; a
154
+ // hosted-only tenant has no separate public site. Computed PURELY (no probe)
155
+ // so the URL shows even when the freshness probe above timed out.
156
+ const requestHost = Astro.request.headers.get("host") ?? Astro.url.host;
157
+ const liveStoreUrl = `${Astro.url.protocol}//${requestHost}${liveHref}`;
158
+ const publicManifestUrl = staticPublishEnabled
159
+ ? staticLiveManifestUrl({
160
+ canonicalDomain: tenant.canonicalDomain,
161
+ // Per-tenant override first (never bleeds across stores), then the global env
162
+ // escape hatch, then the canonical-apex default — mirrors resolveLiveVersion.
163
+ explicitUrl: tenant.staticPublish?.verifyUrl ?? (await readEnv("WWW_PUBLISH_VERIFY_URL")) ?? null,
164
+ })
165
+ : null;
166
+ const publicSiteUrl = publicManifestUrl
167
+ ? publicManifestUrl.replace(/\/manifest\.json$/, "/")
168
+ : null;
169
+ const stripScheme = (u: string): string => u.replace(/^https?:\/\//, "");
170
+
171
+ // r08 — a signed, tenant-scoped, expiring `?__preview` capability link an owner can
172
+ // copy and hand to a non-technical stakeholder (view-only, no sign-in required).
173
+ // Minted server-side so the signing secret never reaches the browser (mirrors
174
+ // preview/dashboard.astro). Signs the tenant_id claim: the shared-preview
175
+ // middleware verifies against `tenant.tenant_id` and the candidate renderer accepts
176
+ // tenant_id too, so ONE token authorizes BOTH the shared next-release preview AND
177
+ // per-candidate `preview/pr/<N>` links. Only minted for a ship-capable viewer, and
178
+ // absent when signing isn't configured — the copy buttons then stay hidden rather
179
+ // than offering a dead link. TTL 24h so a link copied late in a long session still
180
+ // works; a reload re-mints a fresh one.
181
+ const previewShareToken = viewerCanShip ? await mintPreviewShareToken(tenant.tenant_id) : null;
182
+
150
183
  const domainStatusEndpoint = withBase(basePath || "", "/api/domain/status");
151
184
  const domainDispatchEndpoint = withBase(basePath || "", "/api/domain/dispatch");
152
- // u9: the machine-verifiable apex-cutover readiness gate — the SAME server gate the
185
+ // The machine-verifiable apex-cutover readiness gate — the SAME server gate the
153
186
  // `tot go-live` CLI reads, so the admin display and the CLI never diverge.
154
187
  const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness");
155
188
  ---
@@ -165,7 +198,6 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
165
198
  data-changes-endpoint={changesEndpoint}
166
199
  data-integrate-endpoint={integrateEndpoint}
167
200
  data-ship-endpoint={shipEndpoint}
168
- data-retire-endpoint={retireEndpoint}
169
201
  data-build-endpoint={buildEndpoint}
170
202
  data-versions-endpoint={versionsEndpoint}
171
203
  data-rollback-endpoint={rollbackEndpoint}
@@ -176,17 +208,20 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
176
208
  data-domain-readiness-endpoint={domainReadinessEndpoint}
177
209
  data-repo={tenant.appDomain}
178
210
  data-app-domain={tenant.appDomain}
211
+ data-canonical-domain={tenant.canonicalDomain ?? ""}
212
+ data-preview-share-token={previewShareToken ?? ""}
179
213
  data-live-href={liveHref}
180
214
  data-can-ship={viewerCanShip ? "true" : "false"}
181
215
  data-is-owner={viewerIsOwner ? "true" : "false"}
182
216
  data-entitled={goLiveEntitled ? "true" : "false"}
183
217
  data-static-publish={staticPublishEnabled ? "true" : "false"}
184
218
  data-dispatch-configured={dispatchConfigured ? "true" : "false"}
219
+ data-public-site-host={publicSiteUrl ? stripScheme(publicSiteUrl).replace(/\/$/, "") : ""}
185
220
  >
186
221
  <div class="section-title">
187
222
  <div>
188
223
  <h2 id="publish-title">Publish</h2>
189
- <p>Accept changes into the preview aggregate, then publish the reviewed build live.</p>
224
+ <p>Review what's changed, then publish your next release when you're ready.</p>
190
225
  </div>
191
226
  <div class="actions">
192
227
  <button class="btn" type="button" data-publish-refresh>
@@ -237,11 +272,79 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
237
272
  </div>
238
273
  )}
239
274
 
275
+ <!-- State-driven "next action" hero (r01) — the SINGLE top-of-tab state
276
+ element. ONE sentence + ONE primary CTA, derived by lib/publish/publishHero.ts
277
+ from the SAME /api/changes read the queue + aggregate already fetch (no extra
278
+ read). It ABSORBS the two former standalone banners: the merge-doctor health
279
+ summary (B1) and the platform-fault "Report to support" escalation (B3) are
280
+ folded into the hero's state model, expressed through this one element — a
281
+ merge conflict/platform blocker turns the hero red/urgent instead of rendering
282
+ a separate banner. Rendered by renderHero() in adminPublishTab.ts; starts
283
+ hidden until the first /api/changes read resolves. The r03 stale-public-site
284
+ alarm sits alongside it in this region (a distinct top-level signal that
285
+ deep-links into Live Site), so there is ONE hero/alert region, not floating
286
+ banners. Urgency is never color-only: the tone drives the icon + an explicit
287
+ "Action needed" label. -->
288
+ <div class="publish-hero-region" data-publish-hero-region>
289
+ <div class="publish-hero" data-publish-hero data-tone="calm" hidden role="status" aria-live="polite">
290
+ <div class="publish-hero-main">
291
+ <span class="publish-hero-icon" data-publish-hero-icon aria-hidden="true">
292
+ <svg><use href="#i-check"></use></svg>
293
+ </span>
294
+ <div class="publish-hero-copy">
295
+ <span class="publish-hero-kicker" data-publish-hero-kicker hidden>Action needed</span>
296
+ <b class="publish-hero-headline" data-publish-hero-headline></b>
297
+ <span class="publish-hero-detail" data-publish-hero-detail></span>
298
+ </div>
299
+ </div>
300
+ <div class="publish-hero-actions">
301
+ <button class="btn primary publish-hero-cta" type="button" data-publish-hero-cta hidden></button>
302
+ <a class="btn primary publish-hero-cta" data-publish-hero-cta-link hidden href="mailto:success@tokenoftrust.com"></a>
303
+ </div>
304
+ </div>
305
+
306
+ <!-- r03: stale-public-site alarm, LIFTED to top-level. The "public site is
307
+ behind the live pointer" verdict (resolveLiveVersion / isPublicSiteCurrent)
308
+ used to sit buried inside the collapsible Live Site section; when the public
309
+ target is behind, surface it here as a top-level amber alarm that deep-links
310
+ into Live Site (where the full breakdown stays nested). SSR-driven from the
311
+ same publicSiteVerdict the Live Site chip reads — shown only for a
312
+ static-publish tenant whose public target is confirmed behind. -->
313
+ {staticPublishEnabled && publicSiteVerdict && publicSiteVerdict.status === "behind" && (
314
+ <div class="risk-alert amber publish-stale-site" data-publish-stale-site role="status" aria-live="polite">
315
+ <div class="risk-alert-main">
316
+ <svg><use href="#i-alert"></use></svg>
317
+ <div>
318
+ <b>Public site is behind — re-publish to catch it up</b>
319
+ <span>
320
+ Your public site is still serving an older release
321
+ (<code class="publish-mono">{publicSiteVerdict.publishedSha.slice(0, 12)}</code>)
322
+ while your live pointer has moved on to
323
+ (<code class="publish-mono">{publicSiteVerdict.livePointerSha.slice(0, 12)}</code>).
324
+ Re-publish, or open the publishing details for the full breakdown.
325
+ </span>
326
+ </div>
327
+ </div>
328
+ <a class="btn" href="#publish-live-site" data-publish-stale-site-link>Open publishing details</a>
329
+ </div>
330
+ )}
331
+ </div>
332
+
240
333
  <div class="grid cols-3">
241
334
  <div class="metric">
242
335
  <span>Live version</span>
243
- <strong data-publish-live-version>—</strong>
336
+ <strong>
337
+ <!-- The human version label ("v42") links straight to the live store; the
338
+ raw SHA is demoted to the Technical details disclosure below. -->
339
+ <a class="live-version-link" data-publish-live-version-link href={liveHref}
340
+ target="_blank" rel="noopener" hidden>—</a>
341
+ <span data-publish-live-version-none>—</span>
342
+ </strong>
244
343
  <small data-publish-live-source>What shoppers see right now</small>
344
+ <details class="metric-tech" data-publish-live-tech hidden>
345
+ <summary>Technical details</summary>
346
+ <span class="publish-mono subtle" data-publish-live-sha>—</span>
347
+ </details>
245
348
  </div>
246
349
  <div class="metric">
247
350
  <span>Published</span>
@@ -271,64 +374,12 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
271
374
  </div>
272
375
  )}
273
376
 
274
- <!-- Public-site status — the READ-BACK half of the static-publish seam
275
- (resolveLiveVersion's staticLiveDigest), made visible instead of only
276
- existing in API JSON. Distinct from "Live version" above: that's the
277
- CONTROL-PLANE record of what was promoted; this is what the external
278
- target (CloudFront/S3) is HONESTLY observed serving right now — the two
279
- can diverge (a stale/failed republish job) with nothing else in this
280
- console able to tell you. Answers "did going live actually reach the
281
- public site?" without a manual curl. -->
282
- {staticPublishEnabled && publicSiteDigest && publicSiteVerdict && (
283
- <div
284
- class={`risk-alert ${publicSiteVerdict.status === "current" ? "public-site-ok" : "amber"}`}
285
- data-publish-public-site
286
- >
287
- <div class="risk-alert-main">
288
- <svg><use href={publicSiteVerdict.status === "current" ? "#i-check" : "#i-alert"}></use></svg>
289
- <div>
290
- {publicSiteVerdict.status === "current" ? (
291
- <>
292
- <b>Public site is up to date</b>
293
- <span>
294
- <code>{tenant.appDomain}</code>'s public target is serving the same commit
295
- (<code class="publish-mono">{liveVersion?.versionId?.slice(0, 12)}</code>)
296
- as the hosted store's live pointer. Publishing live above
297
- republishes it automatically — usually within a few minutes.
298
- </span>
299
- </>
300
- ) : publicSiteVerdict.status === "behind" ? (
301
- <>
302
- <b>Public site is behind the hosted store's live pointer</b>
303
- <span>
304
- <code>{tenant.appDomain}</code>'s public target is still serving{" "}
305
- <code class="publish-mono">{publicSiteVerdict.publishedSha.slice(0, 12)}</code>, but
306
- the hosted store's live pointer has since moved to{" "}
307
- <code class="publish-mono">{publicSiteVerdict.livePointerSha.slice(0, 12)}</code>.
308
- The republish job hasn't caught up yet (or a go-live happened before it was enabled) —
309
- check the workflow run, or trigger a fresh go-live to re-dispatch it.
310
- </span>
311
- </>
312
- ) : (
313
- <>
314
- <b>Couldn't verify the public site is current</b>
315
- <span>
316
- Checked <code class="publish-mono">{publicSiteDigest.url}</code>: {publicSiteVerdict.reason}.
317
- This does NOT mean the hosted store's own "Live version" above is wrong, only that the
318
- separate public static target couldn't be confirmed current.
319
- </span>
320
- </>
321
- )}
322
- </div>
323
- </div>
324
- </div>
325
- )}
326
-
327
- <div class="panel">
328
- <div class="panel-head">
329
- <h3><svg><use href="#i-export"></use></svg>Candidate queue</h3>
377
+ <div class="panel" id="publish-queue">
378
+ <details class="publish-queue-details" open>
379
+ <summary class="panel-head">
380
+ <h3><svg><use href="#i-export"></use></svg>What needs your approval?</h3>
330
381
  <span class="chip" data-publish-queue-chip>Loading</span>
331
- </div>
382
+ </summary>
332
383
  <div class="panel-body grid">
333
384
  <p class="note publish-status" data-publish-status role="status" aria-live="polite"></p>
334
385
  <!-- rux1: forge-listing degraded strip. Shown ONLY when GET /api/changes
@@ -375,20 +426,113 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
375
426
  </p>
376
427
  )}
377
428
  </div>
429
+ </details>
430
+ </div>
431
+
432
+ <!-- "See all" history: changes that LEFT the queue by being rejected. Rejecting
433
+ is otherwise a black hole — this collapsed panel keeps them visible (and their
434
+ preview reachable) so a reject is recoverable-from-history. Collapsed by
435
+ default (a reference, not a work surface); the whole panel is hidden by the
436
+ client when there are none, so it never adds empty chrome. Excludes shipped
437
+ (merged) changes — those live in the Live Site / versions panel. -->
438
+ <div class="panel" id="publish-closed" data-publish-closed-panel hidden>
439
+ <details class="closed-changes-details">
440
+ <summary class="panel-head">
441
+ <h3><svg><use href="#i-undo"></use></svg>Closed &amp; rejected changes</h3>
442
+ <span class="chip" data-publish-closed-chip>0</span>
443
+ </summary>
444
+ <div class="panel-body grid">
445
+ <p class="note subtle">
446
+ Changes you rejected. They're not in your next release and won't go live —
447
+ kept here so nothing you closed silently disappears. Open a preview to review
448
+ what it was.
449
+ </p>
450
+ <div class="table-wrap">
451
+ <table class="publish-table">
452
+ <thead>
453
+ <tr>
454
+ <th>Change</th>
455
+ <th>Closed</th>
456
+ <th>Head</th>
457
+ </tr>
458
+ </thead>
459
+ <tbody data-publish-closed-body></tbody>
460
+ </table>
461
+ </div>
462
+ </div>
463
+ </details>
378
464
  </div>
379
465
 
380
466
  <!-- b11: the AGGREGATE card — the protected `preview` aggregate the accepted
381
467
  candidates integrate into, its combined evidence (the last integration
382
468
  run's honest state), and the exact-digest Publish (b09/b10 ship the
383
469
  reviewed pinned SHA + digest, never a rebuild). -->
384
- <div class="panel agg-console" data-publish-aggregate>
385
- <div class="panel-head">
386
- <h3><svg><use href="#i-box"></use></svg>Preview aggregate</h3>
470
+ <div class="panel agg-console" id="publish-next-release" data-publish-aggregate>
471
+ <details class="agg-console-details" open>
472
+ <summary class="panel-head">
473
+ <h3><svg><use href="#i-box"></use></svg>Next release</h3>
474
+ <!-- r11 — the human release name (aggregate.name), shown beside the heading.
475
+ Client-populated by renderAggregate; hidden (falls back to just "Next
476
+ release") when the release has no name. -->
477
+ <span class="agg-release-name" data-aggregate-name hidden></span>
478
+ <!-- One-click hop to the current preview build. Shown only when a
479
+ genuinely-built revision exists (renderAggregate toggles it via
480
+ classifyBuildLink), so it can never lead to a 404. The click is
481
+ stop-propagated in adminPublishTab.ts so opening the preview never
482
+ also toggles this collapsible. -->
483
+ <a class="btn btn-compact" data-aggregate-open-preview target="_blank" rel="noopener" hidden>Open preview</a>
484
+ <!-- r08 — copy a shareable, view-only preview link for the next release to hand
485
+ to a non-technical stakeholder (no sign-in). Unhidden by the client only
486
+ when a share token was minted; the click is stop-propagated so copying
487
+ never also toggles this collapsible. -->
488
+ <button class="btn btn-compact" type="button" data-aggregate-copy-preview hidden>
489
+ <svg><use href="#i-copy"></use></svg><span data-copy-preview-label>Copy preview link</span>
490
+ </button>
387
491
  <span class="chip" data-aggregate-chip>Loading</span>
388
- </div>
492
+ </summary>
389
493
  <div class="panel-body grid">
390
494
  <p class="note publish-status" data-aggregate-status role="status" aria-live="polite"></p>
391
495
 
496
+ <!-- Keep the primary action beside the release status. The detail table can
497
+ be long; publishing must never be buried beneath it. -->
498
+ <div class="check-row agg-launch">
499
+ <div>
500
+ <b>
501
+ {staticPublishEnabled
502
+ ? "Publish this release to the hosted and public sites"
503
+ : "Publish this release to your live store"}
504
+ </b>
505
+ <p data-aggregate-publish-detail>
506
+ {publicSiteUrl ? (
507
+ <>
508
+ Publishes the exact build you reviewed to the hosted store, then
509
+ automatically republishes <code>{stripScheme(publicSiteUrl).replace(/\/$/, "")}</code>.
510
+ </>
511
+ ) : (
512
+ <>Publishes the exact build you reviewed — no rebuild.</>
513
+ )}
514
+ </p>
515
+ </div>
516
+ <button class="btn primary agg-publish-btn" type="button" data-aggregate-publish disabled>
517
+ <svg><use href="#i-export"></use></svg>
518
+ {staticPublishEnabled ? "Publish to both live sites" : "Publish live"}
519
+ </button>
520
+ </div>
521
+
522
+ <!-- Only visible after an actually failed aggregate rebuild. -->
523
+ <div class="check-row" data-aggregate-retry-row hidden>
524
+ <div>
525
+ <b>Retry the build</b>
526
+ <p>
527
+ Your change was added, but rebuilding the preview failed. Retry rebuilds it
528
+ and re-runs your store's checks — nothing new is added.
529
+ </p>
530
+ </div>
531
+ <button class="btn" type="button" data-aggregate-retry-build>
532
+ <svg><use href="#i-refresh"></use></svg>Retry build
533
+ </button>
534
+ </div>
535
+
392
536
  <!-- AI-assembled release summary — a plain-language "what am I about to
393
537
  publish?" ahead of the technical trust chain. Rendered ONLY when the
394
538
  server supplies data.releaseSummary; otherwise it stays hidden and the
@@ -409,34 +553,38 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
409
553
  </div>
410
554
 
411
555
  <!-- Chain of custody: the build that was verified → its combined evidence →
412
- the digest that ships. The reviewed digest wears an accented token so
413
- the eye can track that exactly what was reviewed is what goes live. The
414
- three values keep their original data hooks (sha / evidence / digest). -->
415
- <div class="agg-chain">
416
- <div class="agg-stop">
417
- <span class="agg-stop-label">Aggregate build</span>
418
- <span class="agg-stop-val"><strong class="publish-mono" data-aggregate-sha>—</strong></span>
419
- <p class="agg-stop-cap" data-aggregate-run-link>The current green preview build for this store.</p>
420
- </div>
421
- <div class="agg-link" aria-hidden="true">
422
- <span class="agg-flowlabel">verified by</span>
423
- <span class="agg-arrow"><svg viewBox="0 0 34 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M1 5h29m0 0-4-4m4 4-4 4"></path></svg></span>
424
- </div>
425
- <div class="agg-stop">
426
- <span class="agg-stop-label">Combined evidence</span>
427
- <span class="agg-stop-val"><strong data-aggregate-evidence>—</strong></span>
428
- <p class="agg-stop-cap" data-aggregate-evidence-detail>The last integration run's verdict.</p>
429
- </div>
430
- <div class="agg-link" aria-hidden="true">
431
- <span class="agg-flowlabel">produces</span>
432
- <span class="agg-arrow"><svg viewBox="0 0 34 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M1 5h29m0 0-4-4m4 4-4 4"></path></svg></span>
433
- </div>
434
- <div class="agg-stop">
435
- <span class="agg-stop-label">Reviewed digest</span>
436
- <span class="agg-stop-val"><strong class="publish-mono agg-digest" data-aggregate-digest>—</strong></span>
437
- <p class="agg-stop-cap">The content digest that will be published — exactly what you reviewed.</p>
556
+ the digest that ships. Collapsed by default this is verification
557
+ detail for a developer, not something a merchant needs to read to
558
+ decide whether to publish; the plain-language summary above and the
559
+ Publish button below are the actual decision surface. -->
560
+ <details class="agg-chain-details">
561
+ <summary>Technical details</summary>
562
+ <div class="agg-chain">
563
+ <div class="agg-stop">
564
+ <span class="agg-stop-label">Aggregate build</span>
565
+ <span class="agg-stop-val"><strong class="publish-mono" data-aggregate-sha>—</strong></span>
566
+ <p class="agg-stop-cap" data-aggregate-run-link>The current green preview build for this store.</p>
567
+ </div>
568
+ <div class="agg-link" aria-hidden="true">
569
+ <span class="agg-flowlabel">verified by</span>
570
+ <span class="agg-arrow"><svg viewBox="0 0 34 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M1 5h29m0 0-4-4m4 4-4 4"></path></svg></span>
571
+ </div>
572
+ <div class="agg-stop">
573
+ <span class="agg-stop-label">Combined evidence</span>
574
+ <span class="agg-stop-val"><strong data-aggregate-evidence>—</strong></span>
575
+ <p class="agg-stop-cap" data-aggregate-evidence-detail>The last integration run's verdict.</p>
576
+ </div>
577
+ <div class="agg-link" aria-hidden="true">
578
+ <span class="agg-flowlabel">produces</span>
579
+ <span class="agg-arrow"><svg viewBox="0 0 34 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M1 5h29m0 0-4-4m4 4-4 4"></path></svg></span>
580
+ </div>
581
+ <div class="agg-stop">
582
+ <span class="agg-stop-label">Reviewed digest</span>
583
+ <span class="agg-stop-val"><strong class="publish-mono agg-digest" data-aggregate-digest>—</strong></span>
584
+ <p class="agg-stop-cap">The content digest that will be published — exactly what you reviewed.</p>
585
+ </div>
438
586
  </div>
439
- </div>
587
+ </details>
440
588
 
441
589
  <!-- Included PRs — the batch this aggregate composes (b01 includedPrs). u14:
442
590
  the "missing middle" — this is the STAGED delta vs live. The title + note
@@ -467,255 +615,281 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
467
615
  </span>
468
616
  </div>
469
617
  <p class="note" data-aggregate-delta-note hidden></p>
470
- <ul class="aggregate-prs-list agg-changes" data-aggregate-prs></ul>
618
+ <!-- ONE table, one row per change — merges what used to be two separate
619
+ "Included changes" lists (this staged-delta section + the Publish
620
+ plan's own duplicate) into a single source of truth. Populated by
621
+ renderAggregate, enriched with the plan's per-change title/description/
622
+ file+line counts when available. -->
623
+ <div class="table-wrap">
624
+ <table class="publish-table agg-included-table">
625
+ <thead>
626
+ <tr>
627
+ <th>Change</th>
628
+ <th>Head</th>
629
+ <th>Files</th>
630
+ <th>Lines</th>
631
+ <th>Added</th>
632
+ <th class="action">Actions</th>
633
+ </tr>
634
+ </thead>
635
+ <tbody data-aggregate-prs></tbody>
636
+ </table>
637
+ </div>
471
638
  </div>
472
639
 
473
640
  <!-- The exact-digest Publish plan (b09 plan()): pinned SHA + digest +
474
- included PRs + rollback target + paywall verdict + run link. Shown
475
- inline (from GET /api/changes `plan`) so the confirm never POSTs to
476
- preview. Publish is refused until the plan is `ok`. -->
641
+ rollback target + paywall verdict + run link. Shown inline (from
642
+ GET /api/changes `plan`) so the confirm never POSTs to preview.
643
+ Publish is refused until the plan is `ok`. Its own included-changes
644
+ list was merged into the single table above — see data-aggregate-prs. -->
477
645
  <div class="aggregate-plan agg-manifest" data-aggregate-plan hidden>
478
646
  <div class="aggregate-plan-head agg-manifest-head">
479
647
  <b><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 11l3 3L22 4"></path><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path></svg>Publish plan</b>
480
648
  <span class="chip" data-aggregate-plan-chip></span>
481
649
  </div>
482
- <dl class="aggregate-plan-grid" data-aggregate-plan-grid></dl>
483
-
484
- <!-- Openable "Included changes": each accepted PR as a line item with its
485
- human title, a one-line description of what it does, and meta (sha /
486
- files / who + when). Populated by renderPlan; degrades to a bare count
487
- when per-change titles/descriptions aren't available. The <details>
488
- element is static so its open/closed state survives a poll re-render —
489
- only the list contents are replaced. -->
490
- <div class="agg-included" data-aggregate-plan-included hidden>
491
- <details class="agg-included-details">
492
- <summary>
493
- <span class="agg-included-lead">Included changes</span>
494
- <span class="agg-included-badge" data-aggregate-plan-included-count>0</span>
495
- <span class="agg-included-hint" data-aggregate-plan-included-hint></span>
496
- <span class="agg-chev" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l6 6-6 6"></path></svg></span>
497
- </summary>
498
- <ul class="agg-included-list" data-aggregate-plan-included-list></ul>
499
- </details>
500
- </div>
650
+ <details class="agg-chain-details">
651
+ <summary>Technical details</summary>
652
+ <dl class="aggregate-plan-grid" data-aggregate-plan-grid></dl>
653
+ </details>
501
654
 
502
655
  <p class="note" data-aggregate-plan-note></p>
503
656
  </div>
504
657
 
505
- <!-- u15 — retry a DEAD rebuild (no new merge). Shown only while the aggregate
506
- is red: a candidate's PR already merged into preview, but the
507
- build/evidence step that follows died (e.g. a transient MCP error) — this
508
- retries ONLY that step, against the sha the forge already carries. -->
509
- <div class="check-row" data-aggregate-retry-row hidden>
510
- <div>
511
- <b>Retry the build</b>
512
- <p>
513
- The last merge landed on preview, but rebuilding it failed. Retry re-runs
514
- the build + evidence check alone — no new merge, no membership change.
515
- </p>
516
- </div>
517
- <button class="btn" type="button" data-aggregate-retry-build>
518
- <svg><use href="#i-refresh"></use></svg>Retry build
519
- </button>
520
- </div>
521
-
522
- <div class="check-row agg-launch">
523
- <div>
524
- <b>Publish the reviewed build to your live store</b>
525
- <p data-aggregate-publish-detail>
526
- Publishes the exact reviewed digest — no rebuild. Available once the
527
- aggregate is green and its integration run passed.
528
- </p>
529
- </div>
530
- <button class="btn primary agg-publish-btn" type="button" data-aggregate-publish disabled>
531
- <svg><use href="#i-export"></use></svg>Publish live
532
- </button>
533
- </div>
534
- </div>
535
- </div>
536
-
537
- <!-- dg7: published versions + promotion history + rollback (u2/u3 promotion_status,
538
- u5 immutable /rev links, u8 rollback through the one orchestrator). -->
539
- <div class="panel" data-publish-versions>
540
- <div class="panel-head">
541
- <h3><svg><use href="#i-refresh"></use></svg>Published versions</h3>
542
- <span class="chip" data-versions-chip>Loading</span>
543
- </div>
544
- <div class="panel-body grid">
545
- <p class="note publish-status" data-versions-status role="status" aria-live="polite"></p>
546
- <p class="note" data-versions-rollback-note hidden></p>
547
- <!-- u14: orients the owner between the staged aggregate above and live versions
548
- below — "N staged change(s) not yet published; Publish live creates the next
549
- version here." Owned by renderAggregate (its own element, so its timing never
550
- races renderVersions). -->
551
- <p class="note" data-versions-orientation hidden></p>
552
- <div class="table-wrap">
553
- <table class="publish-table versions-table">
554
- <thead>
555
- <tr>
556
- <th>Version</th>
557
- <th>Digest</th>
558
- <th>Promoted</th>
559
- <th>Preview</th>
560
- <th>State</th>
561
- <th class="action">Actions</th>
562
- </tr>
563
- </thead>
564
- <tbody data-versions-body>
565
- <tr data-versions-placeholder>
566
- <td colspan="6"><span class="subtle">Loading published versions…</span></td>
567
- </tr>
568
- </tbody>
569
- </table>
570
- </div>
571
- </div>
572
- </div>
573
-
574
- <!-- b17: branch retention — Admin visibility into the branch-lifecycle
575
- contract's "Candidate branch retirement" table (P1 items 8/9/11). Reads
576
- the guarded `candidate_list` classification (protected/active-open/
577
- stale-open/integrated/closed-or-rejected/orphan) and, for a branch that
578
- classifies as ready, runs the guarded `candidate_delete` — which
579
- re-classifies fresh server-side and refuses anything not integrated or
580
- closed-or-rejected, no matter what this panel last rendered. DISTINCT
581
- from the "Retire" action in the candidate queue above: retire evicts a
582
- hosted PREVIEW ARTIFACT (rebuildable); this panel deletes a Git BRANCH
583
- (transport only, never the durable record — see the note below). An
584
- orphan is always shown quarantined for review, never one-click deletable. -->
585
- <div class="panel" data-publish-branches>
586
- <div class="panel-head">
587
- <h3><svg><use href="#i-workflows"></use></svg>Branch retention</h3>
588
- <span class="chip" data-branches-chip>Loading</span>
589
- </div>
590
- <div class="panel-body grid">
591
- <p class="note">
592
- Cleans up candidate <b>branches</b> once their pull request is merged or
593
- closed — a different action on a different object than <b>Retire</b> in
594
- the queue above, which only evicts a hosted preview build (rebuildable
595
- on demand, never a branch). Every classification below is a fresh
596
- dry-run; <b>Sweep dead branches</b> lists exactly what it will delete and
597
- asks once to confirm, then the server re-checks each ref fresh before
598
- removing it.
599
- </p>
600
- <p class="note publish-status" data-branches-status role="status" aria-live="polite"></p>
601
- <div class="toolbar" data-branches-actions hidden>
602
- <button type="button" class="btn" data-branches-sweep>Sweep dead branches</button>
603
- </div>
604
- <div class="table-wrap">
605
- <table class="publish-table">
606
- <thead>
607
- <tr>
608
- <th>Branch</th>
609
- <th>Classification</th>
610
- <th>PR</th>
611
- <th>Tip commit</th>
612
- <th>Age</th>
613
- <th>Evidence</th>
614
- <th class="action">Actions</th>
615
- </tr>
616
- </thead>
617
- <tbody data-branches-body>
618
- <tr data-branches-placeholder>
619
- <td colspan="7"><span class="subtle">Loading branch classification…</span></td>
620
- </tr>
621
- </tbody>
622
- </table>
623
- </div>
624
658
  </div>
659
+ </details>
625
660
  </div>
626
661
 
627
- {staticPublishEnabled && (
628
- <div class="panel" data-publish-domain>
629
- <div class="panel-head">
630
- <h3><svg><use href="#i-shield"></use></svg>Public domain</h3>
631
- <span class="chip" data-domain-state-chip>Loading</span>
632
- </div>
662
+ <!-- Branch retention panel removed entirely — old candidate branches now
663
+ clean themselves up automatically (auto-swept after each successful
664
+ accept/publish, see adminPublishTab.ts autoSweepBranches). There is
665
+ nothing here for anyone to look at or act on day-to-day, so there is no
666
+ panel at all, not even a collapsed one. -->
667
+
668
+ <!-- Publish next release — the collapsed destination/status panel below the
669
+ staged release. Its summary answers the operational question first: either
670
+ live already matches the staged release, or there is an action to advance it.
671
+ The disclosure contains destination links, freshness, rollback, and domain
672
+ controls for operators who need the detail. -->
673
+ <div class="panel" id="publish-live-site" data-publish-live-site>
674
+ <details class="live-site-details">
675
+ <summary class="panel-head">
676
+ <h3><svg><use href="#i-export"></use></svg>Publish next release</h3>
677
+ <span class="chip" data-live-release-state>Checking release…</span>
678
+ <button
679
+ class="btn primary btn-compact live-release-publish"
680
+ type="button"
681
+ data-live-release-publish
682
+ hidden
683
+ >Publish next release</button>
684
+ </summary>
633
685
  <div class="panel-body grid">
634
- <div class="grid cols-3">
635
- <div class="field">
636
- <label>Serving from</label>
637
- <strong data-domain-state>—</strong>
638
- <p data-domain-since>Current host for {tenant.appDomain}</p>
639
- </div>
640
- <div class="field">
641
- <label>Rollback records</label>
642
- <strong data-domain-capture>None captured</strong>
643
- <p>DNS records captured before connect — rollback restores these.</p>
644
- </div>
645
- <div class="field">
646
- <label>Last run</label>
647
- <strong data-domain-run>None</strong>
648
- <p data-domain-run-detail>Connect and rollback runs appear here.</p>
649
- </div>
650
- </div>
651
-
652
- <p class="note publish-status" data-domain-status role="status" aria-live="polite"></p>
653
-
654
- <!-- u9: apex-cutover readiness the itemized preconditions the server gate
655
- requires before a connect is permitted. Populated from
656
- /api/domain/readiness (the SAME gate `tot go-live` reads). -->
657
- <div class="domain-readiness" data-domain-readiness hidden>
658
- <div class="domain-readiness-head">
659
- <b>Cutover readiness</b>
660
- <span class="chip" data-domain-readiness-chip>Checking…</span>
661
- </div>
662
- <ul class="domain-readiness-list" data-domain-readiness-list></ul>
663
- <p class="note" data-domain-readiness-note></p>
664
- </div>
665
-
666
- <div class="check-row">
667
- <div>
668
- <b>Connect {tenant.appDomain} to the storefront</b>
669
- <p>
670
- Updates two DNS names <code>{tenant.appDomain}</code> and <code>www.{tenant.appDomain}</code> —
671
- to serve the published site. Takes effect in about a minute. Type the
672
- domain to confirm.
673
- </p>
674
- {!viewerCanApexCutover && (
675
- <p><span class="chip amber">Owner/admin only</span> Connecting the domain requires an owner or admin sign-in.</p>
676
- )}
677
- </div>
678
- <div class="domain-connect-controls">
679
- <input
680
- type="text"
681
- placeholder={tenant.appDomain}
682
- autocomplete="off"
683
- spellcheck="false"
684
- aria-label="Type the domain to confirm"
685
- data-domain-confirm
686
- disabled={!viewerCanApexCutover}
687
- />
688
- <label class="domain-rehearsal">
689
- <input type="checkbox" data-domain-rehearsal disabled={!viewerCanApexCutover} />
690
- Rehearsal (dry run, no DNS change)
691
- </label>
692
- <button
693
- class="btn primary"
694
- type="button"
695
- data-domain-connect
696
- data-owner-locked={!viewerCanApexCutover ? "true" : undefined}
697
- disabled={!viewerCanApexCutover}
698
- >
699
- <svg><use href="#i-check"></use></svg>Connect domain
700
- </button>
686
+ <ul class="live-site-links">
687
+ <li>
688
+ <span class="live-site-link-label">Hosted store</span>
689
+ <a class="live-site-link-url publish-mono" href={liveHref} target="_blank" rel="noopener">{stripScheme(liveStoreUrl)}</a>
690
+ <span class="live-site-link-note">Updates immediately when you publish the reviewed release above.</span>
691
+ </li>
692
+ {publicSiteUrl && (
693
+ <li>
694
+ <span class="live-site-link-label">Public site</span>
695
+ <a class="live-site-link-url publish-mono" href={publicSiteUrl} target="_blank" rel="noopener">{stripScheme(publicSiteUrl)}</a>
696
+ <span class="live-site-link-note">Republishes automatically after the hosted release is published.</span>
697
+ </li>
698
+ )}
699
+ </ul>
700
+
701
+ <!-- Public-site freshness read-back (static-publish seam's staticLiveDigest):
702
+ what the external target (CloudFront/S3) is HONESTLY observed serving
703
+ right now, vs the control-plane's live pointer — the two can diverge on a
704
+ stale/failed republish. Answers "did going live actually reach the public
705
+ site?" without a manual curl. -->
706
+ {staticPublishEnabled && publicSiteDigest && publicSiteVerdict && (
707
+ <div
708
+ class={`risk-alert ${publicSiteVerdict.status === "current" ? "public-site-ok" : "amber"}`}
709
+ data-publish-public-site
710
+ >
711
+ <div class="risk-alert-main">
712
+ <svg><use href={publicSiteVerdict.status === "current" ? "#i-check" : "#i-alert"}></use></svg>
713
+ <div>
714
+ {publicSiteVerdict.status === "current" ? (
715
+ <>
716
+ <b>Public site matches the currently published release</b>
717
+ <span>
718
+ <code>{stripScheme(publicSiteUrl ?? tenant.appDomain).replace(/\/$/, "")}</code> is serving
719
+ (<code class="publish-mono">{liveVersion?.versionId?.slice(0, 12)}</code>)
720
+ the same release as the hosted store. Staged preview changes are
721
+ a separate next release: use <b>Publish to both live sites</b> above
722
+ to advance the hosted store and republish this public site.
723
+ </span>
724
+ </>
725
+ ) : publicSiteVerdict.status === "behind" ? (
726
+ <>
727
+ <b>Public site is behind the hosted store's live pointer</b>
728
+ <span>
729
+ <code>{tenant.appDomain}</code>'s public target is still serving{" "}
730
+ <code class="publish-mono">{publicSiteVerdict.publishedSha.slice(0, 12)}</code>, but
731
+ the hosted store's live pointer has since moved to{" "}
732
+ <code class="publish-mono">{publicSiteVerdict.livePointerSha.slice(0, 12)}</code>.
733
+ The republish job hasn't caught up yet (or a go-live happened before it was enabled) —
734
+ check the workflow run, or trigger a fresh go-live to re-dispatch it.
735
+ </span>
736
+ </>
737
+ ) : (
738
+ <>
739
+ <b>Couldn't verify the public site is current</b>
740
+ <span>
741
+ Checked <code class="publish-mono">{publicSiteDigest.url}</code>: {publicSiteVerdict.reason}.
742
+ This does NOT mean the hosted store's own "Live version" above is wrong, only that the
743
+ separate public static target couldn't be confirmed current.
744
+ </span>
745
+ </>
746
+ )}
747
+ </div>
748
+ </div>
701
749
  </div>
750
+ )}
751
+
752
+ <!-- Rollback options — CONTAINED within Live Site as a subsection (was a sibling
753
+ panel). The full published-version history + one-click rollback through the
754
+ single rollback orchestrator; collapsed by default (most people never open
755
+ it — the live version is already the metric up top). Keeps its
756
+ data-publish-versions + data-versions-* hooks verbatim. -->
757
+ <div class="live-site-subsection" data-publish-versions>
758
+ <details class="versions-details">
759
+ <summary class="live-site-subhead">
760
+ <h4><svg><use href="#i-refresh"></use></svg>Rollback options</h4>
761
+ <span class="chip" data-versions-chip>Loading</span>
762
+ </summary>
763
+ <div class="grid">
764
+ <p class="note publish-status" data-versions-status role="status" aria-live="polite"></p>
765
+ <p class="note" data-versions-rollback-note hidden></p>
766
+ <!-- u14: orients the owner between the staged aggregate above and live versions
767
+ below — "N staged change(s) not yet published; Publish live creates the next
768
+ version here." Owned by renderAggregate (its own element, so its timing never
769
+ races renderVersions). -->
770
+ <p class="note" data-versions-orientation hidden></p>
771
+ <div class="table-wrap">
772
+ <table class="publish-table versions-table">
773
+ <thead>
774
+ <tr>
775
+ <th>Version</th>
776
+ <th>Digest</th>
777
+ <th>Promoted</th>
778
+ <th>Preview</th>
779
+ <th>State</th>
780
+ <th class="action">Actions</th>
781
+ </tr>
782
+ </thead>
783
+ <tbody data-versions-body>
784
+ <tr data-versions-placeholder>
785
+ <td colspan="6"><span class="subtle">Loading published versions…</span></td>
786
+ </tr>
787
+ </tbody>
788
+ </table>
789
+ </div>
790
+ </div>
791
+ </details>
702
792
  </div>
703
793
 
704
- <div class="check-row">
705
- <div>
706
- <b>Roll back to the previous host</b>
707
- <p data-domain-rollback-detail>
708
- Restores the DNS records captured before connect. Available once records
709
- have been captured.
710
- </p>
794
+ <!-- Public domain (apex connect / rollback) — CONTAINED within Live Site as a
795
+ subsection. Keeps its data-publish-domain + data-domain-* hooks verbatim so
796
+ the client controller is unchanged; only the wrapper/heading are lighter to
797
+ read as a nested section rather than a sibling panel. -->
798
+ {staticPublishEnabled && (
799
+ <div class="live-site-subsection" data-publish-domain>
800
+ <div class="live-site-subhead">
801
+ <h4><svg><use href="#i-shield"></use></svg>Public domain</h4>
802
+ <span class="chip" data-domain-state-chip>Loading</span>
803
+ </div>
804
+ <div class="grid">
805
+ <div class="grid cols-3">
806
+ <div class="field">
807
+ <label>Serving from</label>
808
+ <strong data-domain-state>—</strong>
809
+ <p data-domain-since>Current host for {tenant.appDomain}</p>
810
+ </div>
811
+ <div class="field">
812
+ <label>Rollback records</label>
813
+ <strong data-domain-capture>None captured</strong>
814
+ <p>DNS records captured before connect — rollback restores these.</p>
815
+ </div>
816
+ <div class="field">
817
+ <label>Last run</label>
818
+ <strong data-domain-run>None</strong>
819
+ <p data-domain-run-detail>Connect and rollback runs appear here.</p>
820
+ </div>
821
+ </div>
822
+
823
+ <p class="note publish-status" data-domain-status role="status" aria-live="polite"></p>
824
+
825
+ <!-- u9: apex-cutover readiness — the itemized preconditions the server gate
826
+ requires before a connect is permitted. Populated from
827
+ /api/domain/readiness (the SAME gate `tot go-live` reads). -->
828
+ <div class="domain-readiness" data-domain-readiness hidden>
829
+ <div class="domain-readiness-head">
830
+ <b>Cutover readiness</b>
831
+ <span class="chip" data-domain-readiness-chip>Checking…</span>
832
+ </div>
833
+ <ul class="domain-readiness-list" data-domain-readiness-list></ul>
834
+ <p class="note" data-domain-readiness-note></p>
835
+ </div>
836
+
837
+ <div class="check-row">
838
+ <div>
839
+ <b>Connect {tenant.appDomain} to the storefront</b>
840
+ <p>
841
+ Updates two DNS names — <code>{tenant.appDomain}</code> and <code>www.{tenant.appDomain}</code> —
842
+ to serve the published site. Takes effect in about a minute. Type the
843
+ domain to confirm.
844
+ </p>
845
+ {!viewerCanApexCutover && (
846
+ <p><span class="chip amber">Owner/admin only</span> Connecting the domain requires an owner or admin sign-in.</p>
847
+ )}
848
+ </div>
849
+ <div class="domain-connect-controls">
850
+ <input
851
+ type="text"
852
+ placeholder={tenant.appDomain}
853
+ autocomplete="off"
854
+ spellcheck="false"
855
+ aria-label="Type the domain to confirm"
856
+ data-domain-confirm
857
+ disabled={!viewerCanApexCutover}
858
+ />
859
+ <label class="domain-rehearsal">
860
+ <input type="checkbox" data-domain-rehearsal disabled={!viewerCanApexCutover} />
861
+ Rehearsal (dry run, no DNS change)
862
+ </label>
863
+ <button
864
+ class="btn primary"
865
+ type="button"
866
+ data-domain-connect
867
+ data-owner-locked={!viewerCanApexCutover ? "true" : undefined}
868
+ disabled={!viewerCanApexCutover}
869
+ >
870
+ <svg><use href="#i-check"></use></svg>Connect domain
871
+ </button>
872
+ </div>
873
+ </div>
874
+
875
+ <div class="check-row">
876
+ <div>
877
+ <b>Roll back to the previous host</b>
878
+ <p data-domain-rollback-detail>
879
+ Restores the DNS records captured before connect. Available once records
880
+ have been captured.
881
+ </p>
882
+ </div>
883
+ <button class="btn" type="button" data-domain-rollback disabled={!viewerCanShip}>
884
+ <svg><use href="#i-refresh"></use></svg>Roll back
885
+ </button>
886
+ </div>
887
+ </div>
711
888
  </div>
712
- <button class="btn" type="button" data-domain-rollback disabled={!viewerCanShip}>
713
- <svg><use href="#i-refresh"></use></svg>Roll back
714
- </button>
715
- </div>
889
+ )}
716
890
  </div>
717
- </div>
718
- )}
891
+ </details>
892
+ </div>
719
893
 
720
894
  <!-- Accept-to-preview LIFTOFF — the plan confirm + live integration flight +
721
895
  outcome ceremony. One dialog, three phases (plan → flight → outcome).
@@ -725,44 +899,45 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
725
899
  <div class="liftoff-backdrop" data-liftoff hidden>
726
900
  <div class="liftoff-card" role="dialog" aria-modal="true" aria-labelledby="liftoff-title" tabindex="-1">
727
901
  <div class="liftoff-phase" data-liftoff-plan>
728
- <p class="liftoff-kicker">Accept to preview</p>
729
- <h3 class="liftoff-title" id="liftoff-title" data-liftoff-title>Integrate into preview</h3>
902
+ <p class="liftoff-kicker">Add to release</p>
903
+ <h3 class="liftoff-title" id="liftoff-title" data-liftoff-title>Add to your next release</h3>
730
904
  <dl class="liftoff-grid" data-liftoff-grid></dl>
731
905
  <p class="note">
732
- Integrates this candidate into the protected <code>preview</code> aggregate, then
733
- rebuilds it and re-runs its combined evidence. <b>Nothing goes live</b> —
734
- publishing the reviewed build stays a separate, explicit step.
906
+ Adds this change to your next release, then rebuilds the preview and re-runs your
907
+ store's checks. <b>Nothing goes live</b> — publishing your release stays a
908
+ separate, explicit step.
735
909
  </p>
736
910
  <div class="liftoff-actions">
737
911
  <button class="btn" type="button" data-liftoff-cancel>Cancel</button>
738
912
  <button class="btn primary" type="button" data-liftoff-proceed>
739
- <svg><use href="#i-check"></use></svg>Integrate into preview
913
+ <svg><use href="#i-check"></use></svg>Add to release
740
914
  </button>
741
915
  </div>
742
916
  </div>
743
917
 
744
918
  <div class="liftoff-phase" data-liftoff-flight hidden>
745
- <p class="liftoff-kicker">Integration in flight</p>
746
- <h3 class="liftoff-title" data-liftoff-flight-title>Integrating…</h3>
919
+ <p class="liftoff-kicker">Adding to your release</p>
920
+ <h3 class="liftoff-title" data-liftoff-flight-title>Adding…</h3>
747
921
  <ol class="liftoff-stages">
748
922
  <li data-liftoff-stage="merge">
749
923
  <span class="liftoff-dot"></span>
750
- <div><b>Merge</b><span>squash-merge the candidate into <code>preview</code></span></div>
924
+ <div><b>Add</b><span>add this change to your next release</span></div>
751
925
  </li>
752
926
  <li data-liftoff-stage="build">
753
927
  <span class="liftoff-dot"></span>
754
- <div><b>Rebuild</b><span>build the combined preview aggregate</span></div>
928
+ <div><b>Build</b><span>rebuild the preview with your change in it</span></div>
755
929
  </li>
756
930
  <li data-liftoff-stage="evidence">
757
931
  <span class="liftoff-dot"></span>
758
- <div><b>Evidence</b><span>re-run the aggregate's combined checks</span></div>
932
+ <div><b>Check</b><span>re-run your store's checks on the preview</span></div>
759
933
  </li>
760
934
  <li data-liftoff-stage="green">
761
935
  <span class="liftoff-dot"></span>
762
- <div><b>Green</b><span>the aggregate becomes the reviewable, shippable build</span></div>
936
+ <div><b>Ready</b><span>your next release is ready to review and publish</span></div>
763
937
  </li>
764
938
  </ol>
765
939
  <p class="liftoff-live" data-liftoff-live role="status" aria-live="polite">Working…</p>
940
+ <div class="stream-log" data-liftoff-log role="log" aria-live="polite"></div>
766
941
  <div class="liftoff-meta">
767
942
  <span class="publish-mono" data-liftoff-elapsed>0:00</span>
768
943
  <button class="btn" type="button" data-liftoff-hide>Continue in background</button>
@@ -826,7 +1001,7 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
826
1001
  <p class="note" data-conflict-intro>
827
1002
  From the store checkout that submitted this change, bring it up to date and
828
1003
  re-submit — the open PR updates in place. Once the new build is ready,
829
- finish here with <b>Integrate into preview</b>, or from the terminal with
1004
+ finish here with <b>Add to release</b>, or from the terminal with
830
1005
  <code>tot accept --tenant &lt;store&gt; --pr &lt;N&gt;</code>.
831
1006
  Check <code>git status</code> first — abort any half-finished rebase from an
832
1007
  earlier attempt. Then <code>tot pr</code>: this change should be marked
@@ -843,6 +1018,11 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
843
1018
  </div>
844
1019
  <div class="liftoff-actions">
845
1020
  <a class="btn" data-liftoff-review target="_blank" rel="noopener" hidden>Review the preview build</a>
1021
+ <!-- Fail-visible next step: when the merge LANDED but the preview build
1022
+ failed, this offers Rebuild (not a dead "did not land"); on an
1023
+ indeterminate outcome it offers Re-check. Hidden on success/conflict
1024
+ (conflict recovery owns its own primary action). -->
1025
+ <button class="btn primary" type="button" data-liftoff-rebuild hidden>Rebuild the preview build</button>
846
1026
  <button class="btn primary" type="button" data-liftoff-done>Done</button>
847
1027
  </div>
848
1028
  </div>
@@ -881,8 +1061,10 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
881
1061
  <p class="liftoff-live" data-shipoff-live role="status" aria-live="polite">
882
1062
  Pinning the exact reviewed build to your live channel…
883
1063
  </p>
1064
+ <div class="stream-log" data-shipoff-log role="log" aria-live="polite"></div>
884
1065
  <div class="liftoff-meta">
885
1066
  <span class="publish-mono" data-shipoff-elapsed>0:00</span>
1067
+ <button class="btn" type="button" data-shipoff-hide>Continue in background</button>
886
1068
  </div>
887
1069
  </div>
888
1070
 
@@ -890,8 +1072,23 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
890
1072
  <div class="liftoff-seal" data-shipoff-seal aria-hidden="true"></div>
891
1073
  <h3 class="liftoff-title" data-shipoff-outcome-title></h3>
892
1074
  <p class="note" data-shipoff-outcome-detail></p>
1075
+ <!-- Failure detail + "Copy for your AI agent" — shown ONLY when the publish did
1076
+ not complete. Mirrors the build-flight failure affordance. -->
1077
+ <div class="liftoff-conflict" data-shipoff-failure hidden>
1078
+ <pre class="liftoff-commands publish-mono" data-shipoff-failure-detail></pre>
1079
+ <div class="liftoff-conflict-actions">
1080
+ <button class="btn" type="button" data-shipoff-copy>Copy for your AI agent</button>
1081
+ </div>
1082
+ </div>
893
1083
  <div class="liftoff-actions">
894
- <a class="btn primary" data-shipoff-view target="_blank" rel="noopener" hidden>View your live store</a>
1084
+ <a class="btn primary" data-shipoff-view target="_blank" rel="noopener" hidden>View live</a>
1085
+ <!-- r10 — right at the post-publish moment: jump straight to the rollback
1086
+ row for the version this release just replaced, pre-targeted, so a
1087
+ publish that looked wrong is one click from being undone. Reuses the
1088
+ existing Live Site rollback action; shown only on a successful ship. -->
1089
+ <button class="btn" type="button" data-shipoff-undo hidden>
1090
+ <svg><use href="#i-undo"></use></svg>Undo this release
1091
+ </button>
895
1092
  <button class="btn" type="button" data-shipoff-done>Done</button>
896
1093
  </div>
897
1094
  </div>
@@ -899,6 +1096,103 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
899
1096
  <div class="liftoff-confetti" data-shipoff-confetti aria-hidden="true"></div>
900
1097
  </div>
901
1098
  </div>
1099
+
1100
+ <!-- Build/check flight — the real-time counterpart of the accept liftoff for the
1101
+ "Build preview" / "Run checks" action. `POST /api/preview/build` streams its
1102
+ lifecycle phases over SSE (Accept: text/event-stream); this dialog reflects them
1103
+ live on the SAME stage-rail + "Continue in background" pattern as the liftoff.
1104
+ On a FAILURE the outcome shows the full detail the build reported plus a "Copy
1105
+ for your AI agent" button (mirrors the merge-conflict copy affordance). Progress
1106
+ reduction + copy assembly live in the tested buildStream.ts; this glue mirrors
1107
+ it (verified on preview). -->
1108
+ <div class="liftoff-backdrop" data-buildoff hidden>
1109
+ <div class="liftoff-card" role="dialog" aria-modal="true" aria-labelledby="buildoff-title" tabindex="-1">
1110
+ <div class="liftoff-phase" data-buildoff-flight>
1111
+ <p class="liftoff-kicker">Building preview</p>
1112
+ <h3 class="liftoff-title" id="buildoff-title" data-buildoff-flight-title>Building…</h3>
1113
+ <ol class="liftoff-stages">
1114
+ <li data-buildoff-stage="resolve">
1115
+ <span class="liftoff-dot"></span>
1116
+ <div><b>Locate commit</b><span>find the pull request's latest commit</span></div>
1117
+ </li>
1118
+ <li data-buildoff-stage="read">
1119
+ <span class="liftoff-dot"></span>
1120
+ <div><b>Read &amp; validate</b><span>read your store's files and check them</span></div>
1121
+ </li>
1122
+ <li data-buildoff-stage="publish">
1123
+ <span class="liftoff-dot"></span>
1124
+ <div><b>Publish version</b><span>materialize this version's hosted preview</span></div>
1125
+ </li>
1126
+ <li data-buildoff-stage="check">
1127
+ <span class="liftoff-dot"></span>
1128
+ <div><b>Run checks</b><span>compute the evidence for this version</span></div>
1129
+ </li>
1130
+ </ol>
1131
+ <p class="liftoff-live" data-buildoff-live role="status" aria-live="polite">Starting…</p>
1132
+ <div class="stream-log" data-buildoff-log role="log" aria-live="polite"></div>
1133
+ <div class="liftoff-meta">
1134
+ <span class="publish-mono" data-buildoff-elapsed>0:00</span>
1135
+ <button class="btn" type="button" data-buildoff-hide>Continue in background</button>
1136
+ </div>
1137
+ </div>
1138
+
1139
+ <div class="liftoff-phase liftoff-outcome-phase" data-buildoff-outcome hidden>
1140
+ <div class="liftoff-seal" data-buildoff-seal aria-hidden="true"></div>
1141
+ <h3 class="liftoff-title" data-buildoff-outcome-title></h3>
1142
+ <p class="note" data-buildoff-outcome-detail></p>
1143
+ <!-- Failure detail + "Copy for your AI agent" — shown ONLY when the build did
1144
+ not succeed. The detail is the honest reason the build reported (errors +
1145
+ blocking findings); the copy button hands a framed brief to a coding agent. -->
1146
+ <div class="liftoff-conflict" data-buildoff-failure hidden>
1147
+ <pre class="liftoff-commands publish-mono" data-buildoff-failure-detail></pre>
1148
+ <div class="liftoff-conflict-actions">
1149
+ <button class="btn" type="button" data-buildoff-copy>Copy for your AI agent</button>
1150
+ </div>
1151
+ </div>
1152
+ <div class="liftoff-actions">
1153
+ <a class="btn" data-buildoff-review target="_blank" rel="noopener" hidden>Open the preview</a>
1154
+ <button class="btn primary" type="button" data-buildoff-done>Done</button>
1155
+ </div>
1156
+ </div>
1157
+ </div>
1158
+ </div>
1159
+
1160
+ <!-- Retry-build flight — the real-time counterpart for the aggregate "Retry build"
1161
+ action (`POST /api/changes/rebuild`), which re-runs ONLY the aggregate rebuild +
1162
+ combined evidence against the last merged commit (no new merge). It streams its
1163
+ reading/publishing/checking phases over SSE into the SAME shared scrolling
1164
+ live-log console; "Continue in background" hides it while the stream keeps
1165
+ running, and on a FAILURE the outcome shows the honest detail + a "Copy for your
1166
+ AI agent" button — the same affordances as the build/ship flights. -->
1167
+ <div class="liftoff-backdrop" data-rebuildoff hidden>
1168
+ <div class="liftoff-card" role="dialog" aria-modal="true" aria-labelledby="rebuildoff-title" tabindex="-1">
1169
+ <div class="liftoff-phase" data-rebuildoff-flight>
1170
+ <p class="liftoff-kicker">Retrying the build</p>
1171
+ <h3 class="liftoff-title" id="rebuildoff-title">Rebuilding your next release preview…</h3>
1172
+ <p class="liftoff-live" data-rebuildoff-live role="status" aria-live="polite">Starting…</p>
1173
+ <div class="stream-log" data-rebuildoff-log role="log" aria-live="polite"></div>
1174
+ <div class="liftoff-meta">
1175
+ <span class="publish-mono" data-rebuildoff-elapsed>0:00</span>
1176
+ <button class="btn" type="button" data-rebuildoff-hide>Continue in background</button>
1177
+ </div>
1178
+ </div>
1179
+
1180
+ <div class="liftoff-phase liftoff-outcome-phase" data-rebuildoff-outcome hidden>
1181
+ <div class="liftoff-seal" data-rebuildoff-seal aria-hidden="true"></div>
1182
+ <h3 class="liftoff-title" data-rebuildoff-outcome-title></h3>
1183
+ <p class="note" data-rebuildoff-outcome-detail></p>
1184
+ <div class="liftoff-conflict" data-rebuildoff-failure hidden>
1185
+ <pre class="liftoff-commands publish-mono" data-rebuildoff-failure-detail></pre>
1186
+ <div class="liftoff-conflict-actions">
1187
+ <button class="btn" type="button" data-rebuildoff-copy>Copy for your AI agent</button>
1188
+ </div>
1189
+ </div>
1190
+ <div class="liftoff-actions">
1191
+ <button class="btn primary" type="button" data-rebuildoff-done>Done</button>
1192
+ </div>
1193
+ </div>
1194
+ </div>
1195
+ </div>
902
1196
  </section>
903
1197
 
904
1198
  <script>
@@ -911,11 +1205,241 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
911
1205
  min-width: 900px;
912
1206
  }
913
1207
 
1208
+ /* r06 — responsive change queue. On a narrow screen the min-width:900px queue
1209
+ table forces horizontal scroll, unusable on a phone. Below ~700px, restack the
1210
+ existing rows into labeled cards — CSS-only, no DOM/JS change: the table, rows
1211
+ and cells become blocks, the visual header is hidden (kept for assistive tech),
1212
+ and each cell gains its column label via a ::before drawn from the fixed thead
1213
+ order. Scoped to #publish-queue so the aggregate/versions tables are untouched.
1214
+ Full-width colspan rows (empty state, sign-in notice, the inline review pane)
1215
+ stay unlabeled and full width. */
1216
+ @media (max-width: 700px) {
1217
+ #publish-queue .publish-table,
1218
+ #publish-queue .publish-table tbody,
1219
+ #publish-queue .publish-table tr,
1220
+ #publish-queue .publish-table td {
1221
+ display: block;
1222
+ min-width: 0;
1223
+ width: auto;
1224
+ }
1225
+ #publish-queue .publish-table thead {
1226
+ position: absolute;
1227
+ width: 1px;
1228
+ height: 1px;
1229
+ overflow: hidden;
1230
+ clip: rect(0 0 0 0);
1231
+ clip-path: inset(50%);
1232
+ white-space: nowrap;
1233
+ }
1234
+ #publish-queue .publish-table tbody tr {
1235
+ margin: 0 0 12px;
1236
+ padding: 10px 12px;
1237
+ border: 1px solid var(--admin-line);
1238
+ border-radius: var(--admin-radius-sm);
1239
+ background: var(--admin-surface, #fff);
1240
+ }
1241
+ #publish-queue .publish-table tbody td {
1242
+ padding: 4px 0;
1243
+ text-align: left;
1244
+ border: 0;
1245
+ }
1246
+ #publish-queue .publish-table tbody td:not([colspan])::before {
1247
+ display: block;
1248
+ margin-bottom: 2px;
1249
+ font-size: 11px;
1250
+ font-weight: 600;
1251
+ text-transform: uppercase;
1252
+ letter-spacing: 0.04em;
1253
+ opacity: 0.6;
1254
+ }
1255
+ #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(1)::before {
1256
+ content: "Change";
1257
+ }
1258
+ #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(2)::before {
1259
+ content: "Status";
1260
+ }
1261
+ #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(3)::before {
1262
+ content: "Checks";
1263
+ }
1264
+ #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(4)::before {
1265
+ content: "Head";
1266
+ }
1267
+ #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(5)::before {
1268
+ content: "Updated";
1269
+ }
1270
+ #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(6)::before {
1271
+ content: "Actions";
1272
+ }
1273
+ }
1274
+
1275
+ /* Collapsible panels (What needs your approval, Next release, Live Site): the
1276
+ panel-head IS the summary. Kill the native marker, add a rotating chevron, and
1277
+ make the whole head a click target. Mirrors the Rollback options (.versions-details). */
1278
+ .publish-queue-details > summary.panel-head,
1279
+ .closed-changes-details > summary.panel-head,
1280
+ .agg-console-details > summary.panel-head,
1281
+ .live-site-details > summary.panel-head,
1282
+ .versions-details > summary.live-site-subhead {
1283
+ cursor: pointer;
1284
+ list-style: none;
1285
+ user-select: none;
1286
+ }
1287
+ .publish-queue-details > summary.panel-head::-webkit-details-marker,
1288
+ .closed-changes-details > summary.panel-head::-webkit-details-marker,
1289
+ .agg-console-details > summary.panel-head::-webkit-details-marker,
1290
+ .live-site-details > summary.panel-head::-webkit-details-marker,
1291
+ .versions-details > summary.live-site-subhead::-webkit-details-marker {
1292
+ display: none;
1293
+ }
1294
+ .publish-queue-details > summary.panel-head h3::before,
1295
+ .closed-changes-details > summary.panel-head h3::before,
1296
+ .agg-console-details > summary.panel-head h3::before,
1297
+ .live-site-details > summary.panel-head h3::before,
1298
+ .versions-details > summary.live-site-subhead h4::before {
1299
+ content: "";
1300
+ width: 7px;
1301
+ height: 7px;
1302
+ margin-right: 2px;
1303
+ border-right: 2px solid currentColor;
1304
+ border-bottom: 2px solid currentColor;
1305
+ transform: rotate(-45deg);
1306
+ transition: transform 0.15s ease;
1307
+ opacity: 0.6;
1308
+ }
1309
+ .publish-queue-details[open] > summary.panel-head h3::before,
1310
+ .closed-changes-details[open] > summary.panel-head h3::before,
1311
+ .agg-console-details[open] > summary.panel-head h3::before,
1312
+ .live-site-details[open] > summary.panel-head h3::before,
1313
+ .versions-details[open] > summary.live-site-subhead h4::before {
1314
+ transform: rotate(45deg);
1315
+ }
1316
+
1317
+ /* The collapsed release summary owns one mutually exclusive state affordance:
1318
+ a green current-state chip or the action that advances live. Both classes set
1319
+ display, so explicitly preserve the semantic hidden state. */
1320
+ [data-live-release-state][hidden],
1321
+ .live-release-publish[hidden] {
1322
+ display: none;
1323
+ }
1324
+
1325
+ /* Live Site — the two clickable public entry points, previously undiscoverable. */
1326
+ .live-site-links {
1327
+ list-style: none;
1328
+ margin: 0;
1329
+ padding: 0;
1330
+ display: grid;
1331
+ gap: 12px;
1332
+ }
1333
+ .live-site-links > li {
1334
+ display: grid;
1335
+ grid-template-columns: minmax(96px, auto) 1fr;
1336
+ align-items: baseline;
1337
+ gap: 4px 12px;
1338
+ }
1339
+ .live-site-link-label {
1340
+ font-weight: 600;
1341
+ font-size: 13px;
1342
+ color: var(--admin-fg, inherit);
1343
+ }
1344
+ .live-site-link-url {
1345
+ word-break: break-all;
1346
+ font-size: 13px;
1347
+ }
1348
+ .live-site-link-note {
1349
+ grid-column: 2;
1350
+ font-size: 12px;
1351
+ opacity: 0.7;
1352
+ }
1353
+
1354
+ /* r13 — Rollback + Public domain nested INSIDE Live Site read as a subsection,
1355
+ not a sibling panel: a single left rule + generous spacing rather than a boxed
1356
+ card, so the Live Site panel doesn't stack boxes-in-boxes. */
1357
+ .live-site-subsection {
1358
+ border-left: 2px solid var(--admin-line);
1359
+ padding-left: 16px;
1360
+ margin-top: 18px;
1361
+ display: grid;
1362
+ gap: 12px;
1363
+ }
1364
+ /* r13 — the Public-domain status trio (Serving from / Rollback records / Last run)
1365
+ were full `.field` cards (border + fill) nested inside the Live Site panel — the
1366
+ genuine boxes-in-boxes. Flatten them to plain label/value blocks (hooks kept);
1367
+ the subsection's left rule + grid gap carry the separation instead. */
1368
+ .live-site-subsection .field {
1369
+ border: 0;
1370
+ background: transparent;
1371
+ padding: 0;
1372
+ }
1373
+ .live-site-subhead {
1374
+ display: flex;
1375
+ align-items: center;
1376
+ justify-content: space-between;
1377
+ gap: 12px;
1378
+ }
1379
+ .live-site-subhead h4 {
1380
+ margin: 0;
1381
+ display: flex;
1382
+ align-items: center;
1383
+ gap: 8px;
1384
+ font-size: 14px;
1385
+ }
1386
+
914
1387
  .publish-mono {
915
1388
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
916
1389
  font-size: 12px;
917
1390
  }
918
1391
 
1392
+ /* r11 — the human release name beside the "Next release" heading. */
1393
+ .agg-release-name {
1394
+ font-size: 13px;
1395
+ font-weight: 600;
1396
+ color: var(--admin-muted);
1397
+ }
1398
+ .agg-release-name[hidden] {
1399
+ display: none;
1400
+ }
1401
+
1402
+ /* r02 — the humanized "Live version" metric: "v42" as a link to the live store,
1403
+ with the raw SHA tucked into a "Technical details" disclosure. */
1404
+ .live-version-link {
1405
+ color: inherit;
1406
+ text-decoration: none;
1407
+ border-bottom: 1px solid color-mix(in srgb, currentColor 35%, transparent);
1408
+ }
1409
+ .live-version-link:hover {
1410
+ border-bottom-color: currentColor;
1411
+ }
1412
+ .metric-tech,
1413
+ .ver-tech {
1414
+ margin-top: 4px;
1415
+ font-size: 12px;
1416
+ }
1417
+ .metric-tech > summary,
1418
+ .ver-tech > summary {
1419
+ cursor: pointer;
1420
+ color: var(--muted, #64716d);
1421
+ font-size: 11px;
1422
+ list-style: none;
1423
+ }
1424
+ .metric-tech > summary::-webkit-details-marker,
1425
+ .ver-tech > summary::-webkit-details-marker {
1426
+ display: none;
1427
+ }
1428
+ .ver-tech {
1429
+ margin-top: 6px;
1430
+ }
1431
+
1432
+ /* r08 — the "Copy preview link" affordance (icon + text, never color-only). */
1433
+ .publish-copy-preview {
1434
+ display: inline-flex;
1435
+ align-items: center;
1436
+ gap: 5px;
1437
+ }
1438
+ .publish-copy-preview svg {
1439
+ width: 14px;
1440
+ height: 14px;
1441
+ }
1442
+
919
1443
  .publish-actions {
920
1444
  white-space: nowrap;
921
1445
  }
@@ -964,6 +1488,148 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
964
1488
  color: var(--admin-green);
965
1489
  }
966
1490
 
1491
+ /* ── State-driven hero (r01) ─────────────────────────────────────────────
1492
+ The single top-of-tab state element. One row: an icon whose tone conveys
1493
+ urgency (never color alone — a "Action needed" kicker rides the two blocker
1494
+ tones), the one-sentence state + supporting detail, and one primary CTA laid
1495
+ out to the trailing edge (like the paywall gate). Tone is set by
1496
+ renderHero() via [data-tone]. */
1497
+ .publish-hero-region {
1498
+ display: grid;
1499
+ gap: 12px;
1500
+ }
1501
+ .publish-hero {
1502
+ display: flex;
1503
+ align-items: center;
1504
+ justify-content: space-between;
1505
+ gap: 16px;
1506
+ flex-wrap: wrap;
1507
+ padding: 14px 16px;
1508
+ border: 1px solid var(--admin-line-strong);
1509
+ border-left-width: 4px;
1510
+ border-radius: var(--admin-radius, 12px);
1511
+ background: var(--admin-surface);
1512
+ }
1513
+ .publish-hero[hidden] {
1514
+ display: none;
1515
+ }
1516
+ .publish-hero-main {
1517
+ display: flex;
1518
+ align-items: center;
1519
+ gap: 13px;
1520
+ min-width: 0;
1521
+ }
1522
+ .publish-hero-icon {
1523
+ flex: none;
1524
+ width: 34px;
1525
+ height: 34px;
1526
+ display: grid;
1527
+ place-items: center;
1528
+ border-radius: 9px;
1529
+ background: var(--admin-surface-soft);
1530
+ }
1531
+ .publish-hero-icon svg {
1532
+ width: 19px;
1533
+ height: 19px;
1534
+ }
1535
+ .publish-hero-copy {
1536
+ display: grid;
1537
+ gap: 2px;
1538
+ min-width: 0;
1539
+ }
1540
+ .publish-hero-kicker {
1541
+ font-size: 10.5px;
1542
+ font-weight: 760;
1543
+ letter-spacing: 0.08em;
1544
+ text-transform: uppercase;
1545
+ color: var(--admin-red);
1546
+ }
1547
+ .publish-hero-kicker[hidden] {
1548
+ display: none;
1549
+ }
1550
+ .publish-hero-headline {
1551
+ font-size: 15.5px;
1552
+ line-height: 1.3;
1553
+ color: var(--admin-ink);
1554
+ }
1555
+ .publish-hero-detail {
1556
+ font-size: 13px;
1557
+ color: var(--admin-muted);
1558
+ line-height: 1.45;
1559
+ }
1560
+ .publish-hero-detail:empty {
1561
+ display: none;
1562
+ }
1563
+ .publish-hero-actions {
1564
+ flex: none;
1565
+ display: flex;
1566
+ gap: 8px;
1567
+ }
1568
+ .publish-hero-cta[hidden] {
1569
+ display: none;
1570
+ }
1571
+
1572
+ /* Tones — the accent rail + icon colour. Blocker/warn borrow the admin
1573
+ red/amber tokens; ready/attention the teal; calm stays neutral. */
1574
+ .publish-hero[data-tone="blocker"] {
1575
+ border-left-color: var(--admin-red);
1576
+ background: var(--admin-red-soft);
1577
+ }
1578
+ .publish-hero[data-tone="blocker"] .publish-hero-icon svg {
1579
+ color: var(--admin-red);
1580
+ }
1581
+ .publish-hero[data-tone="warn"] {
1582
+ border-left-color: var(--admin-amber);
1583
+ background: var(--admin-surface-soft);
1584
+ }
1585
+ .publish-hero[data-tone="warn"] .publish-hero-icon svg {
1586
+ color: var(--admin-amber);
1587
+ }
1588
+ .publish-hero[data-tone="attention"] {
1589
+ border-left-color: var(--admin-teal, #0f9d8f);
1590
+ }
1591
+ .publish-hero[data-tone="attention"] .publish-hero-icon svg {
1592
+ color: var(--admin-teal, #0f9d8f);
1593
+ }
1594
+ .publish-hero[data-tone="ready"] {
1595
+ border-left-color: var(--admin-green);
1596
+ background: var(--admin-green-soft);
1597
+ }
1598
+ .publish-hero[data-tone="ready"] .publish-hero-icon svg {
1599
+ color: var(--admin-green);
1600
+ }
1601
+ .publish-hero[data-tone="calm"] {
1602
+ border-left-color: var(--admin-line-strong);
1603
+ }
1604
+ .publish-hero[data-tone="calm"] .publish-hero-icon svg {
1605
+ color: var(--admin-green);
1606
+ }
1607
+
1608
+ /* r03 stale-public-site alarm — lays its "Open Live Site" deep-link to the
1609
+ trailing edge, like the paywall gate. Inherits the amber .risk-alert look. */
1610
+ .risk-alert.publish-stale-site {
1611
+ display: flex;
1612
+ align-items: center;
1613
+ justify-content: space-between;
1614
+ gap: 12px;
1615
+ flex-wrap: wrap;
1616
+ }
1617
+ .risk-alert.publish-stale-site .btn {
1618
+ flex: none;
1619
+ }
1620
+
1621
+ /* Per-row finding: the ownership-tag chip leads the row's action cell (what's
1622
+ wrong, in operator words), and the mapped action button gets a subtle ring so
1623
+ the operator sees which button resolves it. Tone (red/amber/neutral) rides the
1624
+ shared .chip convention; primed emphasis is added to the EXISTING button. */
1625
+ .publish-actions .publish-finding-chip {
1626
+ vertical-align: middle;
1627
+ }
1628
+
1629
+ .publish-actions .btn.publish-cta-primed {
1630
+ box-shadow: 0 0 0 2px var(--admin-amber);
1631
+ }
1632
+
967
1633
  /* ?pr=N deep link: "not in the queue" callout + row flash */
968
1634
  .publish-pr-callout {
969
1635
  border: 1px solid var(--admin-line-strong);
@@ -1139,7 +1805,7 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
1139
1805
  overflow-wrap: anywhere;
1140
1806
  }
1141
1807
 
1142
- /* u4 — automate-first recovery: the primary action leads, option cards appear
1808
+ /* Automate-first recovery: the primary action leads, option cards appear
1143
1809
  only on a genuine overlap, the terminal guidance is a collapsed fallback. */
1144
1810
  .liftoff-conflict-summary {
1145
1811
  margin: 0 0 10px;
@@ -1320,6 +1986,31 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
1320
1986
  color: var(--admin-ink);
1321
1987
  }
1322
1988
 
1989
+ /* The shared scrolling live-log console — a "CI build log" window reused across
1990
+ the build / integrate / rebuild / ship flight dialogs. Fixed height, scrolls,
1991
+ newest line auto-scrolled into view by the client (see makeLogConsole). */
1992
+ .stream-log {
1993
+ margin: 10px 0 0;
1994
+ max-height: 150px;
1995
+ overflow-y: auto;
1996
+ padding: 8px 10px;
1997
+ border: 1px solid var(--admin-border, rgba(0, 0, 0, 0.12));
1998
+ border-radius: 8px;
1999
+ background: var(--admin-code-bg, rgba(0, 0, 0, 0.04));
2000
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
2001
+ font-size: 12px;
2002
+ line-height: 1.5;
2003
+ color: var(--admin-ink);
2004
+ -webkit-overflow-scrolling: touch;
2005
+ }
2006
+ .stream-log:empty {
2007
+ display: none;
2008
+ }
2009
+ .stream-log-line {
2010
+ white-space: pre-wrap;
2011
+ word-break: break-word;
2012
+ }
2013
+
1323
2014
  .liftoff-meta {
1324
2015
  display: flex;
1325
2016
  justify-content: space-between;
@@ -1444,7 +2135,7 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
1444
2135
  }
1445
2136
  }
1446
2137
 
1447
- /* u9: apex-cutover readiness itemization */
2138
+ /* Apex-cutover readiness itemization */
1448
2139
  .domain-readiness {
1449
2140
  margin: 0.75rem 0;
1450
2141
  padding: 0.75rem 1rem;
@@ -1500,7 +2191,7 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
1500
2191
  opacity: 0.95;
1501
2192
  }
1502
2193
 
1503
- /* b11: aggregate card + ship plan */
2194
+ /* Aggregate card + ship plan */
1504
2195
  .aggregate-prs {
1505
2196
  display: grid;
1506
2197
  gap: 6px;
@@ -1521,12 +2212,12 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
1521
2212
  font-size: 13px;
1522
2213
  }
1523
2214
 
1524
- /* u14 — provenance ("integrated <age> by <email>") sits after the sha, subtle. */
2215
+ /* Provenance ("integrated <age> by <email>") sits after the sha, subtle. */
1525
2216
  .aggregate-pr-meta {
1526
2217
  font-size: 12px;
1527
2218
  }
1528
2219
 
1529
- /* u14 — the per-change Undo (revert) button is pushed to the row's trailing edge
2220
+ /* The per-change Undo (revert) button is pushed to the row's trailing edge
1530
2221
  and kept compact so the batch reads as a list, not a stack of buttons. */
1531
2222
  .aggregate-prs-list .btn-compact {
1532
2223
  margin-left: auto;
@@ -1827,41 +2518,6 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
1827
2518
  font-weight: 720;
1828
2519
  }
1829
2520
 
1830
- /* Staged change rows (richer than the plain PR list) */
1831
- .agg-changes li.agg-change {
1832
- display: grid;
1833
- grid-template-columns: 1fr auto;
1834
- align-items: center;
1835
- gap: 12px;
1836
- padding: 10px 12px;
1837
- border: 1px solid var(--admin-line);
1838
- border-radius: var(--admin-radius-sm);
1839
- background: var(--admin-surface);
1840
- }
1841
- .agg-change-main {
1842
- display: grid;
1843
- gap: 4px;
1844
- min-width: 0;
1845
- }
1846
- .agg-change-line {
1847
- display: flex;
1848
- align-items: center;
1849
- gap: 10px;
1850
- flex-wrap: wrap;
1851
- }
1852
- .agg-change-pr {
1853
- font-weight: 720;
1854
- font-size: 13px;
1855
- }
1856
- .agg-change-title {
1857
- font-weight: 600;
1858
- font-size: 13.5px;
1859
- }
1860
- .agg-change-sub {
1861
- color: var(--admin-muted);
1862
- font-size: 12px;
1863
- }
1864
-
1865
2521
  /* Publish plan → launch manifest */
1866
2522
  [data-publish-aggregate] .agg-manifest {
1867
2523
  display: block;
@@ -1896,13 +2552,10 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
1896
2552
  background: var(--admin-surface-soft);
1897
2553
  }
1898
2554
 
1899
- /* Openable "Included changes" */
1900
- .agg-included {
1901
- border-top: 1px dashed var(--admin-line-strong);
1902
- }
1903
- .agg-included[hidden] {
1904
- display: none;
1905
- }
2555
+ /* Openable "Included changes" — reused by the Published Versions "What
2556
+ shipped" per-version disclosure (.ver-changes below); the staged-delta
2557
+ table above no longer uses the <details> wrap itself, only the row/diff
2558
+ classes it shares with changeSummaryItem. */
1906
2559
  .agg-included-details > summary {
1907
2560
  list-style: none;
1908
2561
  cursor: pointer;
@@ -1938,14 +2591,6 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
1938
2591
  font-size: 11.5px;
1939
2592
  font-weight: 720;
1940
2593
  }
1941
- .agg-included-hint {
1942
- color: var(--admin-muted-2);
1943
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1944
- font-size: 11.5px;
1945
- overflow: hidden;
1946
- text-overflow: ellipsis;
1947
- white-space: nowrap;
1948
- }
1949
2594
  .agg-chev {
1950
2595
  margin-left: auto;
1951
2596
  display: inline-flex;
@@ -2212,12 +2857,12 @@ const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness"
2212
2857
  }
2213
2858
  }
2214
2859
 
2215
- /* dg7: versions + diff */
2860
+ /* Versions + diff */
2216
2861
  .versions-table {
2217
2862
  min-width: 780px;
2218
2863
  }
2219
2864
 
2220
- /* rux6: unified review pane */
2865
+ /* Unified review pane */
2221
2866
  .publish-review-cell {
2222
2867
  padding: 12px 14px;
2223
2868
  background: var(--admin-surface-soft);