@tokenoftrust/storefront-runner 2.0.2 → 2.0.4

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 (254) hide show
  1. package/apps/storefront/.astro/content.d.ts +159 -0
  2. package/apps/storefront/.astro/integrations/_astrojs_cloudflare/cloudflare.d.ts +1 -0
  3. package/apps/storefront/.astro/types.d.ts +3 -0
  4. package/apps/storefront/node_modules/.bin/acorn +43 -0
  5. package/apps/storefront/node_modules/.bin/astro +43 -0
  6. package/apps/storefront/node_modules/.bin/astro-check +43 -0
  7. package/apps/storefront/node_modules/.bin/browserslist +43 -0
  8. package/apps/storefront/node_modules/.bin/cf-wrangler +43 -0
  9. package/apps/storefront/node_modules/.bin/depcruise +43 -0
  10. package/apps/storefront/node_modules/.bin/depcruise-baseline +43 -0
  11. package/apps/storefront/node_modules/.bin/depcruise-fmt +43 -0
  12. package/apps/storefront/node_modules/.bin/depcruise-wrap-stream-in-html +43 -0
  13. package/apps/storefront/node_modules/.bin/dependency-cruise +43 -0
  14. package/apps/storefront/node_modules/.bin/dependency-cruiser +43 -0
  15. package/apps/storefront/node_modules/.bin/drizzle-kit +43 -0
  16. package/apps/storefront/node_modules/.bin/esbuild +43 -0
  17. package/apps/storefront/node_modules/.bin/jiti +43 -0
  18. package/apps/storefront/node_modules/.bin/playwright +43 -0
  19. package/apps/storefront/node_modules/.bin/playwright-core +43 -0
  20. package/apps/storefront/node_modules/.bin/prettier +43 -0
  21. package/apps/storefront/node_modules/.bin/rollup +43 -0
  22. package/apps/storefront/node_modules/.bin/tsc +43 -0
  23. package/apps/storefront/node_modules/.bin/tsserver +43 -0
  24. package/apps/storefront/node_modules/.bin/tsx +43 -0
  25. package/apps/storefront/node_modules/.bin/vite +43 -0
  26. package/apps/storefront/node_modules/.bin/vitest +43 -0
  27. package/apps/storefront/node_modules/.bin/workerd +43 -0
  28. package/apps/storefront/node_modules/.bin/wrangler +43 -0
  29. package/apps/storefront/node_modules/.bin/wrangler2 +43 -0
  30. package/apps/storefront/node_modules/.bin/yaml +43 -0
  31. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js +58 -0
  32. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js.map +1 -0
  33. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js +741 -0
  34. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js.map +1 -0
  35. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js +2549 -0
  36. package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js.map +1 -0
  37. package/apps/storefront/node_modules/.vite/deps/_metadata.json +150 -0
  38. package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js +434 -0
  39. package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js.map +1 -0
  40. package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  41. package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  42. package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js +2705 -0
  43. package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  44. package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  45. package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  46. package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  47. package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  48. package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js +1535 -0
  49. package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js.map +1 -0
  50. package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js +48 -0
  51. package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js.map +1 -0
  52. package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js +42 -0
  53. package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js.map +1 -0
  54. package/apps/storefront/node_modules/.vite/deps/package.json +3 -0
  55. package/apps/storefront/node_modules/.vite/deps/preact.js +310 -0
  56. package/apps/storefront/node_modules/.vite/deps/preact.js.map +1 -0
  57. package/apps/storefront/node_modules/.vite/deps/preact_compat.js +420 -0
  58. package/apps/storefront/node_modules/.vite/deps/preact_compat.js.map +1 -0
  59. package/apps/storefront/node_modules/.vite/deps/preact_compat_jsx-runtime.js +4 -0
  60. package/apps/storefront/node_modules/.vite/deps/preact_debug.js +236 -0
  61. package/apps/storefront/node_modules/.vite/deps/preact_debug.js.map +1 -0
  62. package/apps/storefront/node_modules/.vite/deps/preact_devtools.js +12 -0
  63. package/apps/storefront/node_modules/.vite/deps/preact_devtools.js.map +1 -0
  64. package/apps/storefront/node_modules/.vite/deps/preact_hooks.js +192 -0
  65. package/apps/storefront/node_modules/.vite/deps/preact_hooks.js.map +1 -0
  66. package/apps/storefront/node_modules/.vite/deps/preact_jsx-dev-runtime.js +3 -0
  67. package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js +92 -0
  68. package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js.map +1 -0
  69. package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js +59 -0
  70. package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js.map +1 -0
  71. package/apps/storefront/node_modules/.vite/deps/rolldown-runtime-BPOCksWG.js +24 -0
  72. package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js +189 -0
  73. package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js.map +1 -0
  74. package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js +13 -0
  75. package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js.map +1 -0
  76. package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js +458 -0
  77. package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js.map +1 -0
  78. package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js +1016 -0
  79. package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js.map +1 -0
  80. package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js +32 -0
  81. package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js.map +1 -0
  82. package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js +113 -0
  83. package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js.map +1 -0
  84. package/apps/storefront/node_modules/.vite/deps_ssr/_metadata.json +8 -0
  85. package/apps/storefront/node_modules/.vite/deps_ssr/package.json +3 -0
  86. package/apps/storefront/src/layouts/Layout.astro +1 -1
  87. package/apps/storefront/src/lib/apps/registryAccess.ts +45 -0
  88. package/apps/storefront/src/lib/auth/adminEntry.ts +4 -4
  89. package/apps/storefront/src/lib/shared/capability.ts +68 -0
  90. package/apps/storefront/src/lib/shared/developerInvites.ts +1250 -0
  91. package/apps/storefront/src/lib/shared/invitePreflight.ts +123 -0
  92. package/apps/storefront/src/lib/webhooks/deliveryEngine.ts +38 -4
  93. package/apps/storefront/src/lib/webhooks/emit.ts +1 -1
  94. package/apps/storefront/src/lib/webhooks/getDispatcher.ts +1 -1
  95. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +1 -1
  96. package/apps/storefront/src/pages/index.astro +1 -1
  97. package/apps/storefront/src/pages/products/[handle].astro +1 -1
  98. package/package.json +1 -1
  99. package/packages/public-runtime/node_modules/.bin/tsc +43 -0
  100. package/packages/public-runtime/node_modules/.bin/tsserver +43 -0
  101. package/packages/public-runtime/node_modules/.bin/vitest +43 -0
  102. package/pnpm-workspace.yaml +3 -0
  103. package/scripts/lib/tenant-dirs.mjs +80 -0
  104. package/scripts/tenant/lint-embeds.mjs +180 -0
  105. package/scripts/tenant/materialize-tenant-content.mjs +533 -0
  106. package/scripts/tenant/validate.mjs +837 -0
  107. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +0 -68
  108. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +0 -111
  109. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +0 -2
  110. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +0 -247
  111. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +0 -73
  112. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +0 -79
  113. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +0 -10982
  114. package/apps/storefront/scripts/import-v84-through-ws2.ts +0 -38
  115. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +0 -2
  116. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +0 -4
  117. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +0 -185
  118. package/apps/storefront/scripts/prove-clone-isolation.ts +0 -506
  119. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +0 -148
  120. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +0 -35
  121. package/apps/storefront/scripts/replay-v84-clean-clone.ts +0 -214
  122. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +0 -18
  123. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +0 -18
  124. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +0 -18
  125. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +0 -17
  126. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +0 -15
  127. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +0 -21
  128. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +0 -42
  129. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +0 -145
  130. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +0 -18
  131. package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +0 -531
  132. package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +0 -878
  133. package/apps/storefront/src/components/admin/AdminBlogTab.astro +0 -1435
  134. package/apps/storefront/src/components/admin/AdminCheckoutSettingsPanel.astro +0 -226
  135. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +0 -593
  136. package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +0 -465
  137. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +0 -105
  138. package/apps/storefront/src/components/admin/AdminProductsTab.astro +0 -901
  139. package/apps/storefront/src/components/admin/AdminPublishTab.astro +0 -2983
  140. package/apps/storefront/src/components/admin/AdminReportingTab.astro +0 -435
  141. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +0 -782
  142. package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +0 -611
  143. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +0 -1223
  144. package/apps/storefront/src/lib/apps/adminService.ts +0 -116
  145. package/apps/storefront/src/lib/apps/adminSession.ts +0 -214
  146. package/apps/storefront/src/lib/apps/apiAuth.ts +0 -91
  147. package/apps/storefront/src/lib/apps/apiRoute.ts +0 -35
  148. package/apps/storefront/src/lib/apps/catalogMapper.ts +0 -39
  149. package/apps/storefront/src/lib/apps/consentConfigStore.ts +0 -228
  150. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +0 -447
  151. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +0 -367
  152. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +0 -315
  153. package/apps/storefront/src/lib/apps/healthAggregate.ts +0 -66
  154. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +0 -614
  155. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +0 -125
  156. package/apps/storefront/src/lib/apps/integrationReadModel.ts +0 -563
  157. package/apps/storefront/src/lib/apps/integrationsTab.ts +0 -547
  158. package/apps/storefront/src/lib/apps/integrationsView.ts +0 -106
  159. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +0 -599
  160. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +0 -170
  161. package/apps/storefront/src/lib/apps/tokenIssuer.ts +0 -121
  162. package/apps/storefront/src/lib/apps/tokenVerifier.ts +0 -148
  163. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +0 -16
  164. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +0 -105
  165. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +0 -177
  166. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +0 -251
  167. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +0 -241
  168. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +0 -211
  169. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +0 -409
  170. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +0 -368
  171. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +0 -188
  172. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +0 -291
  173. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +0 -95
  174. package/apps/storefront/src/lib/content-edit/applyDiff.ts +0 -91
  175. package/apps/storefront/src/lib/content-edit/client.ts +0 -286
  176. package/apps/storefront/src/lib/content-edit/index.ts +0 -14
  177. package/apps/storefront/src/lib/content-edit/route.ts +0 -25
  178. package/apps/storefront/src/lib/content-edit/types.ts +0 -19
  179. package/apps/storefront/src/lib/dev/cockpitStore.ts +0 -57
  180. package/apps/storefront/src/lib/publish/adminPublishTab.ts +0 -4625
  181. package/apps/storefront/src/lib/publish/apex-readiness.ts +0 -360
  182. package/apps/storefront/src/lib/publish/buildStream.ts +0 -269
  183. package/apps/storefront/src/lib/publish/collapsePersistence.ts +0 -72
  184. package/apps/storefront/src/lib/publish/dispatchHealth.ts +0 -267
  185. package/apps/storefront/src/lib/publish/domainState.ts +0 -360
  186. package/apps/storefront/src/lib/publish/lifecycleState.ts +0 -369
  187. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +0 -29
  188. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +0 -78
  189. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +0 -62
  190. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +0 -124
  191. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +0 -75
  192. package/apps/storefront/src/lib/publish/panelPolling.ts +0 -78
  193. package/apps/storefront/src/lib/publish/publish-guard.ts +0 -88
  194. package/apps/storefront/src/lib/publish/publishFormat.ts +0 -165
  195. package/apps/storefront/src/lib/publish/publishHero.ts +0 -177
  196. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +0 -66
  197. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +0 -141
  198. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +0 -41
  199. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +0 -47
  200. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +0 -42
  201. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +0 -59
  202. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +0 -440
  203. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +0 -161
  204. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +0 -325
  205. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +0 -241
  206. package/apps/storefront/src/lib/publish/shipWorkspace.ts +0 -439
  207. package/apps/storefront/src/lib/publish/sseRoute.ts +0 -119
  208. package/apps/storefront/src/lib/publish/stageBundleUpload.ts +0 -215
  209. package/apps/storefront/src/lib/publish/streamLogConsole.ts +0 -142
  210. package/apps/storefront/src/lib/publish/versionNumber.ts +0 -52
  211. package/apps/storefront/src/pages/admin/ops-timeline.astro +0 -420
  212. package/apps/storefront/src/pages/admin/review-release.astro +0 -265
  213. package/apps/storefront/src/pages/admin/select.astro +0 -96
  214. package/apps/storefront/src/pages/admin.astro +0 -570
  215. package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +0 -86
  216. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +0 -174
  217. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +0 -96
  218. package/apps/storefront/src/pages/api/apps/admin/health.ts +0 -44
  219. package/apps/storefront/src/pages/api/apps/admin/install.ts +0 -125
  220. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +0 -118
  221. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +0 -89
  222. package/apps/storefront/src/pages/api/apps/admin/list.ts +0 -26
  223. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +0 -85
  224. package/apps/storefront/src/pages/api/apps/admin/resume.ts +0 -79
  225. package/apps/storefront/src/pages/api/apps/admin/suspend.ts +0 -80
  226. package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +0 -98
  227. package/apps/storefront/src/pages/api/apps/admin/update.ts +0 -126
  228. package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +0 -70
  229. package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries.ts +0 -51
  230. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +0 -255
  231. package/apps/storefront/src/pages/api/apps/oauth/token.ts +0 -87
  232. package/apps/storefront/src/pages/api/apps/v1/attribution.ts +0 -162
  233. package/apps/storefront/src/pages/api/apps/v1/catalog/products/[handle].ts +0 -39
  234. package/apps/storefront/src/pages/api/apps/v1/catalog/products.ts +0 -49
  235. package/apps/storefront/src/pages/api/apps/v1/health.ts +0 -32
  236. package/apps/storefront/src/pages/api/apps/v1/inventory.ts +0 -59
  237. package/apps/storefront/src/pages/api/apps/v1/orders/[id].ts +0 -48
  238. package/apps/storefront/src/pages/api/apps/v1/orders.ts +0 -73
  239. package/apps/storefront/src/pages/api/apps/v1/reports.ts +0 -21
  240. package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +0 -74
  241. package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries.ts +0 -47
  242. package/apps/storefront/src/pages/api/cache-purge.ts +0 -55
  243. package/apps/storefront/src/pages/api/compliance/tamper.ts +0 -88
  244. package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +0 -164
  245. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +0 -154
  246. package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +0 -120
  247. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +0 -108
  248. package/apps/storefront/src/pages/api/publish/stage-bundle.ts +0 -70
  249. package/apps/storefront/src/pages/api/test/dev-session.ts +0 -133
  250. package/apps/storefront/src/pages/cockpit.astro +0 -1548
  251. package/apps/storefront/src/pages/dashboard/[appDomain]/apps/index.astro +0 -119
  252. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +0 -105
  253. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +0 -177
  254. package/apps/storefront/src/pages/dashboard/index.astro +0 -160
@@ -1,2983 +0,0 @@
1
- ---
2
- /**
3
- * Publish tab — the go-live surface (handoffs 2026-08-05-admin-golive-surface
4
- * + 2026-08-05-domain-connect-ux). Replaces the terminal runbook for a
5
- * permissioned operator:
6
- *
7
- * - OPEN CHANGE QUEUE from GET /api/changes ({ current, changes }) with
8
- * per-change reconcile/compliance state, preview link, head sha, and age.
9
- * - ACCEPT TO PREVIEW (POST /api/changes/integrate — the accept-to-preview
10
- * split; advances the shared `preview` aggregate, does NOT go live) and
11
- * REJECT (with a note) per change. Going live is the aggregate card's
12
- * exact-digest PUBLISH (POST /api/changes/ship), never a per-change accept.
13
- * - LIVE STATE: the current live version (live-channel pointer) + the
14
- * static-republish dispatch status for static-publish tenants (fired /
15
- * skipped / sink-unbound notice).
16
- * - DOMAIN section (static-publish tenants): apex connect + rollback via the
17
- * www-domain dispatch endpoints — typed-confirmation connect (owner-only),
18
- * always-visible rollback with the captured-records timestamp, in-flight
19
- * run + legible failures.
20
- *
21
- * PERMISSIONS (server-authoritative; mirrored here for UX only):
22
- * - Ship actions require an owner/admin session (`resolveOwnerSession`'s
23
- * OWNER_CAPABILITIES) — others see the queue read-only with a sign-in note.
24
- * - Non-premium tenants see the go-live control as a PAYWALL UPSELL — never
25
- * hidden, never "coming soon" (recorded product direction). The
26
- * entitlement signal is the existing premium model: a custom-domain
27
- * tenant is premium/pilot; a `<label>.tokenoftrust.store` self-serve store
28
- * is not. TODO(paywall workstream): replace with the real billing
29
- * entitlement when one exists.
30
- */
31
- import { readViewerSession } from "@/lib/auth/route";
32
- import { withBase } from "@/lib/basePath";
33
- import { readEnv } from "@/lib/env";
34
- import { resolveViewerShipCapability } from "@/lib/change";
35
- import { resolveLiveVersion } from "@/lib/change/views/live-version";
36
- import { isPublicSiteCurrent, staticLiveManifestUrl } from "@/lib/change/views/live-digest";
37
- import { isHostedStoreDomain, isInternalTestTenant } from "@tot/public-runtime";
38
- import { mintPreviewShareToken } from "@/lib/preview/previewShareLink";
39
-
40
- const { envelope } = Astro.props;
41
- const { tenant, basePath } = Astro.locals;
42
-
43
- // Viewer capability, mirrored from the same session model the API routes gate
44
- // on (resolveOwnerSession admits capability owner|admin). UX-only: the server
45
- // re-authorizes every action. Checked against `envelope.targetTenant`, not the
46
- // routed `tenant.appDomain` — a ToT-staff viewer operating a store other than
47
- // the one that served this admin route must be checked against the tenant
48
- // they SELECTED, else a real grant on the selected tenant reads as "none".
49
- const viewerSession = await readViewerSession(Astro);
50
- // Session capability counts only when the session's resource IS the selected tenant.
51
- const sessionCapability =
52
- viewerSession && viewerSession.resource === envelope.targetTenant
53
- ? viewerSession.capability
54
- : undefined;
55
- const viewerCapability =
56
- Astro.locals.viewer?.capability ?? sessionCapability ?? "none";
57
- // A signed-in DEVELOPER (not owner/admin) may hold an explicit, owner-revocable
58
- // ship-on-behalf grant. Resolve it LIVE from the MCP subject-capability store —
59
- // NEVER the coarse role, NEVER a client header — so a ship-granted developer sees
60
- // the queue + Go-live/Reject. This MIRRORS the server (resolveShipSession /
61
- // effectiveShipCapability); the server re-authorizes every action. UX-only.
62
- let viewerCanShip = viewerCapability === "owner" || viewerCapability === "admin";
63
- if (!viewerCanShip && Astro.locals.viewer?.email) {
64
- const shipGrant = await resolveViewerShipCapability(
65
- Astro.locals.viewer.email,
66
- envelope.targetTenant,
67
- );
68
- if (shipGrant === "ship-on-behalf") viewerCanShip = true;
69
- }
70
- const viewerIsOwner = viewerCapability === "owner";
71
- // Apex/domain cutover is granted to owner OR admin (decision: apex-cutover =
72
- // owner+admin). This MIRRORS the server, whose `apexCutover` = `isOwner` over
73
- // OWNER_CAPABILITIES {owner, admin} (see change/route.ts decideIsOwner +
74
- // change/shipCapabilities.ts) — this UI gate was wrongly owner-only, stricter
75
- // than the gate the server actually enforces. UX-only: the server re-authorizes.
76
- const viewerCanApexCutover = viewerCapability === "owner" || viewerCapability === "admin";
77
-
78
- // Entitlement: the existing premium signal (custom-domain tenant = premium).
79
- // TODO(paywall workstream): swap for the real billing entitlement.
80
- const goLiveEntitled = !isHostedStoreDomain(tenant.appDomain);
81
- const internalTestTenant = isInternalTestTenant(tenant);
82
-
83
- const staticPublishEnabled = Boolean(
84
- tenant.staticPublish && tenant.staticPublish.enabled !== false,
85
- );
86
- // Presence-only signal for the dispatch sink (never the value).
87
- const dispatchConfigured = Boolean((await readEnv("WWW_PUBLISH_DISPATCH_TOKEN"))?.trim());
88
-
89
- // "What does the public site actually serve right now?" — the read-back half of
90
- // the static-publish seam (resolveLiveVersion's staticLiveDigest), computed here
91
- // so it's ALWAYS visible rather than only living in GET /api/changes JSON. This is
92
- // the exact gap that can let tokenoftrust.com's published public site
93
- // sit two days stale behind several accepted-but-unpublished changes with nothing
94
- // in the console calling it out — going live and staying live for the PUBLIC
95
- // static target are two different facts, and this card makes the second one
96
- // honest instead of assumed. Best-effort: resolveLiveVersion never throws.
97
- // BUDGETED: resolveLiveVersion chains several TENANT_CACHE reads (resolvePreviewTenant,
98
- // resolveChannelVersion, readSiteVersion) that are pathologically slow + variable on the
99
- // preview worker (measured 2-10s EACH, spiking together — a KV/storage-latency issue, not
100
- // this code). It runs in blocking SSR frontmatter, so it gated the whole admin document
101
- // stream. It's best-effort display chrome ("what does live/ serve right now"), and the
102
- // client-side GET /api/changes already returns `current`, so cap it: on timeout the card
103
- // renders from the client fetch instead of blocking first paint.
104
- const liveVersion = staticPublishEnabled
105
- ? await Promise.race([
106
- resolveLiveVersion(tenant.appDomain),
107
- new Promise<null>((resolve) => setTimeout(() => resolve(null), 1500)),
108
- ])
109
- : null;
110
- const publicSiteDigest = liveVersion?.staticLiveDigest ?? null;
111
- // The actual verdict: does the public target's manifest sha match what was last
112
- // promoted to "live" here? Both are real tenant Gitea shas — a direct,
113
- // honest comparison, not a heuristic. See liveDigest.ts's isPublicSiteCurrent.
114
- const publicSiteVerdict = publicSiteDigest
115
- ? isPublicSiteCurrent(liveVersion?.versionId ?? null, publicSiteDigest)
116
- : null;
117
-
118
- const changesEndpoint = withBase(basePath || "", "/api/changes");
119
- // The branch-integration lifecycle SPLIT of accept from publish:
120
- // - integrateEndpoint = "accept = integrate into the protected preview
121
- // aggregate" (POST /api/changes/integrate → TenantIntegrationQueue.enqueue).
122
- // This is the candidate-queue "Accept to preview" action — it advances the
123
- // shared aggregate, it does NOT go live. Returns an IntegrateOutcome
124
- // (queueState/runState/statusMessage), never a bare merged:true.
125
- // - shipEndpoint = the exact-digest go-live (POST /api/changes/ship →
126
- // AggregateShipOrchestrator.plan/ship). The aggregate card's "Publish"
127
- // action: it ships the reviewed pinned SHA + digest, double-gated
128
- // (paywall + human confirm). The read-only plan is served on GET /api/changes
129
- // (`plan`), so the confirm surface never has to POST to preview it.
130
- const integrateEndpoint = withBase(basePath || "", "/api/changes/integrate");
131
- const shipEndpoint = withBase(basePath || "", "/api/changes/ship");
132
- // Build-on-demand for a synthetic not-built PR row: materialize the PR's
133
- // hosted preview (INERT — flips no live channel). Gated server-side by buildGate
134
- // (the ship-on-behalf floor); the endpoint resolves the head sha via forge PR-read
135
- // when the row supplies only the PR number.
136
- const buildEndpoint = withBase(basePath || "", "/api/preview/build");
137
- // The ship-operations workspace woven into this tab: versions/promotion
138
- // history, per-candidate diff, and rollback (through the ONE rollback
139
- // orchestrator). Read endpoints mirror the change-queue's auth/scope.
140
- const versionsEndpoint = withBase(basePath || "", "/api/changes/versions");
141
- const rollbackEndpoint = withBase(basePath || "", "/api/changes/rollback");
142
- // Branch retention: GET dry-run classification / POST guarded delete.
143
- const branchesEndpoint = withBase(basePath || "", "/api/changes/branches");
144
- // The client builds `${diffEndpointBase}/<changeId>/diff` per candidate.
145
- const diffEndpointBase = changesEndpoint;
146
- // The live store's root on THIS surface (basePath-aware): where the published
147
- // digest actually serves. Rendered into "already live" copy + the publish
148
- // ceremony's "View your live store" CTA.
149
- const liveHref = withBase(basePath || "", "/");
150
-
151
- // LIVE-SITE ENTRY POINTS — surfaced explicitly so "where does my store actually
152
- // serve?" is never a mystery (it was). Two public URLs, always shown + clickable:
153
- // 1. Hosted store — THIS worker at the request host + tenant base path
154
- // (e.g. storefront.tokenoftrust.store/tokenoftrust.com/).
155
- // 2. Public site — the static target's canonical URL, derived from the SAME
156
- // manifest URL the freshness read-back probes (WWW_PUBLISH_VERIFY_URL or
157
- // https://<canonicalDomain>/manifest.json), minus the /manifest.json tail
158
- // (e.g. published.tokenoftrust.com). Only for static-publish tenants; a
159
- // hosted-only tenant has no separate public site. Computed PURELY (no probe)
160
- // so the URL shows even when the freshness probe above timed out.
161
- const requestHost = Astro.request.headers.get("host") ?? Astro.url.host;
162
- const liveStoreUrl = `${Astro.url.protocol}//${requestHost}${liveHref}`;
163
- const publicManifestUrl = staticPublishEnabled
164
- ? staticLiveManifestUrl({
165
- canonicalDomain: tenant.canonicalDomain,
166
- // Per-tenant override first (never bleeds across stores), then the global env
167
- // escape hatch, then the canonical-apex default — mirrors resolveLiveVersion.
168
- explicitUrl: tenant.staticPublish?.verifyUrl ?? (await readEnv("WWW_PUBLISH_VERIFY_URL")) ?? null,
169
- })
170
- : null;
171
- const publicSiteUrl = publicManifestUrl
172
- ? publicManifestUrl.replace(/\/manifest\.json$/, "/")
173
- : null;
174
- const stripScheme = (u: string): string => u.replace(/^https?:\/\//, "");
175
-
176
- // r08 — a signed, tenant-scoped, expiring `?__preview` capability link an owner can
177
- // copy and hand to a non-technical stakeholder (view-only, no sign-in required).
178
- // Minted server-side so the signing secret never reaches the browser (mirrors
179
- // preview/dashboard.astro). Signs the tenant_id claim: the shared-preview
180
- // middleware verifies against `tenant.tenant_id` and the candidate renderer accepts
181
- // tenant_id too, so ONE token authorizes BOTH the shared next-release preview AND
182
- // per-candidate `preview/pr/<N>` links. Only minted for a ship-capable viewer, and
183
- // absent when signing isn't configured — the copy buttons then stay hidden rather
184
- // than offering a dead link. TTL 24h so a link copied late in a long session still
185
- // works; a reload re-mints a fresh one.
186
- const previewShareToken = viewerCanShip ? await mintPreviewShareToken(tenant.tenant_id) : null;
187
-
188
- const domainStatusEndpoint = withBase(basePath || "", "/api/domain/status");
189
- const domainDispatchEndpoint = withBase(basePath || "", "/api/domain/dispatch");
190
- // The machine-verifiable apex-cutover readiness gate — the SAME server gate the
191
- // `tot go-live` CLI reads, so the admin display and the CLI never diverge.
192
- const domainReadinessEndpoint = withBase(basePath || "", "/api/domain/readiness");
193
- ---
194
-
195
- <!-- agent-hook:publish-tab -->
196
- <section
197
- id="publish"
198
- class="band publish-tab"
199
- aria-labelledby="admin-tab-publish publish-title"
200
- data-admin-panel
201
- data-agent-scope="publish"
202
- role="tabpanel"
203
- data-changes-endpoint={changesEndpoint}
204
- data-integrate-endpoint={integrateEndpoint}
205
- data-ship-endpoint={shipEndpoint}
206
- data-build-endpoint={buildEndpoint}
207
- data-versions-endpoint={versionsEndpoint}
208
- data-rollback-endpoint={rollbackEndpoint}
209
- data-branches-endpoint={branchesEndpoint}
210
- data-diff-endpoint-base={diffEndpointBase}
211
- data-domain-status-endpoint={domainStatusEndpoint}
212
- data-domain-dispatch-endpoint={domainDispatchEndpoint}
213
- data-domain-readiness-endpoint={domainReadinessEndpoint}
214
- data-repo={tenant.appDomain}
215
- data-app-domain={tenant.appDomain}
216
- data-canonical-domain={tenant.canonicalDomain ?? ""}
217
- data-preview-share-token={previewShareToken ?? ""}
218
- data-live-href={liveHref}
219
- data-can-ship={viewerCanShip ? "true" : "false"}
220
- data-is-owner={viewerIsOwner ? "true" : "false"}
221
- data-entitled={goLiveEntitled ? "true" : "false"}
222
- data-static-publish={staticPublishEnabled ? "true" : "false"}
223
- data-dispatch-configured={dispatchConfigured ? "true" : "false"}
224
- data-public-site-host={publicSiteUrl ? stripScheme(publicSiteUrl).replace(/\/$/, "") : ""}
225
- >
226
- <div class="section-title">
227
- <div>
228
- <h2 id="publish-title">Publish</h2>
229
- <p>Review what's changed, then publish your next release when you're ready.</p>
230
- </div>
231
- <div class="actions">
232
- <button class="btn" type="button" data-publish-refresh>
233
- <svg><use href="#i-refresh"></use></svg>Refresh
234
- </button>
235
- </div>
236
- </div>
237
-
238
- {internalTestTenant && (
239
- <div class="ship-gate amber">
240
- <div class="ship-gate-main">
241
- <span class="seal"><svg><use href="#i-alert"></use></svg></span>
242
- <div>
243
- <b>Internal test store</b>
244
- <span>This store exists for testing only and is blocked from going live.</span>
245
- </div>
246
- </div>
247
- </div>
248
- )}
249
-
250
- {!goLiveEntitled && (
251
- <div class="ship-gate amber" data-publish-paywall>
252
- <div class="ship-gate-main">
253
- <span class="seal"><svg><use href="#i-card"></use></svg></span>
254
- <div>
255
- <b>Publishing to your live store is part of the paid plan</b>
256
- <span>
257
- Previewing and reviewing changes stays free. Upgrade to take changes live
258
- and publish your public site.
259
- </span>
260
- </div>
261
- </div>
262
- <a class="btn primary" href="mailto:success@tokenoftrust.com?subject=Upgrade%20store%20publishing">
263
- Upgrade to publish
264
- </a>
265
- </div>
266
- )}
267
-
268
- {goLiveEntitled && !viewerCanShip && (
269
- <div class="ship-gate amber">
270
- <div class="ship-gate-main">
271
- <span class="seal"><svg><use href="#i-shield"></use></svg></span>
272
- <div>
273
- <b>Read-only view</b>
274
- <span>Going live requires an owner or admin sign-in for this store.</span>
275
- </div>
276
- </div>
277
- </div>
278
- )}
279
-
280
- <!-- State-driven "next action" hero (r01) — the SINGLE top-of-tab state
281
- element. ONE sentence + ONE primary CTA, derived by lib/publish/publishHero.ts
282
- from the SAME /api/changes read the queue + aggregate already fetch (no extra
283
- read). It ABSORBS the two former standalone banners: the merge-doctor health
284
- summary (B1) and the platform-fault "Report to support" escalation (B3) are
285
- folded into the hero's state model, expressed through this one element — a
286
- merge conflict/platform blocker turns the hero red/urgent instead of rendering
287
- a separate banner. Rendered by renderHero() in adminPublishTab.ts; starts
288
- hidden until the first /api/changes read resolves. The r03 stale-public-site
289
- alarm sits alongside it in this region (a distinct top-level signal that
290
- deep-links into Live Site), so there is ONE hero/alert region, not floating
291
- banners. Urgency is never color-only: the tone drives the icon + an explicit
292
- "Action needed" label. -->
293
- <div class="publish-hero-region" data-publish-hero-region>
294
- <div class="publish-hero" data-publish-hero data-tone="calm" hidden role="status" aria-live="polite">
295
- <div class="publish-hero-main">
296
- <span class="publish-hero-icon" data-publish-hero-icon aria-hidden="true">
297
- <svg><use href="#i-check"></use></svg>
298
- </span>
299
- <div class="publish-hero-copy">
300
- <span class="publish-hero-kicker" data-publish-hero-kicker hidden>Action needed</span>
301
- <b class="publish-hero-headline" data-publish-hero-headline></b>
302
- <span class="publish-hero-detail" data-publish-hero-detail></span>
303
- </div>
304
- </div>
305
- <div class="publish-hero-actions">
306
- <button class="btn primary publish-hero-cta" type="button" data-publish-hero-cta hidden></button>
307
- <a class="btn primary publish-hero-cta" data-publish-hero-cta-link hidden href="mailto:success@tokenoftrust.com"></a>
308
- </div>
309
- </div>
310
-
311
- <!-- r03: stale-public-site alarm, LIFTED to top-level. The "public site is
312
- behind the live pointer" verdict (resolveLiveVersion / isPublicSiteCurrent)
313
- used to sit buried inside the collapsible Live Site section; when the public
314
- target is behind, surface it here as a top-level amber alarm that deep-links
315
- into Live Site (where the full breakdown stays nested). SSR-driven from the
316
- same publicSiteVerdict the Live Site chip reads — shown only for a
317
- static-publish tenant whose public target is confirmed behind. -->
318
- {staticPublishEnabled && publicSiteVerdict && publicSiteVerdict.status === "behind" && (
319
- <div class="risk-alert amber publish-stale-site" data-publish-stale-site role="status" aria-live="polite">
320
- <div class="risk-alert-main">
321
- <svg><use href="#i-alert"></use></svg>
322
- <div>
323
- <b>Public site is behind — re-publish to catch it up</b>
324
- <span>
325
- Your public site is still serving an older release
326
- (<code class="publish-mono">{publicSiteVerdict.publishedSha.slice(0, 12)}</code>)
327
- while your live pointer has moved on to
328
- (<code class="publish-mono">{publicSiteVerdict.livePointerSha.slice(0, 12)}</code>).
329
- Re-publish, or open the publishing details for the full breakdown.
330
- </span>
331
- </div>
332
- </div>
333
- <a class="btn" href="#publish-live-site" data-publish-stale-site-link>Open publishing details</a>
334
- </div>
335
- )}
336
- </div>
337
-
338
- <div class="grid cols-3">
339
- <div class="metric">
340
- <span>Live version</span>
341
- <strong>
342
- <!-- The human version label ("v42") links straight to the live store; the
343
- raw SHA is demoted to the Technical details disclosure below. -->
344
- <a class="live-version-link" data-publish-live-version-link href={liveHref}
345
- target="_blank" rel="noopener" hidden>—</a>
346
- <span data-publish-live-version-none>—</span>
347
- </strong>
348
- <small data-publish-live-source>What shoppers see right now</small>
349
- <details class="metric-tech" data-publish-live-tech hidden>
350
- <summary>Technical details</summary>
351
- <span class="publish-mono subtle" data-publish-live-sha>—</span>
352
- </details>
353
- </div>
354
- <div class="metric">
355
- <span>Published</span>
356
- <strong data-publish-live-at>—</strong>
357
- <small>When the live version last changed</small>
358
- <small data-publish-live-shipped-by></small>
359
- </div>
360
- <div class="metric">
361
- <span>Open changes</span>
362
- <strong data-publish-queue-count>—</strong>
363
- <small>Waiting for review or go-live</small>
364
- </div>
365
- </div>
366
-
367
- {staticPublishEnabled && !dispatchConfigured && (
368
- <div class="risk-alert" data-publish-sink-unbound>
369
- <div class="risk-alert-main">
370
- <svg><use href="#i-alert"></use></svg>
371
- <div>
372
- <b>Automatic public-site republish is not yet enabled on this environment</b>
373
- <span>
374
- Going live updates the hosted store immediately; the public-site republish
375
- job is dispatched once publishing is enabled here.
376
- </span>
377
- </div>
378
- </div>
379
- </div>
380
- )}
381
-
382
- <div class="panel" id="publish-queue">
383
- <details class="publish-queue-details" open>
384
- <summary class="panel-head">
385
- <h3><svg><use href="#i-export"></use></svg>What needs your approval?</h3>
386
- <span class="chip" data-publish-queue-chip>Loading</span>
387
- </summary>
388
- <div class="panel-body grid">
389
- <p class="note publish-status" data-publish-status role="status" aria-live="polite"></p>
390
- <!-- rux1: forge-listing degraded strip. Shown ONLY when GET /api/changes
391
- reports `degraded` (the open-PR listing failed) — distinct from the
392
- genuine empty queue, so a forge blip never reads as "No open changes". -->
393
- <div class="risk-alert" data-publish-degraded hidden role="alert">
394
- <div class="risk-alert-main">
395
- <svg><use href="#i-alert"></use></svg>
396
- <div>
397
- <b>Some open changes may not be shown</b>
398
- <span>
399
- We couldn't reach the pull-request list, so open changes that haven't been
400
- built yet may be missing from the queue below. Built candidates are
401
- unaffected. Refresh to try again.
402
- </span>
403
- </div>
404
- </div>
405
- </div>
406
- <p class="note publish-pr-callout" data-publish-pr-callout hidden role="status" aria-live="polite"></p>
407
- <div class="table-wrap">
408
- <table class="publish-table">
409
- <thead>
410
- <tr>
411
- <th>Change</th>
412
- <th>Status</th>
413
- <th>Checks</th>
414
- <th>Head</th>
415
- <th>Updated</th>
416
- <th class="action">Actions</th>
417
- </tr>
418
- </thead>
419
- <tbody data-publish-queue-body>
420
- <tr data-publish-placeholder>
421
- <td colspan="6"><span class="subtle">Loading the change queue…</span></td>
422
- </tr>
423
- </tbody>
424
- </table>
425
- </div>
426
- {staticPublishEnabled && (
427
- <p class="note" data-publish-dispatch-status>
428
- {dispatchConfigured
429
- ? "Public-site republish: dispatched automatically when a change goes live."
430
- : "Public-site republish: not yet enabled on this environment."}
431
- </p>
432
- )}
433
- </div>
434
- </details>
435
- </div>
436
-
437
- <!-- "See all" history: changes that LEFT the queue by being rejected. Rejecting
438
- is otherwise a black hole — this collapsed panel keeps them visible (and their
439
- preview reachable) so a reject is recoverable-from-history. Collapsed by
440
- default (a reference, not a work surface); the whole panel is hidden by the
441
- client when there are none, so it never adds empty chrome. Excludes shipped
442
- (merged) changes — those live in the Live Site / versions panel. -->
443
- <div class="panel" id="publish-closed" data-publish-closed-panel hidden>
444
- <details class="closed-changes-details">
445
- <summary class="panel-head">
446
- <h3><svg><use href="#i-undo"></use></svg>Closed &amp; rejected changes</h3>
447
- <span class="chip" data-publish-closed-chip>0</span>
448
- </summary>
449
- <div class="panel-body grid">
450
- <p class="note subtle">
451
- Changes you rejected. They're not in your next release and won't go live —
452
- kept here so nothing you closed silently disappears. Open a preview to review
453
- what it was.
454
- </p>
455
- <div class="table-wrap">
456
- <table class="publish-table">
457
- <thead>
458
- <tr>
459
- <th>Change</th>
460
- <th>Closed</th>
461
- <th>Head</th>
462
- </tr>
463
- </thead>
464
- <tbody data-publish-closed-body></tbody>
465
- </table>
466
- </div>
467
- </div>
468
- </details>
469
- </div>
470
-
471
- <!-- b11: the AGGREGATE card — the protected `preview` aggregate the accepted
472
- candidates integrate into, its combined evidence (the last integration
473
- run's honest state), and the exact-digest Publish (b09/b10 ship the
474
- reviewed pinned SHA + digest, never a rebuild). -->
475
- <div class="panel agg-console" id="publish-next-release" data-publish-aggregate>
476
- <details class="agg-console-details" open>
477
- <summary class="panel-head">
478
- <h3><svg><use href="#i-box"></use></svg>Next release</h3>
479
- <!-- r11 — the human release name (aggregate.name), shown beside the heading.
480
- Client-populated by renderAggregate; hidden (falls back to just "Next
481
- release") when the release has no name. -->
482
- <span class="agg-release-name" data-aggregate-name hidden></span>
483
- <!-- One-click hop to the current preview build. Shown only when a
484
- genuinely-built revision exists (renderAggregate toggles it via
485
- classifyBuildLink), so it can never lead to a 404. The click is
486
- stop-propagated in adminPublishTab.ts so opening the preview never
487
- also toggles this collapsible. -->
488
- <a class="btn btn-compact" data-aggregate-open-preview target="_blank" rel="noopener" hidden>Open preview</a>
489
- <!-- r08 — copy a shareable, view-only preview link for the next release to hand
490
- to a non-technical stakeholder (no sign-in). Unhidden by the client only
491
- when a share token was minted; the click is stop-propagated so copying
492
- never also toggles this collapsible. -->
493
- <button class="btn btn-compact" type="button" data-aggregate-copy-preview hidden>
494
- <svg><use href="#i-copy"></use></svg><span data-copy-preview-label>Copy preview link</span>
495
- </button>
496
- <span class="chip" data-aggregate-chip>Loading</span>
497
- </summary>
498
- <div class="panel-body grid">
499
- <p class="note publish-status" data-aggregate-status role="status" aria-live="polite"></p>
500
-
501
- <!-- Keep the primary action beside the release status. The detail table can
502
- be long; publishing must never be buried beneath it. -->
503
- <div class="check-row agg-launch">
504
- <div>
505
- <b>
506
- {staticPublishEnabled
507
- ? "Publish this release to the hosted and public sites"
508
- : "Publish this release to your live store"}
509
- </b>
510
- <p data-aggregate-publish-detail>
511
- {publicSiteUrl ? (
512
- <>
513
- Publishes the exact build you reviewed to the hosted store, then
514
- automatically republishes <code>{stripScheme(publicSiteUrl).replace(/\/$/, "")}</code>.
515
- </>
516
- ) : (
517
- <>Publishes the exact build you reviewed — no rebuild.</>
518
- )}
519
- </p>
520
- </div>
521
- <button class="btn primary agg-publish-btn" type="button" data-aggregate-publish disabled>
522
- <svg><use href="#i-export"></use></svg>
523
- {staticPublishEnabled ? "Publish to both live sites" : "Publish live"}
524
- </button>
525
- </div>
526
-
527
- <!-- Only visible after an actually failed aggregate rebuild. -->
528
- <div class="check-row" data-aggregate-retry-row hidden>
529
- <div>
530
- <b>Retry the build</b>
531
- <p>
532
- Your change was added, but rebuilding the preview failed. Retry rebuilds it
533
- and re-runs your store's checks — nothing new is added.
534
- </p>
535
- </div>
536
- <button class="btn" type="button" data-aggregate-retry-build>
537
- <svg><use href="#i-refresh"></use></svg>Retry build
538
- </button>
539
- </div>
540
-
541
- <!-- AI-assembled release summary — a plain-language "what am I about to
542
- publish?" ahead of the technical trust chain. Rendered ONLY when the
543
- server supplies data.releaseSummary; otherwise it stays hidden and the
544
- panel degrades to today's behavior (graceful fallback). -->
545
- <div class="agg-summary" data-aggregate-summary hidden>
546
- <div class="agg-summary-head">
547
- <span class="agg-summary-glyph" aria-hidden="true">
548
- <svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l1.9 4.7L18.5 8.6l-4.6 1.9L12 15.2l-1.9-4.7L5.5 8.6l4.6-1.9L12 2z"></path><path d="M18.5 14l.9 2.2 2.3.9-2.3.9-.9 2.2-.9-2.2-2.3-.9 2.3-.9.9-2.2z"></path></svg>
549
- </span>
550
- <span class="agg-summary-label">What&rsquo;s in this release</span>
551
- <span class="agg-ai-chip">
552
- <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2l1.9 4.7L18.5 8.6l-4.6 1.9L12 15.2l-1.9-4.7L5.5 8.6l4.6-1.9L12 2z"></path></svg>
553
- AI summary
554
- </span>
555
- </div>
556
- <p class="agg-summary-lead" data-aggregate-summary-lead></p>
557
- <ul class="agg-summary-points" data-aggregate-summary-points></ul>
558
- </div>
559
-
560
- <!-- Chain of custody: the build that was verified → its combined evidence →
561
- the digest that ships. Collapsed by default — this is verification
562
- detail for a developer, not something a merchant needs to read to
563
- decide whether to publish; the plain-language summary above and the
564
- Publish button below are the actual decision surface. -->
565
- <details class="agg-chain-details">
566
- <summary>Technical details</summary>
567
- <div class="agg-chain">
568
- <div class="agg-stop">
569
- <span class="agg-stop-label">Aggregate build</span>
570
- <span class="agg-stop-val"><strong class="publish-mono" data-aggregate-sha>—</strong></span>
571
- <p class="agg-stop-cap" data-aggregate-run-link>The current green preview build for this store.</p>
572
- </div>
573
- <div class="agg-link" aria-hidden="true">
574
- <span class="agg-flowlabel">verified by</span>
575
- <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>
576
- </div>
577
- <div class="agg-stop">
578
- <span class="agg-stop-label">Combined evidence</span>
579
- <span class="agg-stop-val"><strong data-aggregate-evidence>—</strong></span>
580
- <p class="agg-stop-cap" data-aggregate-evidence-detail>The last integration run's verdict.</p>
581
- </div>
582
- <div class="agg-link" aria-hidden="true">
583
- <span class="agg-flowlabel">produces</span>
584
- <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>
585
- </div>
586
- <div class="agg-stop">
587
- <span class="agg-stop-label">Reviewed digest</span>
588
- <span class="agg-stop-val"><strong class="publish-mono agg-digest" data-aggregate-digest>—</strong></span>
589
- <p class="agg-stop-cap">The content digest that will be published — exactly what you reviewed.</p>
590
- </div>
591
- </div>
592
- </details>
593
-
594
- <!-- Included PRs — the batch this aggregate composes (b01 includedPrs). u14:
595
- the "missing middle" — this is the STAGED delta vs live. The title + note
596
- narrate how far ahead of the live site the staged preview is, and each row
597
- carries an Undo (per-change revert) to roll the staged preview backward. -->
598
- <div class="aggregate-prs agg-staged" data-aggregate-prs-wrap hidden>
599
- <div class="agg-staged-head">
600
- <b data-aggregate-prs-title>Included changes</b>
601
- <!-- Scope-of-change risk measure across the whole staged batch (total
602
- files + lines). Populated by renderAggregate from data.stagedScope;
603
- hidden when no staged change carries size stats. -->
604
- <span class="agg-scope" data-aggregate-scope hidden></span>
605
- </div>
606
- <!-- Staged → live delta strip: staged aggregate sha ahead of the live
607
- pointer. Populated by renderAggregate; hidden when nothing is ahead. -->
608
- <div class="agg-delta-strip" data-aggregate-delta-strip hidden>
609
- <span class="agg-delta-node">
610
- <span class="agg-stop-label">Staged preview</span>
611
- <span class="sha-token" data-agg-delta-staged>—</span>
612
- </span>
613
- <span class="agg-delta-mid" aria-hidden="true">
614
- <span class="agg-delta-count" data-agg-delta-count></span>
615
- <svg viewBox="0 0 26 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M1 5h21m0 0-4-4m4 4-4 4"></path></svg>
616
- </span>
617
- <span class="agg-delta-node">
618
- <span class="agg-stop-label">Live site</span>
619
- <span class="sha-token ghost" data-agg-delta-live>—</span>
620
- </span>
621
- </div>
622
- <p class="note" data-aggregate-delta-note hidden></p>
623
- <!-- ONE table, one row per change — merges what used to be two separate
624
- "Included changes" lists (this staged-delta section + the Publish
625
- plan's own duplicate) into a single source of truth. Populated by
626
- renderAggregate, enriched with the plan's per-change title/description/
627
- file+line counts when available. -->
628
- <div class="table-wrap">
629
- <table class="publish-table agg-included-table">
630
- <thead>
631
- <tr>
632
- <th>Change</th>
633
- <th>Head</th>
634
- <th>Files</th>
635
- <th>Lines</th>
636
- <th>Added</th>
637
- <th class="action">Actions</th>
638
- </tr>
639
- </thead>
640
- <tbody data-aggregate-prs></tbody>
641
- </table>
642
- </div>
643
- </div>
644
-
645
- <!-- The exact-digest Publish plan (b09 plan()): pinned SHA + digest +
646
- rollback target + paywall verdict + run link. Shown inline (from
647
- GET /api/changes `plan`) so the confirm never POSTs to preview.
648
- Publish is refused until the plan is `ok`. Its own included-changes
649
- list was merged into the single table above — see data-aggregate-prs. -->
650
- <div class="aggregate-plan agg-manifest" data-aggregate-plan hidden>
651
- <div class="aggregate-plan-head agg-manifest-head">
652
- <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>
653
- <span class="chip" data-aggregate-plan-chip></span>
654
- </div>
655
- <details class="agg-chain-details">
656
- <summary>Technical details</summary>
657
- <dl class="aggregate-plan-grid" data-aggregate-plan-grid></dl>
658
- </details>
659
-
660
- <p class="note" data-aggregate-plan-note></p>
661
- </div>
662
-
663
- </div>
664
- </details>
665
- </div>
666
-
667
- <!-- Branch retention panel removed entirely — old candidate branches now
668
- clean themselves up automatically (auto-swept after each successful
669
- accept/publish, see adminPublishTab.ts autoSweepBranches). There is
670
- nothing here for anyone to look at or act on day-to-day, so there is no
671
- panel at all, not even a collapsed one. -->
672
-
673
- <!-- Publish next release — the collapsed destination/status panel below the
674
- staged release. Its summary answers the operational question first: either
675
- live already matches the staged release, or there is an action to advance it.
676
- The disclosure contains destination links, freshness, rollback, and domain
677
- controls for operators who need the detail. -->
678
- <div class="panel" id="publish-live-site" data-publish-live-site>
679
- <details class="live-site-details">
680
- <summary class="panel-head">
681
- <h3><svg><use href="#i-export"></use></svg>Publish next release</h3>
682
- <span class="chip" data-live-release-state>Checking release…</span>
683
- <button
684
- class="btn primary btn-compact live-release-publish"
685
- type="button"
686
- data-live-release-publish
687
- hidden
688
- >Publish next release</button>
689
- </summary>
690
- <div class="panel-body grid">
691
- <ul class="live-site-links">
692
- <li>
693
- <span class="live-site-link-label">Hosted store</span>
694
- <a class="live-site-link-url publish-mono" href={liveHref} target="_blank" rel="noopener">{stripScheme(liveStoreUrl)}</a>
695
- <span class="live-site-link-note">Updates immediately when you publish the reviewed release above.</span>
696
- </li>
697
- {publicSiteUrl && (
698
- <li>
699
- <span class="live-site-link-label">Public site</span>
700
- <a class="live-site-link-url publish-mono" href={publicSiteUrl} target="_blank" rel="noopener">{stripScheme(publicSiteUrl)}</a>
701
- <span class="live-site-link-note">Republishes automatically after the hosted release is published.</span>
702
- </li>
703
- )}
704
- </ul>
705
-
706
- <!-- Public-site freshness read-back (static-publish seam's staticLiveDigest):
707
- what the external target (CloudFront/S3) is HONESTLY observed serving
708
- right now, vs the control-plane's live pointer — the two can diverge on a
709
- stale/failed republish. Answers "did going live actually reach the public
710
- site?" without a manual curl. -->
711
- {staticPublishEnabled && publicSiteDigest && publicSiteVerdict && (
712
- <div
713
- class={`risk-alert ${publicSiteVerdict.status === "current" ? "public-site-ok" : "amber"}`}
714
- data-publish-public-site
715
- >
716
- <div class="risk-alert-main">
717
- <svg><use href={publicSiteVerdict.status === "current" ? "#i-check" : "#i-alert"}></use></svg>
718
- <div>
719
- {publicSiteVerdict.status === "current" ? (
720
- <>
721
- <b>Public site matches the currently published release</b>
722
- <span>
723
- <code>{stripScheme(publicSiteUrl ?? tenant.appDomain).replace(/\/$/, "")}</code> is serving
724
- (<code class="publish-mono">{liveVersion?.versionId?.slice(0, 12)}</code>)
725
- — the same release as the hosted store. Staged preview changes are
726
- a separate next release: use <b>Publish to both live sites</b> above
727
- to advance the hosted store and republish this public site.
728
- </span>
729
- </>
730
- ) : publicSiteVerdict.status === "behind" ? (
731
- <>
732
- <b>Public site is behind the hosted store's live pointer</b>
733
- <span>
734
- <code>{tenant.appDomain}</code>'s public target is still serving{" "}
735
- <code class="publish-mono">{publicSiteVerdict.publishedSha.slice(0, 12)}</code>, but
736
- the hosted store's live pointer has since moved to{" "}
737
- <code class="publish-mono">{publicSiteVerdict.livePointerSha.slice(0, 12)}</code>.
738
- The republish job hasn't caught up yet (or a go-live happened before it was enabled) —
739
- check the workflow run, or trigger a fresh go-live to re-dispatch it.
740
- </span>
741
- </>
742
- ) : (
743
- <>
744
- <b>Couldn't verify the public site is current</b>
745
- <span>
746
- Checked <code class="publish-mono">{publicSiteDigest.url}</code>: {publicSiteVerdict.reason}.
747
- This does NOT mean the hosted store's own "Live version" above is wrong, only that the
748
- separate public static target couldn't be confirmed current.
749
- </span>
750
- </>
751
- )}
752
- </div>
753
- </div>
754
- </div>
755
- )}
756
-
757
- <!-- Rollback options — CONTAINED within Live Site as a subsection (was a sibling
758
- panel). The full published-version history + one-click rollback through the
759
- single rollback orchestrator; collapsed by default (most people never open
760
- it — the live version is already the metric up top). Keeps its
761
- data-publish-versions + data-versions-* hooks verbatim. -->
762
- <div class="live-site-subsection" data-publish-versions>
763
- <details class="versions-details">
764
- <summary class="live-site-subhead">
765
- <h4><svg><use href="#i-refresh"></use></svg>Rollback options</h4>
766
- <span class="chip" data-versions-chip>Loading</span>
767
- </summary>
768
- <div class="grid">
769
- <p class="note publish-status" data-versions-status role="status" aria-live="polite"></p>
770
- <p class="note" data-versions-rollback-note hidden></p>
771
- <!-- u14: orients the owner between the staged aggregate above and live versions
772
- below — "N staged change(s) not yet published; Publish live creates the next
773
- version here." Owned by renderAggregate (its own element, so its timing never
774
- races renderVersions). -->
775
- <p class="note" data-versions-orientation hidden></p>
776
- <div class="table-wrap">
777
- <table class="publish-table versions-table">
778
- <thead>
779
- <tr>
780
- <th>Version</th>
781
- <th>Digest</th>
782
- <th>Promoted</th>
783
- <th>Preview</th>
784
- <th>State</th>
785
- <th class="action">Actions</th>
786
- </tr>
787
- </thead>
788
- <tbody data-versions-body>
789
- <tr data-versions-placeholder>
790
- <td colspan="6"><span class="subtle">Loading published versions…</span></td>
791
- </tr>
792
- </tbody>
793
- </table>
794
- </div>
795
- </div>
796
- </details>
797
- </div>
798
-
799
- <!-- Public domain (apex connect / rollback) — CONTAINED within Live Site as a
800
- subsection. Keeps its data-publish-domain + data-domain-* hooks verbatim so
801
- the client controller is unchanged; only the wrapper/heading are lighter to
802
- read as a nested section rather than a sibling panel. -->
803
- {staticPublishEnabled && (
804
- <div class="live-site-subsection" data-publish-domain>
805
- <div class="live-site-subhead">
806
- <h4><svg><use href="#i-shield"></use></svg>Public domain</h4>
807
- <span class="chip" data-domain-state-chip>Loading</span>
808
- </div>
809
- <div class="grid">
810
- <div class="grid cols-3">
811
- <div class="field">
812
- <label>Serving from</label>
813
- <strong data-domain-state>—</strong>
814
- <p data-domain-since>Current host for {tenant.appDomain}</p>
815
- </div>
816
- <div class="field">
817
- <label>Rollback records</label>
818
- <strong data-domain-capture>None captured</strong>
819
- <p>DNS records captured before connect — rollback restores these.</p>
820
- </div>
821
- <div class="field">
822
- <label>Last run</label>
823
- <strong data-domain-run>None</strong>
824
- <p data-domain-run-detail>Connect and rollback runs appear here.</p>
825
- </div>
826
- </div>
827
-
828
- <p class="note publish-status" data-domain-status role="status" aria-live="polite"></p>
829
-
830
- <!-- u9: apex-cutover readiness — the itemized preconditions the server gate
831
- requires before a connect is permitted. Populated from
832
- /api/domain/readiness (the SAME gate `tot go-live` reads). -->
833
- <div class="domain-readiness" data-domain-readiness hidden>
834
- <div class="domain-readiness-head">
835
- <b>Cutover readiness</b>
836
- <span class="chip" data-domain-readiness-chip>Checking…</span>
837
- </div>
838
- <ul class="domain-readiness-list" data-domain-readiness-list></ul>
839
- <p class="note" data-domain-readiness-note></p>
840
- </div>
841
-
842
- <div class="check-row">
843
- <div>
844
- <b>Connect {tenant.appDomain} to the storefront</b>
845
- <p>
846
- Updates two DNS names — <code>{tenant.appDomain}</code> and <code>www.{tenant.appDomain}</code> —
847
- to serve the published site. Takes effect in about a minute. Type the
848
- domain to confirm.
849
- </p>
850
- {!viewerCanApexCutover && (
851
- <p><span class="chip amber">Owner/admin only</span> Connecting the domain requires an owner or admin sign-in.</p>
852
- )}
853
- </div>
854
- <div class="domain-connect-controls">
855
- <input
856
- type="text"
857
- placeholder={tenant.appDomain}
858
- autocomplete="off"
859
- spellcheck="false"
860
- aria-label="Type the domain to confirm"
861
- data-domain-confirm
862
- disabled={!viewerCanApexCutover}
863
- />
864
- <label class="domain-rehearsal">
865
- <input type="checkbox" data-domain-rehearsal disabled={!viewerCanApexCutover} />
866
- Rehearsal (dry run, no DNS change)
867
- </label>
868
- <button
869
- class="btn primary"
870
- type="button"
871
- data-domain-connect
872
- data-owner-locked={!viewerCanApexCutover ? "true" : undefined}
873
- disabled={!viewerCanApexCutover}
874
- >
875
- <svg><use href="#i-check"></use></svg>Connect domain
876
- </button>
877
- </div>
878
- </div>
879
-
880
- <div class="check-row">
881
- <div>
882
- <b>Roll back to the previous host</b>
883
- <p data-domain-rollback-detail>
884
- Restores the DNS records captured before connect. Available once records
885
- have been captured.
886
- </p>
887
- </div>
888
- <button class="btn" type="button" data-domain-rollback disabled={!viewerCanShip}>
889
- <svg><use href="#i-refresh"></use></svg>Roll back
890
- </button>
891
- </div>
892
- </div>
893
- </div>
894
- )}
895
- </div>
896
- </details>
897
- </div>
898
-
899
- <!-- Accept-to-preview LIFTOFF — the plan confirm + live integration flight +
900
- outcome ceremony. One dialog, three phases (plan → flight → outcome).
901
- The flight's stage rail advances ONLY on real signals (the in-flight POST
902
- plus GET /api/changes polling — includedPrs / activeRun / aggregateSha);
903
- nothing is simulated. Replaces the old window.confirm() plan popup. -->
904
- <div class="liftoff-backdrop" data-liftoff hidden>
905
- <div class="liftoff-card" role="dialog" aria-modal="true" aria-labelledby="liftoff-title" tabindex="-1">
906
- <div class="liftoff-phase" data-liftoff-plan>
907
- <p class="liftoff-kicker">Add to release</p>
908
- <h3 class="liftoff-title" id="liftoff-title" data-liftoff-title>Add to your next release</h3>
909
- <dl class="liftoff-grid" data-liftoff-grid></dl>
910
- <p class="note">
911
- Adds this change to your next release, then rebuilds the preview and re-runs your
912
- store's checks. <b>Nothing goes live</b> — publishing your release stays a
913
- separate, explicit step.
914
- </p>
915
- <div class="liftoff-actions">
916
- <button class="btn" type="button" data-liftoff-cancel>Cancel</button>
917
- <button class="btn primary" type="button" data-liftoff-proceed>
918
- <svg><use href="#i-check"></use></svg>Add to release
919
- </button>
920
- </div>
921
- </div>
922
-
923
- <div class="liftoff-phase" data-liftoff-flight hidden>
924
- <p class="liftoff-kicker">Adding to your release</p>
925
- <h3 class="liftoff-title" data-liftoff-flight-title>Adding…</h3>
926
- <ol class="liftoff-stages">
927
- <li data-liftoff-stage="merge">
928
- <span class="liftoff-dot"></span>
929
- <div><b>Add</b><span>add this change to your next release</span></div>
930
- </li>
931
- <li data-liftoff-stage="build">
932
- <span class="liftoff-dot"></span>
933
- <div><b>Build</b><span>rebuild the preview with your change in it</span></div>
934
- </li>
935
- <li data-liftoff-stage="evidence">
936
- <span class="liftoff-dot"></span>
937
- <div><b>Check</b><span>re-run your store's checks on the preview</span></div>
938
- </li>
939
- <li data-liftoff-stage="green">
940
- <span class="liftoff-dot"></span>
941
- <div><b>Ready</b><span>your next release is ready to review and publish</span></div>
942
- </li>
943
- </ol>
944
- <p class="liftoff-live" data-liftoff-live role="status" aria-live="polite">Working…</p>
945
- <div class="stream-log" data-liftoff-log role="log" aria-live="polite"></div>
946
- <div class="liftoff-meta">
947
- <span class="publish-mono" data-liftoff-elapsed>0:00</span>
948
- <button class="btn" type="button" data-liftoff-hide>Continue in background</button>
949
- </div>
950
- </div>
951
-
952
- <div class="liftoff-phase liftoff-outcome-phase" data-liftoff-outcome hidden>
953
- <div class="liftoff-seal" data-liftoff-seal aria-hidden="true"></div>
954
- <h3 class="liftoff-title" data-liftoff-outcome-title></h3>
955
- <p class="note" data-liftoff-outcome-detail></p>
956
- <!-- Merge-conflict recovery (shown ONLY for reason "merge_failed") — decision
957
- `automate-first-conflict-recovery` (u4): the refusal LEADS with one primary
958
- action ("Update this change automatically" → /resolve strategy:"merge",
959
- then auto-re-integrate). The three option cards appear only when diff3 hits
960
- a genuine same-line overlap (RefreshResult.unresolved); the old terminal +
961
- AI-agent guidance is demoted VERBATIM into the collapsed "Do it manually
962
- instead" expert section (nothing removed). Decision logic lives in the
963
- tested `resolveConflict.ts`; this glue mirrors it (verified on preview). -->
964
- <div class="liftoff-conflict" data-liftoff-conflict hidden>
965
- <p class="note liftoff-conflict-summary" data-conflict-summary hidden></p>
966
-
967
- <!-- PRIMARY ACTION — always first when a candidate is refused not-mergeable. -->
968
- <div class="liftoff-conflict-primary" data-conflict-primary>
969
- <p class="note" data-conflict-primary-note>
970
- We can rebuild your change on top of the store's current version and
971
- re-integrate it for you — no terminal, no re-submit.
972
- </p>
973
- <button class="btn primary" type="button" data-conflict-auto>Update this change automatically</button>
974
- <p class="note liftoff-conflict-primary-status" data-conflict-primary-status role="status" aria-live="polite" hidden></p>
975
- </div>
976
-
977
- <!-- OPTION CARDS — revealed only on a genuine same-line overlap diff3 can't
978
- safely auto-pick. Static nodes mirror CONFLICT_OPTION_CARDS. -->
979
- <div class="liftoff-conflict-cards" data-conflict-cards hidden>
980
- <p class="note" data-conflict-cards-intro>
981
- Some of the same lines were changed on both sides, so there's no single safe
982
- answer. Pick how to resolve the overlap:
983
- </p>
984
- <ul class="liftoff-conflict-files publish-mono" data-conflict-files></ul>
985
- <div class="liftoff-conflict-card-grid" role="group" aria-label="Resolve the overlap">
986
- <button class="liftoff-option-card" type="button" data-conflict-card data-strategy="merge" data-recommended="1">
987
- <b>Combine both</b>
988
- <span>Recommended — weave both sets of edits together</span>
989
- </button>
990
- <button class="liftoff-option-card" type="button" data-conflict-card data-strategy="ours">
991
- <b>Keep my change</b>
992
- <span>Use your edits on the conflicting lines</span>
993
- </button>
994
- <button class="liftoff-option-card" type="button" data-conflict-card data-strategy="theirs">
995
- <b>Keep the store's current version</b>
996
- <span>Use the store's edits on the conflicting lines</span>
997
- </button>
998
- </div>
999
- <p class="note liftoff-conflict-cards-status" data-conflict-cards-status role="status" aria-live="polite" hidden></p>
1000
- </div>
1001
-
1002
- <!-- EXPERT FALLBACK — the ORIGINAL terminal + AI-agent guidance, verbatim,
1003
- collapsed. Nothing deleted; just demoted from primary to fallback. -->
1004
- <details class="liftoff-conflict-expert" data-conflict-expert>
1005
- <summary>Do it manually instead</summary>
1006
- <p class="note" data-conflict-intro>
1007
- From the store checkout that submitted this change, bring it up to date and
1008
- re-submit — the open PR updates in place. Once the new build is ready,
1009
- finish here with <b>Add to release</b>, or from the terminal with
1010
- <code>tot accept --tenant &lt;store&gt; --pr &lt;N&gt;</code>.
1011
- Check <code>git status</code> first — abort any half-finished rebase from an
1012
- earlier attempt. Then <code>tot pr</code>: this change should be marked
1013
- <code>(active)</code>, proving you're in the right checkout and branch.
1014
- If git reports an authentication failure, run <code>tot login</code> and retry.
1015
- </p>
1016
- <pre class="liftoff-commands publish-mono" data-conflict-commands></pre>
1017
- <div class="liftoff-conflict-actions">
1018
- <button class="btn" type="button" data-conflict-copy-prompt>Copy prompt for your AI agent</button>
1019
- <button class="btn" type="button" data-conflict-copy>Copy commands</button>
1020
- </div>
1021
- <p class="liftoff-conflict-raw publish-mono" data-conflict-raw></p>
1022
- </details>
1023
- </div>
1024
- <div class="liftoff-actions">
1025
- <a class="btn" data-liftoff-review target="_blank" rel="noopener" hidden>Review the preview build</a>
1026
- <!-- Fail-visible next step: when the merge LANDED but the preview build
1027
- failed, this offers Rebuild (not a dead "did not land"); on an
1028
- indeterminate outcome it offers Re-check. Hidden on success/conflict
1029
- (conflict recovery owns its own primary action). -->
1030
- <button class="btn primary" type="button" data-liftoff-rebuild hidden>Rebuild the preview build</button>
1031
- <button class="btn primary" type="button" data-liftoff-done>Done</button>
1032
- </div>
1033
- </div>
1034
-
1035
- <div class="liftoff-confetti" data-liftoff-confetti aria-hidden="true"></div>
1036
- </div>
1037
- </div>
1038
-
1039
- <!-- Publish-live ceremony — the go-live counterpart of the accept liftoff
1040
- (same shell/styles). Plan states the exact digest + no-rebuild contract;
1041
- flight is a single honest in-flight state (one POST, no staged theater);
1042
- outcome celebrates with a real "View your live store" link. Replaces the
1043
- raw window.confirm() publish popup. -->
1044
- <div class="liftoff-backdrop" data-shipoff hidden>
1045
- <div class="liftoff-card" role="dialog" aria-modal="true" aria-labelledby="shipoff-title" tabindex="-1">
1046
- <div class="liftoff-phase" data-shipoff-plan>
1047
- <p class="liftoff-kicker">Publish live</p>
1048
- <h3 class="liftoff-title" id="shipoff-title">Publish the reviewed build to your live store</h3>
1049
- <dl class="liftoff-grid" data-shipoff-grid></dl>
1050
- <p class="note">
1051
- Publishes this <b>exact reviewed digest</b> — no rebuild, nothing rides along.
1052
- Your live store updates in about a second; rolling back re-publishes the prior
1053
- version from the archive.
1054
- </p>
1055
- <div class="liftoff-actions">
1056
- <button class="btn" type="button" data-shipoff-cancel>Cancel</button>
1057
- <button class="btn primary" type="button" data-shipoff-proceed>
1058
- <svg><use href="#i-export"></use></svg>Publish live
1059
- </button>
1060
- </div>
1061
- </div>
1062
-
1063
- <div class="liftoff-phase" data-shipoff-flight hidden>
1064
- <p class="liftoff-kicker">Publishing</p>
1065
- <h3 class="liftoff-title">Promoting the reviewed digest…</h3>
1066
- <p class="liftoff-live" data-shipoff-live role="status" aria-live="polite">
1067
- Pinning the exact reviewed build to your live channel…
1068
- </p>
1069
- <div class="stream-log" data-shipoff-log role="log" aria-live="polite"></div>
1070
- <div class="liftoff-meta">
1071
- <span class="publish-mono" data-shipoff-elapsed>0:00</span>
1072
- <button class="btn" type="button" data-shipoff-hide>Continue in background</button>
1073
- </div>
1074
- </div>
1075
-
1076
- <div class="liftoff-phase liftoff-outcome-phase" data-shipoff-outcome hidden>
1077
- <div class="liftoff-seal" data-shipoff-seal aria-hidden="true"></div>
1078
- <h3 class="liftoff-title" data-shipoff-outcome-title></h3>
1079
- <p class="note" data-shipoff-outcome-detail></p>
1080
- <!-- Failure detail + "Copy for your AI agent" — shown ONLY when the publish did
1081
- not complete. Mirrors the build-flight failure affordance. -->
1082
- <div class="liftoff-conflict" data-shipoff-failure hidden>
1083
- <pre class="liftoff-commands publish-mono" data-shipoff-failure-detail></pre>
1084
- <div class="liftoff-conflict-actions">
1085
- <button class="btn" type="button" data-shipoff-copy>Copy for your AI agent</button>
1086
- </div>
1087
- </div>
1088
- <div class="liftoff-actions">
1089
- <a class="btn primary" data-shipoff-view target="_blank" rel="noopener" hidden>View live</a>
1090
- <!-- r10 — right at the post-publish moment: jump straight to the rollback
1091
- row for the version this release just replaced, pre-targeted, so a
1092
- publish that looked wrong is one click from being undone. Reuses the
1093
- existing Live Site rollback action; shown only on a successful ship. -->
1094
- <button class="btn" type="button" data-shipoff-undo hidden>
1095
- <svg><use href="#i-undo"></use></svg>Undo this release
1096
- </button>
1097
- <button class="btn" type="button" data-shipoff-done>Done</button>
1098
- </div>
1099
- </div>
1100
-
1101
- <div class="liftoff-confetti" data-shipoff-confetti aria-hidden="true"></div>
1102
- </div>
1103
- </div>
1104
-
1105
- <!-- Build/check flight — the real-time counterpart of the accept liftoff for the
1106
- "Build preview" / "Run checks" action. `POST /api/preview/build` streams its
1107
- lifecycle phases over SSE (Accept: text/event-stream); this dialog reflects them
1108
- live on the SAME stage-rail + "Continue in background" pattern as the liftoff.
1109
- On a FAILURE the outcome shows the full detail the build reported plus a "Copy
1110
- for your AI agent" button (mirrors the merge-conflict copy affordance). Progress
1111
- reduction + copy assembly live in the tested buildStream.ts; this glue mirrors
1112
- it (verified on preview). -->
1113
- <div class="liftoff-backdrop" data-buildoff hidden>
1114
- <div class="liftoff-card" role="dialog" aria-modal="true" aria-labelledby="buildoff-title" tabindex="-1">
1115
- <div class="liftoff-phase" data-buildoff-flight>
1116
- <p class="liftoff-kicker">Building preview</p>
1117
- <h3 class="liftoff-title" id="buildoff-title" data-buildoff-flight-title>Building…</h3>
1118
- <ol class="liftoff-stages">
1119
- <li data-buildoff-stage="resolve">
1120
- <span class="liftoff-dot"></span>
1121
- <div><b>Locate commit</b><span>find the pull request's latest commit</span></div>
1122
- </li>
1123
- <li data-buildoff-stage="read">
1124
- <span class="liftoff-dot"></span>
1125
- <div><b>Read &amp; validate</b><span>read your store's files and check them</span></div>
1126
- </li>
1127
- <li data-buildoff-stage="publish">
1128
- <span class="liftoff-dot"></span>
1129
- <div><b>Publish version</b><span>materialize this version's hosted preview</span></div>
1130
- </li>
1131
- <li data-buildoff-stage="check">
1132
- <span class="liftoff-dot"></span>
1133
- <div><b>Run checks</b><span>compute the evidence for this version</span></div>
1134
- </li>
1135
- </ol>
1136
- <p class="liftoff-live" data-buildoff-live role="status" aria-live="polite">Starting…</p>
1137
- <div class="stream-log" data-buildoff-log role="log" aria-live="polite"></div>
1138
- <div class="liftoff-meta">
1139
- <span class="publish-mono" data-buildoff-elapsed>0:00</span>
1140
- <button class="btn" type="button" data-buildoff-hide>Continue in background</button>
1141
- </div>
1142
- </div>
1143
-
1144
- <div class="liftoff-phase liftoff-outcome-phase" data-buildoff-outcome hidden>
1145
- <div class="liftoff-seal" data-buildoff-seal aria-hidden="true"></div>
1146
- <h3 class="liftoff-title" data-buildoff-outcome-title></h3>
1147
- <p class="note" data-buildoff-outcome-detail></p>
1148
- <!-- Failure detail + "Copy for your AI agent" — shown ONLY when the build did
1149
- not succeed. The detail is the honest reason the build reported (errors +
1150
- blocking findings); the copy button hands a framed brief to a coding agent. -->
1151
- <div class="liftoff-conflict" data-buildoff-failure hidden>
1152
- <pre class="liftoff-commands publish-mono" data-buildoff-failure-detail></pre>
1153
- <div class="liftoff-conflict-actions">
1154
- <button class="btn" type="button" data-buildoff-copy>Copy for your AI agent</button>
1155
- </div>
1156
- </div>
1157
- <div class="liftoff-actions">
1158
- <a class="btn" data-buildoff-review target="_blank" rel="noopener" hidden>Open the preview</a>
1159
- <button class="btn primary" type="button" data-buildoff-done>Done</button>
1160
- </div>
1161
- </div>
1162
- </div>
1163
- </div>
1164
-
1165
- <!-- Retry-build flight — the real-time counterpart for the aggregate "Retry build"
1166
- action (`POST /api/changes/rebuild`), which re-runs ONLY the aggregate rebuild +
1167
- combined evidence against the last merged commit (no new merge). It streams its
1168
- reading/publishing/checking phases over SSE into the SAME shared scrolling
1169
- live-log console; "Continue in background" hides it while the stream keeps
1170
- running, and on a FAILURE the outcome shows the honest detail + a "Copy for your
1171
- AI agent" button — the same affordances as the build/ship flights. -->
1172
- <div class="liftoff-backdrop" data-rebuildoff hidden>
1173
- <div class="liftoff-card" role="dialog" aria-modal="true" aria-labelledby="rebuildoff-title" tabindex="-1">
1174
- <div class="liftoff-phase" data-rebuildoff-flight>
1175
- <p class="liftoff-kicker">Retrying the build</p>
1176
- <h3 class="liftoff-title" id="rebuildoff-title">Rebuilding your next release preview…</h3>
1177
- <p class="liftoff-live" data-rebuildoff-live role="status" aria-live="polite">Starting…</p>
1178
- <div class="stream-log" data-rebuildoff-log role="log" aria-live="polite"></div>
1179
- <div class="liftoff-meta">
1180
- <span class="publish-mono" data-rebuildoff-elapsed>0:00</span>
1181
- <button class="btn" type="button" data-rebuildoff-hide>Continue in background</button>
1182
- </div>
1183
- </div>
1184
-
1185
- <div class="liftoff-phase liftoff-outcome-phase" data-rebuildoff-outcome hidden>
1186
- <div class="liftoff-seal" data-rebuildoff-seal aria-hidden="true"></div>
1187
- <h3 class="liftoff-title" data-rebuildoff-outcome-title></h3>
1188
- <p class="note" data-rebuildoff-outcome-detail></p>
1189
- <div class="liftoff-conflict" data-rebuildoff-failure hidden>
1190
- <pre class="liftoff-commands publish-mono" data-rebuildoff-failure-detail></pre>
1191
- <div class="liftoff-conflict-actions">
1192
- <button class="btn" type="button" data-rebuildoff-copy>Copy for your AI agent</button>
1193
- </div>
1194
- </div>
1195
- <div class="liftoff-actions">
1196
- <button class="btn primary" type="button" data-rebuildoff-done>Done</button>
1197
- </div>
1198
- </div>
1199
- </div>
1200
- </div>
1201
- </section>
1202
-
1203
- <script>
1204
- import { initAdminPublishTab } from "@/lib/publish/adminPublishTab";
1205
- initAdminPublishTab();
1206
- </script>
1207
-
1208
- <style>
1209
- .publish-table {
1210
- min-width: 900px;
1211
- }
1212
-
1213
- /* r06 — responsive change queue. On a narrow screen the min-width:900px queue
1214
- table forces horizontal scroll, unusable on a phone. Below ~700px, restack the
1215
- existing rows into labeled cards — CSS-only, no DOM/JS change: the table, rows
1216
- and cells become blocks, the visual header is hidden (kept for assistive tech),
1217
- and each cell gains its column label via a ::before drawn from the fixed thead
1218
- order. Scoped to #publish-queue so the aggregate/versions tables are untouched.
1219
- Full-width colspan rows (empty state, sign-in notice, the inline review pane)
1220
- stay unlabeled and full width. */
1221
- @media (max-width: 700px) {
1222
- #publish-queue .publish-table,
1223
- #publish-queue .publish-table tbody,
1224
- #publish-queue .publish-table tr,
1225
- #publish-queue .publish-table td {
1226
- display: block;
1227
- min-width: 0;
1228
- width: auto;
1229
- }
1230
- #publish-queue .publish-table thead {
1231
- position: absolute;
1232
- width: 1px;
1233
- height: 1px;
1234
- overflow: hidden;
1235
- clip: rect(0 0 0 0);
1236
- clip-path: inset(50%);
1237
- white-space: nowrap;
1238
- }
1239
- #publish-queue .publish-table tbody tr {
1240
- margin: 0 0 12px;
1241
- padding: 10px 12px;
1242
- border: 1px solid var(--admin-line);
1243
- border-radius: var(--admin-radius-sm);
1244
- background: var(--admin-surface, #fff);
1245
- }
1246
- #publish-queue .publish-table tbody td {
1247
- padding: 4px 0;
1248
- text-align: left;
1249
- border: 0;
1250
- }
1251
- #publish-queue .publish-table tbody td:not([colspan])::before {
1252
- display: block;
1253
- margin-bottom: 2px;
1254
- font-size: 11px;
1255
- font-weight: 600;
1256
- text-transform: uppercase;
1257
- letter-spacing: 0.04em;
1258
- opacity: 0.6;
1259
- }
1260
- #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(1)::before {
1261
- content: "Change";
1262
- }
1263
- #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(2)::before {
1264
- content: "Status";
1265
- }
1266
- #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(3)::before {
1267
- content: "Checks";
1268
- }
1269
- #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(4)::before {
1270
- content: "Head";
1271
- }
1272
- #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(5)::before {
1273
- content: "Updated";
1274
- }
1275
- #publish-queue .publish-table tbody td:not([colspan]):nth-of-type(6)::before {
1276
- content: "Actions";
1277
- }
1278
- }
1279
-
1280
- /* Collapsible panels (What needs your approval, Next release, Live Site): the
1281
- panel-head IS the summary. Kill the native marker, add a rotating chevron, and
1282
- make the whole head a click target. Mirrors the Rollback options (.versions-details). */
1283
- .publish-queue-details > summary.panel-head,
1284
- .closed-changes-details > summary.panel-head,
1285
- .agg-console-details > summary.panel-head,
1286
- .live-site-details > summary.panel-head,
1287
- .versions-details > summary.live-site-subhead {
1288
- cursor: pointer;
1289
- list-style: none;
1290
- user-select: none;
1291
- }
1292
- .publish-queue-details > summary.panel-head::-webkit-details-marker,
1293
- .closed-changes-details > summary.panel-head::-webkit-details-marker,
1294
- .agg-console-details > summary.panel-head::-webkit-details-marker,
1295
- .live-site-details > summary.panel-head::-webkit-details-marker,
1296
- .versions-details > summary.live-site-subhead::-webkit-details-marker {
1297
- display: none;
1298
- }
1299
- .publish-queue-details > summary.panel-head h3::before,
1300
- .closed-changes-details > summary.panel-head h3::before,
1301
- .agg-console-details > summary.panel-head h3::before,
1302
- .live-site-details > summary.panel-head h3::before,
1303
- .versions-details > summary.live-site-subhead h4::before {
1304
- content: "";
1305
- width: 7px;
1306
- height: 7px;
1307
- margin-right: 2px;
1308
- border-right: 2px solid currentColor;
1309
- border-bottom: 2px solid currentColor;
1310
- transform: rotate(-45deg);
1311
- transition: transform 0.15s ease;
1312
- opacity: 0.6;
1313
- }
1314
- .publish-queue-details[open] > summary.panel-head h3::before,
1315
- .closed-changes-details[open] > summary.panel-head h3::before,
1316
- .agg-console-details[open] > summary.panel-head h3::before,
1317
- .live-site-details[open] > summary.panel-head h3::before,
1318
- .versions-details[open] > summary.live-site-subhead h4::before {
1319
- transform: rotate(45deg);
1320
- }
1321
-
1322
- /* The collapsed release summary owns one mutually exclusive state affordance:
1323
- a green current-state chip or the action that advances live. Both classes set
1324
- display, so explicitly preserve the semantic hidden state. */
1325
- [data-live-release-state][hidden],
1326
- .live-release-publish[hidden] {
1327
- display: none;
1328
- }
1329
-
1330
- /* Live Site — the two clickable public entry points, previously undiscoverable. */
1331
- .live-site-links {
1332
- list-style: none;
1333
- margin: 0;
1334
- padding: 0;
1335
- display: grid;
1336
- gap: 12px;
1337
- }
1338
- .live-site-links > li {
1339
- display: grid;
1340
- grid-template-columns: minmax(96px, auto) 1fr;
1341
- align-items: baseline;
1342
- gap: 4px 12px;
1343
- }
1344
- .live-site-link-label {
1345
- font-weight: 600;
1346
- font-size: 13px;
1347
- color: var(--admin-fg, inherit);
1348
- }
1349
- .live-site-link-url {
1350
- word-break: break-all;
1351
- font-size: 13px;
1352
- }
1353
- .live-site-link-note {
1354
- grid-column: 2;
1355
- font-size: 12px;
1356
- opacity: 0.7;
1357
- }
1358
-
1359
- /* r13 — Rollback + Public domain nested INSIDE Live Site read as a subsection,
1360
- not a sibling panel: a single left rule + generous spacing rather than a boxed
1361
- card, so the Live Site panel doesn't stack boxes-in-boxes. */
1362
- .live-site-subsection {
1363
- border-left: 2px solid var(--admin-line);
1364
- padding-left: 16px;
1365
- margin-top: 18px;
1366
- display: grid;
1367
- gap: 12px;
1368
- }
1369
- /* r13 — the Public-domain status trio (Serving from / Rollback records / Last run)
1370
- were full `.field` cards (border + fill) nested inside the Live Site panel — the
1371
- genuine boxes-in-boxes. Flatten them to plain label/value blocks (hooks kept);
1372
- the subsection's left rule + grid gap carry the separation instead. */
1373
- .live-site-subsection .field {
1374
- border: 0;
1375
- background: transparent;
1376
- padding: 0;
1377
- }
1378
- .live-site-subhead {
1379
- display: flex;
1380
- align-items: center;
1381
- justify-content: space-between;
1382
- gap: 12px;
1383
- }
1384
- .live-site-subhead h4 {
1385
- margin: 0;
1386
- display: flex;
1387
- align-items: center;
1388
- gap: 8px;
1389
- font-size: 14px;
1390
- }
1391
-
1392
- .publish-mono {
1393
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1394
- font-size: 12px;
1395
- }
1396
-
1397
- /* r11 — the human release name beside the "Next release" heading. */
1398
- .agg-release-name {
1399
- font-size: 13px;
1400
- font-weight: 600;
1401
- color: var(--admin-muted);
1402
- }
1403
- .agg-release-name[hidden] {
1404
- display: none;
1405
- }
1406
-
1407
- /* r02 — the humanized "Live version" metric: "v42" as a link to the live store,
1408
- with the raw SHA tucked into a "Technical details" disclosure. */
1409
- .live-version-link {
1410
- color: inherit;
1411
- text-decoration: none;
1412
- border-bottom: 1px solid color-mix(in srgb, currentColor 35%, transparent);
1413
- }
1414
- .live-version-link:hover {
1415
- border-bottom-color: currentColor;
1416
- }
1417
- .metric-tech,
1418
- .ver-tech {
1419
- margin-top: 4px;
1420
- font-size: 12px;
1421
- }
1422
- .metric-tech > summary,
1423
- .ver-tech > summary {
1424
- cursor: pointer;
1425
- color: var(--muted, #64716d);
1426
- font-size: 11px;
1427
- list-style: none;
1428
- }
1429
- .metric-tech > summary::-webkit-details-marker,
1430
- .ver-tech > summary::-webkit-details-marker {
1431
- display: none;
1432
- }
1433
- .ver-tech {
1434
- margin-top: 6px;
1435
- }
1436
-
1437
- /* r08 — the "Copy preview link" affordance (icon + text, never color-only). */
1438
- .publish-copy-preview {
1439
- display: inline-flex;
1440
- align-items: center;
1441
- gap: 5px;
1442
- }
1443
- .publish-copy-preview svg {
1444
- width: 14px;
1445
- height: 14px;
1446
- }
1447
-
1448
- .publish-actions {
1449
- white-space: nowrap;
1450
- }
1451
-
1452
- .publish-actions .btn {
1453
- margin-left: 6px;
1454
- }
1455
-
1456
- /* The per-row orphan "Delete this branch" action — a deliberate, destructive,
1457
- one-at-a-time HUMAN delete, styled distinct from the neutral "Clean up" and the
1458
- bulk "Sweep dead branches" so it never reads as a routine cleanup. */
1459
- .btn.branch-delete-orphan {
1460
- border-color: var(--admin-danger, #b3261e);
1461
- color: var(--admin-danger, #b3261e);
1462
- }
1463
-
1464
- .btn.branch-delete-orphan:not(:disabled):hover {
1465
- background: var(--admin-danger, #b3261e);
1466
- color: #fff;
1467
- }
1468
-
1469
- .publish-status {
1470
- min-height: 0;
1471
- }
1472
-
1473
- .publish-status:empty {
1474
- display: none;
1475
- }
1476
-
1477
- .publish-status[data-state="ok"] {
1478
- color: var(--admin-green);
1479
- }
1480
-
1481
- .publish-status[data-state="err"] {
1482
- color: var(--admin-red);
1483
- }
1484
-
1485
- /* Public-site status card, verified-current tone — .risk-alert defaults to an
1486
- amber/warning look (admin.astro), wrong for "this is fine, confirmed fresh". */
1487
- .risk-alert.public-site-ok {
1488
- border-color: var(--admin-green);
1489
- background: var(--admin-green-soft);
1490
- }
1491
-
1492
- .risk-alert.public-site-ok svg {
1493
- color: var(--admin-green);
1494
- }
1495
-
1496
- /* ── State-driven hero (r01) ─────────────────────────────────────────────
1497
- The single top-of-tab state element. One row: an icon whose tone conveys
1498
- urgency (never color alone — a "Action needed" kicker rides the two blocker
1499
- tones), the one-sentence state + supporting detail, and one primary CTA laid
1500
- out to the trailing edge (like the paywall gate). Tone is set by
1501
- renderHero() via [data-tone]. */
1502
- .publish-hero-region {
1503
- display: grid;
1504
- gap: 12px;
1505
- }
1506
- .publish-hero {
1507
- display: flex;
1508
- align-items: center;
1509
- justify-content: space-between;
1510
- gap: 16px;
1511
- flex-wrap: wrap;
1512
- padding: 14px 16px;
1513
- border: 1px solid var(--admin-line-strong);
1514
- border-left-width: 4px;
1515
- border-radius: var(--admin-radius, 12px);
1516
- background: var(--admin-surface);
1517
- }
1518
- .publish-hero[hidden] {
1519
- display: none;
1520
- }
1521
- .publish-hero-main {
1522
- display: flex;
1523
- align-items: center;
1524
- gap: 13px;
1525
- min-width: 0;
1526
- }
1527
- .publish-hero-icon {
1528
- flex: none;
1529
- width: 34px;
1530
- height: 34px;
1531
- display: grid;
1532
- place-items: center;
1533
- border-radius: 9px;
1534
- background: var(--admin-surface-soft);
1535
- }
1536
- .publish-hero-icon svg {
1537
- width: 19px;
1538
- height: 19px;
1539
- }
1540
- .publish-hero-copy {
1541
- display: grid;
1542
- gap: 2px;
1543
- min-width: 0;
1544
- }
1545
- .publish-hero-kicker {
1546
- font-size: 10.5px;
1547
- font-weight: 760;
1548
- letter-spacing: 0.08em;
1549
- text-transform: uppercase;
1550
- color: var(--admin-red);
1551
- }
1552
- .publish-hero-kicker[hidden] {
1553
- display: none;
1554
- }
1555
- .publish-hero-headline {
1556
- font-size: 15.5px;
1557
- line-height: 1.3;
1558
- color: var(--admin-ink);
1559
- }
1560
- .publish-hero-detail {
1561
- font-size: 13px;
1562
- color: var(--admin-muted);
1563
- line-height: 1.45;
1564
- }
1565
- .publish-hero-detail:empty {
1566
- display: none;
1567
- }
1568
- .publish-hero-actions {
1569
- flex: none;
1570
- display: flex;
1571
- gap: 8px;
1572
- }
1573
- .publish-hero-cta[hidden] {
1574
- display: none;
1575
- }
1576
-
1577
- /* Tones — the accent rail + icon colour. Blocker/warn borrow the admin
1578
- red/amber tokens; ready/attention the teal; calm stays neutral. */
1579
- .publish-hero[data-tone="blocker"] {
1580
- border-left-color: var(--admin-red);
1581
- background: var(--admin-red-soft);
1582
- }
1583
- .publish-hero[data-tone="blocker"] .publish-hero-icon svg {
1584
- color: var(--admin-red);
1585
- }
1586
- .publish-hero[data-tone="warn"] {
1587
- border-left-color: var(--admin-amber);
1588
- background: var(--admin-surface-soft);
1589
- }
1590
- .publish-hero[data-tone="warn"] .publish-hero-icon svg {
1591
- color: var(--admin-amber);
1592
- }
1593
- .publish-hero[data-tone="attention"] {
1594
- border-left-color: var(--admin-teal, #0f9d8f);
1595
- }
1596
- .publish-hero[data-tone="attention"] .publish-hero-icon svg {
1597
- color: var(--admin-teal, #0f9d8f);
1598
- }
1599
- .publish-hero[data-tone="ready"] {
1600
- border-left-color: var(--admin-green);
1601
- background: var(--admin-green-soft);
1602
- }
1603
- .publish-hero[data-tone="ready"] .publish-hero-icon svg {
1604
- color: var(--admin-green);
1605
- }
1606
- .publish-hero[data-tone="calm"] {
1607
- border-left-color: var(--admin-line-strong);
1608
- }
1609
- .publish-hero[data-tone="calm"] .publish-hero-icon svg {
1610
- color: var(--admin-green);
1611
- }
1612
-
1613
- /* r03 stale-public-site alarm — lays its "Open Live Site" deep-link to the
1614
- trailing edge, like the paywall gate. Inherits the amber .risk-alert look. */
1615
- .risk-alert.publish-stale-site {
1616
- display: flex;
1617
- align-items: center;
1618
- justify-content: space-between;
1619
- gap: 12px;
1620
- flex-wrap: wrap;
1621
- }
1622
- .risk-alert.publish-stale-site .btn {
1623
- flex: none;
1624
- }
1625
-
1626
- /* Per-row finding: the ownership-tag chip leads the row's action cell (what's
1627
- wrong, in operator words), and the mapped action button gets a subtle ring so
1628
- the operator sees which button resolves it. Tone (red/amber/neutral) rides the
1629
- shared .chip convention; primed emphasis is added to the EXISTING button. */
1630
- .publish-actions .publish-finding-chip {
1631
- vertical-align: middle;
1632
- }
1633
-
1634
- .publish-actions .btn.publish-cta-primed {
1635
- box-shadow: 0 0 0 2px var(--admin-amber);
1636
- }
1637
-
1638
- /* ?pr=N deep link: "not in the queue" callout + row flash */
1639
- .publish-pr-callout {
1640
- border: 1px solid var(--admin-line-strong);
1641
- border-left: 3px solid var(--admin-amber);
1642
- background: var(--admin-surface-soft);
1643
- padding: 8px 12px;
1644
- border-radius: var(--admin-radius-sm);
1645
- color: var(--admin-ink);
1646
- }
1647
-
1648
- @keyframes publish-row-flash {
1649
- 0%,
1650
- 25% {
1651
- background-color: var(--admin-teal-soft);
1652
- }
1653
- 100% {
1654
- background-color: transparent;
1655
- }
1656
- }
1657
-
1658
- .publish-row-highlight > td {
1659
- animation: publish-row-flash 2.4s ease-out;
1660
- }
1661
-
1662
- @media (prefers-reduced-motion: reduce) {
1663
- .publish-row-highlight > td {
1664
- animation: none;
1665
- background-color: var(--admin-teal-soft);
1666
- }
1667
- }
1668
-
1669
- .publish-error-detail {
1670
- color: var(--admin-red);
1671
- font-size: 12px;
1672
- }
1673
-
1674
- /* ── Accept-to-preview LIFTOFF ─────────────────────────────────────────── */
1675
- .liftoff-backdrop {
1676
- position: fixed;
1677
- inset: 0;
1678
- z-index: 60;
1679
- display: grid;
1680
- place-items: center;
1681
- padding: 24px;
1682
- background: color-mix(in srgb, var(--admin-ink) 32%, transparent);
1683
- backdrop-filter: blur(6px);
1684
- -webkit-backdrop-filter: blur(6px);
1685
- }
1686
-
1687
- /* `display: grid` above beats the UA's [hidden] rule — restate it, or the
1688
- invisible backdrop click-blocks the entire page (caught by live test). */
1689
- .liftoff-backdrop[hidden] {
1690
- display: none;
1691
- }
1692
-
1693
- .liftoff-card {
1694
- position: relative;
1695
- width: min(520px, 100%);
1696
- max-height: min(84vh, 640px);
1697
- overflow: auto;
1698
- border: 1px solid var(--admin-line-strong);
1699
- border-radius: var(--admin-radius, 12px);
1700
- background: var(--admin-surface);
1701
- box-shadow: 0 24px 80px rgba(8, 32, 30, 0.35);
1702
- padding: 24px 26px;
1703
- animation: liftoff-in 0.22s ease-out;
1704
- }
1705
-
1706
- @keyframes liftoff-in {
1707
- from { transform: translateY(10px) scale(0.985); opacity: 0; }
1708
- to { transform: none; opacity: 1; }
1709
- }
1710
-
1711
- /* In-flight shimmer: a live gradient beam along the card's top edge. */
1712
- .liftoff-card::before {
1713
- content: "";
1714
- position: absolute;
1715
- inset: 0 0 auto 0;
1716
- height: 3px;
1717
- border-radius: inherit;
1718
- opacity: 0;
1719
- background: linear-gradient(90deg, transparent, var(--admin-teal, #0f9d8f), #f4b942, var(--admin-teal, #0f9d8f), transparent);
1720
- background-size: 220% 100%;
1721
- transition: opacity 0.3s ease;
1722
- }
1723
-
1724
- .liftoff-card[data-mode="flight"]::before {
1725
- opacity: 1;
1726
- animation: liftoff-beam 1.6s linear infinite;
1727
- }
1728
-
1729
- @keyframes liftoff-beam {
1730
- from { background-position: 200% 0; }
1731
- to { background-position: -20% 0; }
1732
- }
1733
-
1734
- .liftoff-kicker {
1735
- margin: 0 0 2px;
1736
- font-size: 11px;
1737
- font-weight: 700;
1738
- letter-spacing: 0.08em;
1739
- text-transform: uppercase;
1740
- color: var(--admin-teal, #0f9d8f);
1741
- }
1742
-
1743
- .liftoff-title {
1744
- margin: 0 0 14px;
1745
- font-size: 19px;
1746
- line-height: 1.25;
1747
- }
1748
-
1749
- .liftoff-grid {
1750
- display: grid;
1751
- grid-template-columns: auto 1fr;
1752
- gap: 6px 16px;
1753
- margin: 0 0 12px;
1754
- padding: 12px 14px;
1755
- border: 1px solid var(--admin-line-strong);
1756
- border-radius: var(--admin-radius-sm);
1757
- background: var(--admin-surface-soft);
1758
- }
1759
-
1760
- .liftoff-grid dt {
1761
- color: var(--admin-muted);
1762
- font-size: 12px;
1763
- }
1764
-
1765
- .liftoff-grid dd {
1766
- margin: 0;
1767
- font-size: 13px;
1768
- }
1769
-
1770
- .liftoff-actions {
1771
- display: flex;
1772
- justify-content: flex-end;
1773
- gap: 8px;
1774
- margin-top: 16px;
1775
- }
1776
-
1777
- /* Merge-conflict guidance (outcome phase, reason "merge_failed" only) —
1778
- directions, not mood: the terminal recovery front and center, the forge's
1779
- raw refusal kept small underneath for support/diagnosis. Left-aligned
1780
- inside the centered outcome phase: commands and prose read as
1781
- instructions, not ceremony. */
1782
- .liftoff-conflict {
1783
- margin-top: 10px;
1784
- text-align: left;
1785
- }
1786
-
1787
- .liftoff-commands {
1788
- margin: 10px 0 8px;
1789
- padding: 10px 12px;
1790
- border: 1px solid var(--admin-line-strong);
1791
- border-radius: var(--admin-radius-sm);
1792
- background: var(--admin-surface-soft);
1793
- font-size: 12px;
1794
- line-height: 1.55;
1795
- overflow-x: auto;
1796
- white-space: pre;
1797
- }
1798
-
1799
- .liftoff-conflict-actions {
1800
- display: flex;
1801
- justify-content: flex-end;
1802
- gap: 8px;
1803
- flex-wrap: wrap;
1804
- }
1805
-
1806
- .liftoff-conflict-raw {
1807
- margin: 10px 0 0;
1808
- font-size: 11px;
1809
- color: var(--admin-muted);
1810
- overflow-wrap: anywhere;
1811
- }
1812
-
1813
- /* Automate-first recovery: the primary action leads, option cards appear
1814
- only on a genuine overlap, the terminal guidance is a collapsed fallback. */
1815
- .liftoff-conflict-summary {
1816
- margin: 0 0 10px;
1817
- }
1818
- .liftoff-conflict-primary {
1819
- display: flex;
1820
- flex-direction: column;
1821
- align-items: flex-start;
1822
- gap: 8px;
1823
- margin-bottom: 12px;
1824
- }
1825
- .liftoff-conflict-primary .btn.primary {
1826
- align-self: flex-start;
1827
- }
1828
- .liftoff-conflict-primary-status[data-state="err"],
1829
- .liftoff-conflict-cards-status[data-state="err"] {
1830
- color: var(--admin-danger, #b3261e);
1831
- }
1832
- .liftoff-conflict-cards {
1833
- margin: 4px 0 12px;
1834
- padding: 12px;
1835
- border: 1px solid var(--admin-line-strong);
1836
- border-radius: var(--admin-radius-sm);
1837
- background: var(--admin-surface-soft);
1838
- }
1839
- .liftoff-conflict-files {
1840
- list-style: none;
1841
- margin: 8px 0 12px;
1842
- padding: 8px 10px;
1843
- border-radius: var(--admin-radius-sm);
1844
- background: var(--admin-surface, #fff);
1845
- font-size: 12px;
1846
- line-height: 1.6;
1847
- overflow-x: auto;
1848
- }
1849
- .liftoff-conflict-card-grid {
1850
- display: grid;
1851
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
1852
- gap: 8px;
1853
- }
1854
- .liftoff-option-card {
1855
- display: flex;
1856
- flex-direction: column;
1857
- gap: 4px;
1858
- text-align: left;
1859
- padding: 12px;
1860
- border: 1px solid var(--admin-line-strong);
1861
- border-radius: var(--admin-radius-sm);
1862
- background: var(--admin-surface, #fff);
1863
- cursor: pointer;
1864
- font: inherit;
1865
- color: inherit;
1866
- }
1867
- .liftoff-option-card:hover:not(:disabled) {
1868
- border-color: var(--admin-accent, #0f9d8f);
1869
- }
1870
- .liftoff-option-card:disabled {
1871
- opacity: 0.55;
1872
- cursor: default;
1873
- }
1874
- .liftoff-option-card[data-recommended="1"] {
1875
- border-color: var(--admin-accent, #0f9d8f);
1876
- box-shadow: inset 0 0 0 1px var(--admin-accent, #0f9d8f);
1877
- }
1878
- .liftoff-option-card b {
1879
- font-size: 13px;
1880
- }
1881
- .liftoff-option-card span {
1882
- font-size: 11px;
1883
- color: var(--admin-muted);
1884
- }
1885
- .liftoff-conflict-expert {
1886
- margin-top: 10px;
1887
- border-top: 1px solid var(--admin-line, #e5e5e5);
1888
- padding-top: 8px;
1889
- }
1890
- .liftoff-conflict-expert > summary {
1891
- cursor: pointer;
1892
- font-size: 12px;
1893
- color: var(--admin-muted);
1894
- user-select: none;
1895
- }
1896
-
1897
- .liftoff-stages {
1898
- list-style: none;
1899
- margin: 6px 0 4px;
1900
- padding: 0;
1901
- display: grid;
1902
- gap: 0;
1903
- }
1904
-
1905
- .liftoff-stages li {
1906
- position: relative;
1907
- display: flex;
1908
- gap: 12px;
1909
- align-items: flex-start;
1910
- padding: 9px 0;
1911
- }
1912
-
1913
- /* Connecting rail between the dots. */
1914
- .liftoff-stages li + li::before {
1915
- content: "";
1916
- position: absolute;
1917
- left: 10px;
1918
- top: -8px;
1919
- height: 16px;
1920
- width: 2px;
1921
- background: var(--admin-line-strong);
1922
- }
1923
-
1924
- .liftoff-stages li > div {
1925
- display: grid;
1926
- gap: 1px;
1927
- }
1928
-
1929
- .liftoff-stages b {
1930
- font-size: 13px;
1931
- }
1932
-
1933
- .liftoff-stages span:not(.liftoff-dot) {
1934
- font-size: 12px;
1935
- color: var(--admin-muted);
1936
- }
1937
-
1938
- .liftoff-dot {
1939
- flex: none;
1940
- width: 21px;
1941
- height: 21px;
1942
- margin-top: 1px;
1943
- border-radius: 50%;
1944
- border: 2px solid var(--admin-line-strong);
1945
- display: grid;
1946
- place-items: center;
1947
- font-size: 12px;
1948
- font-weight: 700;
1949
- color: #fff;
1950
- background: transparent;
1951
- transition: all 0.25s ease;
1952
- }
1953
-
1954
- .liftoff-stages li[data-state="active"] .liftoff-dot {
1955
- border-color: var(--admin-teal, #0f9d8f);
1956
- box-shadow: 0 0 0 0 color-mix(in srgb, var(--admin-teal, #0f9d8f) 45%, transparent);
1957
- animation: liftoff-pulse 1.3s ease-out infinite;
1958
- }
1959
-
1960
- .liftoff-stages li[data-state="active"] b {
1961
- color: var(--admin-teal, #0f9d8f);
1962
- }
1963
-
1964
- @keyframes liftoff-pulse {
1965
- from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--admin-teal, #0f9d8f) 45%, transparent); }
1966
- to { box-shadow: 0 0 0 10px transparent; }
1967
- }
1968
-
1969
- .liftoff-stages li[data-state="done"] .liftoff-dot {
1970
- border-color: var(--admin-green);
1971
- background: var(--admin-green);
1972
- }
1973
-
1974
- .liftoff-stages li[data-state="done"] .liftoff-dot::after {
1975
- content: "✓";
1976
- }
1977
-
1978
- .liftoff-stages li[data-state="fail"] .liftoff-dot {
1979
- border-color: var(--admin-red);
1980
- background: var(--admin-red);
1981
- }
1982
-
1983
- .liftoff-stages li[data-state="fail"] .liftoff-dot::after {
1984
- content: "✕";
1985
- }
1986
-
1987
- .liftoff-live {
1988
- margin: 8px 0 0;
1989
- min-height: 18px;
1990
- font-size: 13px;
1991
- color: var(--admin-ink);
1992
- }
1993
-
1994
- /* The shared scrolling live-log console — a "CI build log" window reused across
1995
- the build / integrate / rebuild / ship flight dialogs. Fixed height, scrolls,
1996
- newest line auto-scrolled into view by the client (see makeLogConsole). */
1997
- .stream-log {
1998
- margin: 10px 0 0;
1999
- max-height: 150px;
2000
- overflow-y: auto;
2001
- padding: 8px 10px;
2002
- border: 1px solid var(--admin-border, rgba(0, 0, 0, 0.12));
2003
- border-radius: 8px;
2004
- background: var(--admin-code-bg, rgba(0, 0, 0, 0.04));
2005
- font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
2006
- font-size: 12px;
2007
- line-height: 1.5;
2008
- color: var(--admin-ink);
2009
- -webkit-overflow-scrolling: touch;
2010
- }
2011
- .stream-log:empty {
2012
- display: none;
2013
- }
2014
- .stream-log-line {
2015
- white-space: pre-wrap;
2016
- word-break: break-word;
2017
- }
2018
-
2019
- .liftoff-meta {
2020
- display: flex;
2021
- justify-content: space-between;
2022
- align-items: center;
2023
- margin-top: 14px;
2024
- color: var(--admin-muted);
2025
- }
2026
-
2027
- .liftoff-outcome-phase {
2028
- text-align: center;
2029
- padding-top: 6px;
2030
- }
2031
-
2032
- .liftoff-outcome-phase .liftoff-actions {
2033
- justify-content: center;
2034
- }
2035
-
2036
- .liftoff-seal {
2037
- width: 56px;
2038
- height: 56px;
2039
- margin: 4px auto 12px;
2040
- border-radius: 50%;
2041
- display: grid;
2042
- place-items: center;
2043
- font-size: 28px;
2044
- font-weight: 700;
2045
- color: #fff;
2046
- }
2047
-
2048
- .liftoff-seal[data-state="ok"] {
2049
- background: var(--admin-green);
2050
- animation: liftoff-seal-pop 0.45s cubic-bezier(0.2, 1.6, 0.4, 1);
2051
- }
2052
-
2053
- .liftoff-seal[data-state="ok"]::after {
2054
- content: "✓";
2055
- }
2056
-
2057
- .liftoff-seal[data-state="err"] {
2058
- background: var(--admin-red);
2059
- }
2060
-
2061
- .liftoff-seal[data-state="err"]::after {
2062
- content: "✕";
2063
- }
2064
-
2065
- @keyframes liftoff-seal-pop {
2066
- from { transform: scale(0.4); opacity: 0; }
2067
- 60% { transform: scale(1.12); }
2068
- to { transform: none; opacity: 1; }
2069
- }
2070
-
2071
- .liftoff-confetti {
2072
- pointer-events: none;
2073
- position: absolute;
2074
- inset: 0;
2075
- overflow: hidden;
2076
- border-radius: inherit;
2077
- }
2078
-
2079
- .liftoff-confetti i {
2080
- position: absolute;
2081
- top: -12px;
2082
- width: 7px;
2083
- height: 11px;
2084
- border-radius: 2px;
2085
- opacity: 0;
2086
- animation-name: liftoff-confetti-fall;
2087
- animation-timing-function: ease-in;
2088
- animation-fill-mode: forwards;
2089
- }
2090
-
2091
- @keyframes liftoff-confetti-fall {
2092
- 0% { opacity: 1; transform: translate(0, 0) rotate(0deg); }
2093
- 100% {
2094
- opacity: 0.9;
2095
- transform: translate(var(--liftoff-drift, 0px), 660px) rotate(var(--liftoff-spin, 240deg));
2096
- }
2097
- }
2098
-
2099
- @media (prefers-reduced-motion: reduce) {
2100
- .liftoff-card,
2101
- .liftoff-card[data-mode="flight"]::before,
2102
- .liftoff-stages li[data-state="active"] .liftoff-dot,
2103
- .liftoff-seal[data-state="ok"] {
2104
- animation: none;
2105
- }
2106
- }
2107
-
2108
- .domain-connect-controls {
2109
- display: grid;
2110
- gap: 8px;
2111
- justify-items: end;
2112
- min-width: 220px;
2113
- }
2114
-
2115
- .domain-connect-controls input[type="text"] {
2116
- width: 100%;
2117
- min-height: 36px;
2118
- padding: 0 10px;
2119
- border: 1px solid var(--admin-line-strong);
2120
- border-radius: var(--admin-radius-sm);
2121
- background: var(--admin-surface);
2122
- color: var(--admin-ink);
2123
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
2124
- font-size: 12px;
2125
- }
2126
-
2127
- .domain-rehearsal {
2128
- display: inline-flex;
2129
- align-items: center;
2130
- gap: 6px;
2131
- color: var(--admin-muted);
2132
- font-size: 12px;
2133
- }
2134
-
2135
- @media (max-width: 720px) {
2136
- .domain-connect-controls {
2137
- justify-items: stretch;
2138
- min-width: 0;
2139
- width: 100%;
2140
- }
2141
- }
2142
-
2143
- /* Apex-cutover readiness itemization */
2144
- .domain-readiness {
2145
- margin: 0.75rem 0;
2146
- padding: 0.75rem 1rem;
2147
- border: 1px solid var(--hairline, rgba(0, 0, 0, 0.12));
2148
- border-radius: 0.5rem;
2149
- }
2150
- .domain-readiness-head {
2151
- display: flex;
2152
- align-items: center;
2153
- gap: 0.5rem;
2154
- margin-bottom: 0.5rem;
2155
- }
2156
- .domain-readiness-list {
2157
- list-style: none;
2158
- margin: 0;
2159
- padding: 0;
2160
- display: grid;
2161
- gap: 0.25rem;
2162
- }
2163
- .domain-readiness-list li {
2164
- display: grid;
2165
- gap: 0.1rem;
2166
- font-size: 0.9em;
2167
- }
2168
- .domain-readiness-item-head {
2169
- display: flex;
2170
- align-items: baseline;
2171
- gap: 0.4rem;
2172
- }
2173
- .domain-readiness-mark {
2174
- font-weight: 700;
2175
- width: 1em;
2176
- flex: 0 0 auto;
2177
- }
2178
- .domain-readiness-list li.ok .domain-readiness-mark {
2179
- color: var(--good, #1a7f37);
2180
- }
2181
- .domain-readiness-list li.blocked .domain-readiness-mark {
2182
- color: var(--bad, #b42318);
2183
- }
2184
- /* detail + remedy sit on their OWN lines, indented under the label (past the
2185
- mark) so neither runs together with the bold label above it. */
2186
- .domain-readiness-detail,
2187
- .domain-readiness-remedy {
2188
- margin: 0;
2189
- padding-left: 1.4em;
2190
- }
2191
- .domain-readiness-detail {
2192
- opacity: 0.75;
2193
- }
2194
- .domain-readiness-remedy {
2195
- color: var(--accent, #0b6bcb);
2196
- opacity: 0.95;
2197
- }
2198
-
2199
- /* Aggregate card + ship plan */
2200
- .aggregate-prs {
2201
- display: grid;
2202
- gap: 6px;
2203
- }
2204
-
2205
- .aggregate-prs-list {
2206
- list-style: none;
2207
- margin: 0;
2208
- padding: 0;
2209
- display: grid;
2210
- gap: 4px;
2211
- }
2212
-
2213
- .aggregate-prs-list li {
2214
- display: flex;
2215
- align-items: center;
2216
- gap: 8px;
2217
- font-size: 13px;
2218
- }
2219
-
2220
- /* Provenance ("integrated <age> by <email>") sits after the sha, subtle. */
2221
- .aggregate-pr-meta {
2222
- font-size: 12px;
2223
- }
2224
-
2225
- /* The per-change Undo (revert) button is pushed to the row's trailing edge
2226
- and kept compact so the batch reads as a list, not a stack of buttons. */
2227
- .aggregate-prs-list .btn-compact {
2228
- margin-left: auto;
2229
- padding: 2px 10px;
2230
- font-size: 12px;
2231
- }
2232
-
2233
- .aggregate-plan {
2234
- padding: 0.75rem 1rem;
2235
- border: 1px solid var(--hairline, rgba(0, 0, 0, 0.12));
2236
- border-radius: 0.5rem;
2237
- display: grid;
2238
- gap: 8px;
2239
- }
2240
-
2241
- .aggregate-plan-head {
2242
- display: flex;
2243
- align-items: center;
2244
- gap: 0.5rem;
2245
- }
2246
-
2247
- .aggregate-plan-grid {
2248
- display: grid;
2249
- grid-template-columns: max-content 1fr;
2250
- gap: 4px 16px;
2251
- margin: 0;
2252
- }
2253
-
2254
- .aggregate-plan-grid dt {
2255
- color: var(--admin-muted);
2256
- font-size: 12px;
2257
- }
2258
-
2259
- .aggregate-plan-grid dd {
2260
- margin: 0;
2261
- font-size: 13px;
2262
- overflow-wrap: anywhere;
2263
- }
2264
-
2265
- /* ── Preview-aggregate console — the go-live surface ─────────────────────
2266
- Everything here is scoped under [data-publish-aggregate] / .agg-* so it
2267
- never leaks into the other panels (candidate queue, versions, branches)
2268
- that share .panel / .field / .check-row / .chip. */
2269
- .agg-console .panel-body {
2270
- gap: 16px;
2271
- }
2272
-
2273
- [data-publish-aggregate] .sha-token {
2274
- display: inline-flex;
2275
- align-items: center;
2276
- padding: 2px 8px;
2277
- border: 1px solid var(--admin-line-strong);
2278
- border-radius: 5px;
2279
- background: var(--admin-surface-soft);
2280
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
2281
- font-size: 12.5px;
2282
- color: var(--admin-ink);
2283
- }
2284
- [data-publish-aggregate] .sha-token.ghost {
2285
- background: transparent;
2286
- color: var(--admin-muted);
2287
- border-style: dashed;
2288
- }
2289
-
2290
- /* AI-assembled release summary */
2291
- .agg-summary {
2292
- display: grid;
2293
- gap: 11px;
2294
- padding: 15px 16px;
2295
- border: 1px solid var(--admin-line);
2296
- border-radius: var(--admin-radius);
2297
- background:
2298
- linear-gradient(180deg, var(--admin-teal-soft), transparent 62%),
2299
- var(--admin-surface);
2300
- }
2301
- .agg-summary[hidden] {
2302
- display: none;
2303
- }
2304
- .agg-summary-head {
2305
- display: flex;
2306
- align-items: center;
2307
- gap: 10px;
2308
- }
2309
- .agg-summary-glyph {
2310
- flex: none;
2311
- width: 26px;
2312
- height: 26px;
2313
- display: grid;
2314
- place-items: center;
2315
- border-radius: 7px;
2316
- background: var(--admin-surface);
2317
- color: var(--admin-teal-dark);
2318
- border: 1px solid color-mix(in srgb, var(--admin-teal) 30%, transparent);
2319
- }
2320
- /* The sparkle glyphs are filled shapes; opt out of the global stroked-svg rule. */
2321
- .agg-summary-glyph svg,
2322
- .agg-ai-chip svg {
2323
- fill: currentColor;
2324
- stroke: none;
2325
- }
2326
- .agg-summary-glyph svg {
2327
- width: 15px;
2328
- height: 15px;
2329
- }
2330
- .agg-summary-label {
2331
- flex: 1;
2332
- font-size: 11px;
2333
- font-weight: 760;
2334
- letter-spacing: 0.08em;
2335
- text-transform: uppercase;
2336
- color: var(--admin-muted);
2337
- }
2338
- .agg-ai-chip {
2339
- display: inline-flex;
2340
- align-items: center;
2341
- gap: 5px;
2342
- padding: 3px 9px;
2343
- border-radius: 999px;
2344
- background: var(--admin-teal-soft);
2345
- color: #007973;
2346
- font-size: 10px;
2347
- font-weight: 760;
2348
- letter-spacing: 0.06em;
2349
- text-transform: uppercase;
2350
- }
2351
- .agg-ai-chip svg {
2352
- width: 10px;
2353
- height: 10px;
2354
- }
2355
- .agg-summary-lead {
2356
- margin: 0;
2357
- font-size: 15px;
2358
- line-height: 1.55;
2359
- color: var(--admin-ink);
2360
- max-width: 68ch;
2361
- }
2362
- .agg-summary-points {
2363
- list-style: none;
2364
- margin: 0;
2365
- padding: 0;
2366
- display: grid;
2367
- gap: 7px;
2368
- }
2369
- .agg-summary-points[hidden] {
2370
- display: none;
2371
- }
2372
- .agg-summary-points li {
2373
- display: flex;
2374
- align-items: flex-start;
2375
- gap: 9px;
2376
- font-size: 13px;
2377
- color: var(--admin-muted);
2378
- line-height: 1.45;
2379
- }
2380
- .agg-summary-points .agg-tick {
2381
- flex: none;
2382
- margin-top: 1px;
2383
- width: 16px;
2384
- height: 16px;
2385
- border-radius: 50%;
2386
- display: grid;
2387
- place-items: center;
2388
- background: var(--admin-green-soft);
2389
- color: var(--admin-green);
2390
- }
2391
- .agg-summary-points .agg-tick svg {
2392
- width: 10px;
2393
- height: 10px;
2394
- }
2395
- .agg-summary-points b {
2396
- color: var(--admin-ink);
2397
- font-weight: 640;
2398
- }
2399
-
2400
- /* Chain of custody: build → evidence → digest */
2401
- .agg-chain {
2402
- display: grid;
2403
- grid-template-columns: 1fr auto 1fr auto 1fr;
2404
- align-items: stretch;
2405
- padding: 16px;
2406
- border: 1px solid var(--admin-line);
2407
- border-radius: var(--admin-radius);
2408
- background: var(--admin-surface-soft);
2409
- }
2410
- .agg-stop {
2411
- display: flex;
2412
- flex-direction: column;
2413
- gap: 7px;
2414
- padding: 2px 12px;
2415
- min-width: 0;
2416
- }
2417
- .agg-stop:first-child {
2418
- padding-left: 2px;
2419
- }
2420
- .agg-stop:last-child {
2421
- padding-right: 2px;
2422
- }
2423
- .agg-stop-label {
2424
- font-size: 10.5px;
2425
- font-weight: 760;
2426
- letter-spacing: 0.08em;
2427
- text-transform: uppercase;
2428
- color: var(--admin-muted);
2429
- }
2430
- .agg-stop-val {
2431
- display: flex;
2432
- align-items: center;
2433
- gap: 8px;
2434
- font-size: 15px;
2435
- font-weight: 680;
2436
- }
2437
- .agg-stop-val strong {
2438
- font-size: 15px;
2439
- }
2440
- .agg-stop-cap {
2441
- margin: 0;
2442
- font-size: 12px;
2443
- color: var(--admin-muted);
2444
- line-height: 1.45;
2445
- }
2446
- .agg-stop-cap a {
2447
- white-space: nowrap;
2448
- }
2449
- .agg-digest {
2450
- padding: 2px 8px;
2451
- border: 1px solid color-mix(in srgb, var(--admin-teal) 55%, var(--admin-line-strong));
2452
- border-radius: 5px;
2453
- background: var(--admin-teal-soft);
2454
- color: #007973;
2455
- }
2456
- [data-publish-aggregate] [data-aggregate-evidence][data-state="passed"] {
2457
- color: var(--admin-green);
2458
- }
2459
- [data-publish-aggregate] [data-aggregate-evidence][data-state="failed"] {
2460
- color: var(--admin-red);
2461
- }
2462
- .agg-link {
2463
- display: flex;
2464
- flex-direction: column;
2465
- align-items: center;
2466
- justify-content: center;
2467
- gap: 6px;
2468
- padding: 0 2px;
2469
- align-self: center;
2470
- }
2471
- .agg-flowlabel {
2472
- font-size: 9px;
2473
- font-weight: 720;
2474
- letter-spacing: 0.06em;
2475
- text-transform: uppercase;
2476
- color: var(--admin-muted-2);
2477
- }
2478
- .agg-arrow {
2479
- display: grid;
2480
- place-items: center;
2481
- color: var(--admin-teal);
2482
- }
2483
- .agg-arrow svg {
2484
- width: 32px;
2485
- height: 10px;
2486
- }
2487
-
2488
- /* Staged → live delta strip */
2489
- .agg-delta-strip {
2490
- display: flex;
2491
- align-items: center;
2492
- gap: 12px;
2493
- flex-wrap: wrap;
2494
- padding: 10px 13px;
2495
- border: 1px solid var(--admin-line);
2496
- border-radius: var(--admin-radius-sm);
2497
- background: var(--admin-surface-soft);
2498
- }
2499
- .agg-delta-strip[hidden] {
2500
- display: none;
2501
- }
2502
- .agg-delta-node {
2503
- display: flex;
2504
- flex-direction: column;
2505
- gap: 3px;
2506
- }
2507
- .agg-delta-mid {
2508
- display: flex;
2509
- align-items: center;
2510
- gap: 8px;
2511
- color: var(--admin-teal);
2512
- }
2513
- .agg-delta-mid svg {
2514
- width: 24px;
2515
- height: 10px;
2516
- }
2517
- .agg-delta-count {
2518
- padding: 2px 9px;
2519
- border-radius: 999px;
2520
- background: var(--admin-teal-soft);
2521
- color: #007973;
2522
- font-size: 11.5px;
2523
- font-weight: 720;
2524
- }
2525
-
2526
- /* Publish plan → launch manifest */
2527
- [data-publish-aggregate] .agg-manifest {
2528
- display: block;
2529
- padding: 0;
2530
- overflow: hidden;
2531
- background:
2532
- linear-gradient(180deg, var(--admin-surface-tint), transparent 46px),
2533
- var(--admin-surface);
2534
- }
2535
- .agg-manifest-head {
2536
- padding: 12px 15px;
2537
- border-bottom: 1px dashed var(--admin-line-strong);
2538
- }
2539
- .agg-manifest-head b {
2540
- display: flex;
2541
- align-items: center;
2542
- gap: 8px;
2543
- font-size: 13px;
2544
- }
2545
- .agg-manifest-head b svg {
2546
- width: 15px;
2547
- height: 15px;
2548
- color: var(--admin-teal);
2549
- }
2550
- [data-publish-aggregate] .agg-manifest .aggregate-plan-grid {
2551
- padding: 8px 15px 12px;
2552
- }
2553
- [data-publish-aggregate] .agg-manifest > .note {
2554
- margin: 0;
2555
- padding: 11px 15px;
2556
- border-top: 1px dashed var(--admin-line-strong);
2557
- background: var(--admin-surface-soft);
2558
- }
2559
-
2560
- /* Openable "Included changes" — reused by the Published Versions "What
2561
- shipped" per-version disclosure (.ver-changes below); the staged-delta
2562
- table above no longer uses the <details> wrap itself, only the row/diff
2563
- classes it shares with changeSummaryItem. */
2564
- .agg-included-details > summary {
2565
- list-style: none;
2566
- cursor: pointer;
2567
- display: flex;
2568
- align-items: center;
2569
- gap: 10px;
2570
- padding: 11px 15px;
2571
- font-size: 12px;
2572
- color: var(--admin-muted);
2573
- }
2574
- .agg-included-details > summary::-webkit-details-marker {
2575
- display: none;
2576
- }
2577
- .agg-included-details > summary::marker {
2578
- content: "";
2579
- }
2580
- .agg-included-details > summary:focus-visible {
2581
- outline: 2px solid var(--admin-teal);
2582
- outline-offset: -2px;
2583
- }
2584
- .agg-included-lead {
2585
- font-size: 11px;
2586
- font-weight: 740;
2587
- letter-spacing: 0.03em;
2588
- text-transform: uppercase;
2589
- color: var(--admin-ink);
2590
- }
2591
- .agg-included-badge {
2592
- padding: 1px 8px;
2593
- border-radius: 999px;
2594
- background: var(--admin-surface-tint);
2595
- color: #3f4d49;
2596
- font-size: 11.5px;
2597
- font-weight: 720;
2598
- }
2599
- .agg-chev {
2600
- margin-left: auto;
2601
- display: inline-flex;
2602
- color: var(--admin-muted-2);
2603
- transition: transform 0.18s ease;
2604
- }
2605
- .agg-chev svg {
2606
- width: 14px;
2607
- height: 14px;
2608
- }
2609
- .agg-included-details[open] > summary .agg-chev {
2610
- transform: rotate(90deg);
2611
- }
2612
- @media (prefers-reduced-motion: reduce) {
2613
- .agg-chev {
2614
- transition: none;
2615
- }
2616
- }
2617
- .agg-included-list {
2618
- list-style: none;
2619
- margin: 0;
2620
- padding: 0 15px 14px;
2621
- display: grid;
2622
- gap: 8px;
2623
- }
2624
- .agg-included-item {
2625
- display: grid;
2626
- gap: 5px;
2627
- padding: 11px 12px;
2628
- border: 1px solid var(--admin-line);
2629
- border-radius: var(--admin-radius-sm);
2630
- background: var(--admin-surface-soft);
2631
- }
2632
- .agg-included-top {
2633
- display: flex;
2634
- align-items: center;
2635
- gap: 10px;
2636
- flex-wrap: wrap;
2637
- }
2638
- .agg-included-pr {
2639
- font-weight: 720;
2640
- font-size: 13px;
2641
- }
2642
- .agg-included-title {
2643
- font-weight: 600;
2644
- font-size: 13.5px;
2645
- }
2646
- .agg-included-desc {
2647
- margin: 0;
2648
- color: var(--admin-muted);
2649
- font-size: 12.5px;
2650
- line-height: 1.5;
2651
- }
2652
- .agg-included-meta {
2653
- display: flex;
2654
- align-items: center;
2655
- gap: 6px;
2656
- flex-wrap: wrap;
2657
- color: var(--admin-muted-2);
2658
- font-size: 11.5px;
2659
- }
2660
- .agg-included-meta > span + span::before {
2661
- content: "·";
2662
- padding-right: 6px;
2663
- color: var(--admin-line-strong);
2664
- }
2665
- .agg-included-meta .sha-token {
2666
- font-size: 11px;
2667
- padding: 1px 6px;
2668
- }
2669
-
2670
- /* Published-versions "what shipped": the release summary lead + openable
2671
- per-change list, inline in the Version cell. Reuses the .agg-included-*
2672
- item markup; only the container chrome is retuned for a table cell. */
2673
- .ver-summary {
2674
- margin: 6px 0 0;
2675
- color: var(--admin-muted);
2676
- font-size: 12.5px;
2677
- line-height: 1.5;
2678
- max-width: 48ch;
2679
- }
2680
- .ver-changes {
2681
- margin-top: 8px;
2682
- border: 1px solid var(--admin-line);
2683
- border-radius: var(--admin-radius-sm);
2684
- background: var(--admin-surface-soft);
2685
- }
2686
- .ver-changes > summary {
2687
- padding: 8px 10px;
2688
- }
2689
- .ver-changes .agg-included-list {
2690
- padding: 0 10px 10px;
2691
- }
2692
-
2693
- /* Branch-retention "what this branch changes": the PR title + one-line
2694
- description + scope meta, inline under the branch ref. */
2695
- .branch-pr-title {
2696
- margin: 6px 0 0;
2697
- font-weight: 600;
2698
- font-size: 13px;
2699
- color: var(--admin-ink);
2700
- max-width: 46ch;
2701
- }
2702
- .branch-pr-desc {
2703
- margin: 3px 0 0;
2704
- color: var(--admin-muted);
2705
- font-size: 12.5px;
2706
- line-height: 1.5;
2707
- max-width: 46ch;
2708
- }
2709
- .branch-pr-meta {
2710
- margin-top: 5px;
2711
- }
2712
- .branch-pr-ai {
2713
- color: var(--admin-teal-dark);
2714
- font-weight: 640;
2715
- }
2716
-
2717
- /* Scope-of-change: lines added/removed (green/red), + the top-level readout */
2718
- .agg-lines {
2719
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
2720
- font-variant-numeric: tabular-nums;
2721
- white-space: nowrap;
2722
- }
2723
- .agg-add {
2724
- color: var(--admin-green);
2725
- }
2726
- .agg-del {
2727
- color: var(--admin-red);
2728
- }
2729
-
2730
- /* "View diff" disclosure for a trivial change (the diff IS its summary). */
2731
- .agg-diff {
2732
- margin-top: 2px;
2733
- }
2734
- .agg-diff > summary {
2735
- cursor: pointer;
2736
- font-size: 11.5px;
2737
- font-weight: 640;
2738
- color: var(--admin-teal-dark);
2739
- width: max-content;
2740
- }
2741
- .agg-diff[open] > summary {
2742
- margin-bottom: 6px;
2743
- }
2744
- .agg-diff-file {
2745
- margin-top: 6px;
2746
- }
2747
- .agg-diff-path {
2748
- font-size: 11px;
2749
- color: var(--admin-muted);
2750
- margin-bottom: 3px;
2751
- }
2752
- .agg-diff-patch {
2753
- margin: 0;
2754
- padding: 8px 10px;
2755
- border: 1px solid var(--admin-line);
2756
- border-radius: var(--admin-radius-sm);
2757
- background: var(--admin-surface);
2758
- font-size: 11.5px;
2759
- line-height: 1.5;
2760
- overflow-x: auto;
2761
- white-space: pre;
2762
- }
2763
- .agg-diff-add {
2764
- color: var(--admin-green);
2765
- background: var(--admin-green-soft);
2766
- }
2767
- .agg-diff-del {
2768
- color: var(--admin-red);
2769
- background: var(--admin-red-soft);
2770
- }
2771
- .agg-diff-hunk {
2772
- color: var(--admin-muted-2);
2773
- }
2774
-
2775
- .agg-staged-head {
2776
- display: flex;
2777
- align-items: baseline;
2778
- justify-content: space-between;
2779
- gap: 12px;
2780
- flex-wrap: wrap;
2781
- }
2782
- .agg-scope {
2783
- display: inline-flex;
2784
- align-items: center;
2785
- gap: 8px;
2786
- padding: 3px 10px;
2787
- border: 1px solid var(--admin-line);
2788
- border-radius: 999px;
2789
- background: var(--admin-surface-soft);
2790
- font-size: 11.5px;
2791
- color: var(--admin-ink);
2792
- }
2793
- .agg-scope[hidden] {
2794
- display: none;
2795
- }
2796
- .agg-scope > span + span::before {
2797
- content: "·";
2798
- padding-right: 8px;
2799
- color: var(--admin-line-strong);
2800
- }
2801
- .agg-scope-label {
2802
- font-size: 10px;
2803
- font-weight: 760;
2804
- letter-spacing: 0.08em;
2805
- text-transform: uppercase;
2806
- color: var(--admin-muted);
2807
- }
2808
- .agg-scope-partial {
2809
- color: var(--admin-muted-2);
2810
- font-style: italic;
2811
- cursor: help;
2812
- }
2813
-
2814
- /* Publish action — stack the copy above a full-width, weighted CTA */
2815
- [data-publish-aggregate] .agg-launch {
2816
- display: grid;
2817
- grid-template-columns: 1fr;
2818
- gap: 12px;
2819
- padding: 14px 0 2px;
2820
- }
2821
- /* Extra selector weight so the gradient/shadow beat the .btn.primary base. */
2822
- [data-publish-aggregate] .btn.agg-publish-btn {
2823
- width: 100%;
2824
- justify-content: center;
2825
- min-height: 50px;
2826
- gap: 9px;
2827
- font-size: 15px;
2828
- border-radius: var(--admin-radius-sm);
2829
- background: linear-gradient(178deg, var(--admin-teal), var(--admin-teal-dark));
2830
- box-shadow: 0 8px 22px color-mix(in srgb, var(--admin-teal) 26%, transparent);
2831
- }
2832
- .agg-publish-btn svg {
2833
- width: 18px;
2834
- height: 18px;
2835
- }
2836
- [data-publish-aggregate] .btn.agg-publish-btn:not(:disabled):hover {
2837
- filter: brightness(1.05);
2838
- }
2839
- [data-publish-aggregate] .btn.agg-publish-btn:disabled {
2840
- background: var(--admin-surface-tint);
2841
- border-color: var(--admin-line-strong);
2842
- color: var(--admin-muted);
2843
- box-shadow: none;
2844
- cursor: not-allowed;
2845
- }
2846
-
2847
- @media (max-width: 760px) {
2848
- .agg-chain {
2849
- grid-template-columns: 1fr;
2850
- gap: 4px;
2851
- }
2852
- .agg-link {
2853
- flex-direction: row;
2854
- gap: 10px;
2855
- padding: 4px 12px;
2856
- }
2857
- .agg-link .agg-arrow svg {
2858
- transform: rotate(90deg);
2859
- }
2860
- .agg-stop {
2861
- padding: 8px 2px;
2862
- }
2863
- }
2864
-
2865
- /* Versions + diff */
2866
- .versions-table {
2867
- min-width: 780px;
2868
- }
2869
-
2870
- /* Unified review pane */
2871
- .publish-review-cell {
2872
- padding: 12px 14px;
2873
- background: var(--admin-surface-soft);
2874
- }
2875
-
2876
- .review-pane {
2877
- display: grid;
2878
- grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
2879
- gap: 16px;
2880
- align-items: start;
2881
- }
2882
-
2883
- @media (max-width: 900px) {
2884
- .review-pane {
2885
- grid-template-columns: 1fr;
2886
- }
2887
- }
2888
-
2889
- .review-preview {
2890
- display: grid;
2891
- gap: 8px;
2892
- min-width: 0;
2893
- }
2894
-
2895
- .review-pane-head {
2896
- display: flex;
2897
- align-items: center;
2898
- justify-content: space-between;
2899
- gap: 10px;
2900
- }
2901
-
2902
- .review-frame {
2903
- width: 100%;
2904
- height: 420px;
2905
- border: 1px solid var(--admin-line-strong);
2906
- border-radius: var(--admin-radius-sm);
2907
- background: var(--admin-surface);
2908
- }
2909
-
2910
- .review-detail {
2911
- display: grid;
2912
- gap: 14px;
2913
- min-width: 0;
2914
- }
2915
-
2916
- .review-section {
2917
- display: grid;
2918
- gap: 6px;
2919
- }
2920
-
2921
- .review-chips {
2922
- display: flex;
2923
- flex-wrap: wrap;
2924
- gap: 6px;
2925
- }
2926
-
2927
- .review-actions {
2928
- display: flex;
2929
- flex-wrap: wrap;
2930
- gap: 8px;
2931
- }
2932
-
2933
- .publish-diff-head {
2934
- display: flex;
2935
- flex-wrap: wrap;
2936
- align-items: center;
2937
- gap: 10px;
2938
- margin-bottom: 8px;
2939
- }
2940
-
2941
- .publish-diff-blocked {
2942
- color: var(--admin-amber);
2943
- }
2944
-
2945
- .publish-diff-list {
2946
- list-style: none;
2947
- margin: 0;
2948
- padding: 0;
2949
- display: grid;
2950
- gap: 3px;
2951
- }
2952
-
2953
- .publish-diff-list li {
2954
- display: flex;
2955
- align-items: baseline;
2956
- gap: 8px;
2957
- font-size: 12px;
2958
- }
2959
-
2960
- .publish-diff-mark {
2961
- width: 1em;
2962
- font-weight: 700;
2963
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
2964
- }
2965
-
2966
- .publish-diff-added .publish-diff-mark {
2967
- color: var(--admin-green);
2968
- }
2969
-
2970
- .publish-diff-changed .publish-diff-mark {
2971
- color: var(--admin-blue);
2972
- }
2973
-
2974
- .publish-diff-removed .publish-diff-mark {
2975
- color: var(--admin-red);
2976
- }
2977
-
2978
- .publish-diff-list code {
2979
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
2980
- font-size: 12px;
2981
- overflow-wrap: anywhere;
2982
- }
2983
- </style>