@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,420 +0,0 @@
1
- ---
2
- /**
3
- * `/admin/ops-timeline` — the operational-activity timeline VIEW (card D5).
4
- *
5
- * The READ surface of the actor×action telemetry spine: a chronological,
6
- * tenant-scoped list of lifecycle activity answering "who shipped/rejected what,
7
- * when, why — and what happened to my change/deploy". It reads the D1
8
- * `activity_events` timeline cohort (DECISIONS.md §8) through the pure,
9
- * tenant-scoped query module (`lib/activity/query.ts`).
10
- *
11
- * ROUTING / AUTH — mirrors `admin.astro`: middleware resolves the tenant, injects
12
- * CSP/security headers, and already held anonymous/non-member visitors at the
13
- * sign-in gate. This surface exposes operational detail, so it renders the data
14
- * ONLY for a viewer with owner/admin standing for the routed tenant, or a ToT-staff
15
- * session (`resolveAdminEntry` / `resolveAdminTenantMode`, the same machinery the
16
- * admin shell uses). A signed-in non-owner sees the chrome + a "no standing" notice,
17
- * never another tenant's rows.
18
- *
19
- * TENANT ISOLATION — the query is ALWAYS scoped to `Astro.locals.tenant.tenant_id`
20
- * (the served tenant, never a URL value); `parseTimelineQuery` pins that tenant and
21
- * ignores any `tenant`/`tenantId` query param. See `query.isolation.test.ts` for the
22
- * hostile read-side proof.
23
- *
24
- * DEPLOY SKEW (card D2) — the page surfaces `deriveDeploySkew()`: a banner when the
25
- * build serving this page (or the timeline) shows more than one live version, plus a
26
- * per-row "stale build" chip on superseded `deploy.version_observed` rows.
27
- *
28
- * ALERTS (card D7) — the page also runs `checkActivityAlerts()` (silent-terminal,
29
- * recurring preview.serve miss_no_env, reconcile-hang watchdog, and — promoting the
30
- * skew signal above — a persistent deploy-skew alert) and renders an alerts banner
31
- * above the deploy-skew visibility banner. Same on-demand posture: computed fresh on
32
- * every page load, no cron. `GET /api/admin/activity-alerts` exposes the identical
33
- * check for an external poller.
34
- */
35
- export const prerender = false;
36
-
37
- import { readViewerSession } from "@/lib/auth/route";
38
- import { resolveAdminEntry } from "@/lib/auth/adminEntry";
39
- import { resolveAdminTenantMode } from "@/lib/adminTenantEnvelope";
40
- import { readD1 } from "@/lib/env";
41
- import { fromD1 } from "@/lib/d1/catalog";
42
- import {
43
- parseTimelineQuery,
44
- loadOpsTimeline,
45
- type OpsTimelineData,
46
- type ActivityTimelineRow,
47
- } from "@/lib/activity/query";
48
- import { checkActivityAlerts, type ActivityAlert } from "@/lib/activity/alerts";
49
-
50
- const { tenant, basePath, cspNonce } = Astro.locals;
51
- const adminPath = `${basePath || ""}/admin`;
52
- const timelinePath = `${adminPath}/ops-timeline`;
53
- const tenantLabel = tenant.displayName || tenant.appDomain || tenant.tenant_id;
54
- const nowSeconds = Math.floor(Date.now() / 1000);
55
-
56
- const viewerSession = await readViewerSession(Astro);
57
- const viewerEmail = (Astro.locals.viewer?.email ?? viewerSession?.email ?? "").toLowerCase();
58
-
59
- const adminEntry = resolveAdminEntry({
60
- record: viewerSession,
61
- hostResource: tenant.appDomain,
62
- nowSeconds,
63
- });
64
- const { isStaff } = resolveAdminTenantMode({
65
- session: viewerSession,
66
- adminAppTenant: tenant.appDomain,
67
- requestedTarget: null,
68
- nowSeconds,
69
- });
70
-
71
- // Gate: a signed-in owner/admin of THIS tenant, or ToT staff. Everyone else gets
72
- // chrome + a notice, and the query never runs.
73
- const authorized = Boolean(viewerSession) && (adminEntry.isOwnerViewer || isStaff);
74
-
75
- // The query is scoped to the SERVED tenant's id — never a URL value.
76
- const query = parseTimelineQuery(Astro.url.searchParams, tenant.tenant_id);
77
-
78
- let data: OpsTimelineData | null = null;
79
- let alerts: ActivityAlert[] = [];
80
- let dbAvailable = true;
81
- if (authorized) {
82
- const d1 = await readD1("STOREFRONT_APPS_DB");
83
- if (d1) {
84
- const db = fromD1(d1);
85
- [data, alerts] = await Promise.all([
86
- loadOpsTimeline(db, query),
87
- checkActivityAlerts(db, tenant.tenant_id),
88
- ]);
89
- } else {
90
- // Dev/Node or an env without the binding — the timeline lives only in D1.
91
- dbAvailable = false;
92
- }
93
- }
94
-
95
- const rows = data?.rows ?? [];
96
- const skew = data?.deploySkew ?? null;
97
- // Versions superseded by a newer observation → a "stale build" chip on those rows.
98
- const staleVersions = new Set(
99
- (skew?.observedVersions ?? [])
100
- .filter((v) => v.version !== skew?.latestObservedVersion)
101
- .map((v) => v.version),
102
- );
103
-
104
- /** Short, non-PII actor label (the id is already an opaque pseudonym). */
105
- function shortActor(row: ActivityTimelineRow): string {
106
- const id = row.actorId.length > 14 ? `${row.actorId.slice(0, 14)}…` : row.actorId;
107
- return `${row.actorKind}:${id}`;
108
- }
109
-
110
- /** Map a status to a chip color class. */
111
- function statusClass(status: string): string {
112
- switch (status) {
113
- case "succeeded":
114
- return "green";
115
- case "failed":
116
- return "red";
117
- case "refused":
118
- return "amber";
119
- default:
120
- return "blue"; // invoked
121
- }
122
- }
123
-
124
- /** Compact, human detail line from the (already redacted) payload. */
125
- function detailFor(row: ActivityTimelineRow): string {
126
- const parts: string[] = [];
127
- const rendered = row.payload.rendered ?? {};
128
- for (const [k, v] of Object.entries(rendered)) parts.push(`${k}: ${v}`);
129
- const args = row.payload.args ?? {};
130
- for (const [k, v] of Object.entries(args)) {
131
- if (k === "changeId") continue; // shown in its own column
132
- parts.push(`${k}=${String(v)}`);
133
- }
134
- return parts.join(" · ");
135
- }
136
-
137
- /** The version an event refers to (deploy.version_observed rows carry it). */
138
- function versionOf(row: ActivityTimelineRow): string | undefined {
139
- const v = row.payload.args?.version;
140
- return typeof v === "string" ? v : undefined;
141
- }
142
-
143
- const statusOptions = ["", "invoked", "succeeded", "failed", "refused"];
144
- ---
145
-
146
- <!doctype html>
147
- <html lang="en">
148
- <head>
149
- <meta charset="utf-8" />
150
- <meta name="viewport" content="width=device-width, initial-scale=1" />
151
- <meta name="robots" content="noindex, nofollow" />
152
- <title>{tenantLabel} · Ops Timeline</title>
153
- <style is:global nonce={cspNonce}>
154
- :root {
155
- color-scheme: light;
156
- --bg: #f5f7f6;
157
- --surface: #ffffff;
158
- --surface-soft: #f9fbfa;
159
- --ink: #16211f;
160
- --muted: #64716d;
161
- --line: #dce5e2;
162
- --teal: #007f79;
163
- --green-soft: #e3f5eb;
164
- --green: #137a55;
165
- --red-soft: #fde7e4;
166
- --red: #b42318;
167
- --amber-soft: #fff0d5;
168
- --amber: #a45d00;
169
- --blue-soft: #e7efff;
170
- --blue: #2458c7;
171
- --radius: 8px;
172
- --font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
173
- --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
174
- }
175
- * { box-sizing: border-box; }
176
- body { margin: 0; background: var(--bg); color: var(--ink); font: 14px/1.5 var(--font); }
177
- a { color: var(--teal); }
178
- .wrap { max-width: 1180px; margin: 0 auto; padding: 24px 20px 64px; }
179
- header.page { margin-bottom: 18px; }
180
- header.page h1 { margin: 0; font-size: 22px; font-weight: 760; }
181
- header.page p { margin: 4px 0 0; color: var(--muted); font-size: 13px; }
182
- header.page code { font-family: var(--mono); }
183
- .notice {
184
- padding: 14px 16px; border: 1px solid var(--amber); border-radius: var(--radius);
185
- background: var(--amber-soft); color: #5c3600; margin-bottom: 18px; font-size: 13px;
186
- }
187
- .skew {
188
- display: flex; align-items: flex-start; gap: 12px;
189
- padding: 12px 14px; border: 1px solid #f1c883; border-radius: var(--radius);
190
- background: #fff6e7; margin-bottom: 18px; font-size: 13px;
191
- }
192
- .skew strong { display: block; }
193
- .skew .versions { color: var(--muted); font-size: 12px; margin-top: 3px; }
194
- .skew code { font-family: var(--mono); }
195
- .alerts { display: grid; gap: 8px; margin-bottom: 18px; }
196
- .alert {
197
- display: flex; align-items: flex-start; gap: 12px;
198
- padding: 12px 14px; border-radius: var(--radius); font-size: 13px;
199
- }
200
- .alert strong { display: block; }
201
- .alert .meta { color: inherit; opacity: 0.75; font-size: 12px; margin-top: 3px; }
202
- .alert.page, .alert.critical {
203
- border: 1px solid var(--red); background: var(--red-soft); color: var(--red);
204
- }
205
- .alert.warning {
206
- border: 1px solid #f1c883; background: #fff6e7; color: #7a4a00;
207
- }
208
- .alert.info {
209
- border: 1px solid var(--line); background: var(--surface-soft); color: var(--muted);
210
- }
211
- form.filters {
212
- display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
213
- gap: 10px; align-items: end;
214
- padding: 14px; border: 1px solid var(--line); border-radius: var(--radius);
215
- background: var(--surface); margin-bottom: 18px;
216
- }
217
- form.filters .field { display: grid; gap: 4px; min-width: 0; }
218
- form.filters label { color: var(--muted); font-size: 11px; font-weight: 760; text-transform: uppercase; }
219
- form.filters input, form.filters select {
220
- min-height: 36px; padding: 0 9px; font: inherit;
221
- border: 1px solid var(--line); border-radius: 6px; background: #fcfdfc; color: var(--ink);
222
- }
223
- form.filters .actions { display: flex; gap: 8px; }
224
- .btn {
225
- min-height: 36px; display: inline-flex; align-items: center; padding: 0 14px;
226
- border: 1px solid var(--teal); border-radius: 6px; background: var(--teal); color: #fff;
227
- font-weight: 680; text-decoration: none; cursor: pointer;
228
- }
229
- .btn.ghost { background: var(--surface); color: var(--ink); border-color: var(--line); }
230
- .table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
231
- table { width: 100%; min-width: 920px; border-collapse: collapse; font-size: 13px; }
232
- th {
233
- height: 38px; padding: 0 12px; border-bottom: 1px solid var(--line);
234
- background: var(--surface-soft); color: var(--muted); font-size: 11px;
235
- font-weight: 760; text-align: left; text-transform: uppercase; white-space: nowrap;
236
- }
237
- td { padding: 9px 12px; border-bottom: 1px solid var(--line); vertical-align: top; }
238
- tr:last-child td { border-bottom: 0; }
239
- td.at { white-space: nowrap; font-family: var(--mono); font-size: 12px; color: var(--muted); }
240
- td.action code, td.change code, td.actor code { font-family: var(--mono); font-size: 12px; }
241
- td.action small { display: block; color: var(--muted); font-size: 11px; margin-top: 2px; }
242
- td.detail { color: var(--muted); overflow-wrap: anywhere; }
243
- .chip {
244
- display: inline-flex; align-items: center; min-height: 20px; padding: 0 8px;
245
- border-radius: 999px; background: var(--surface-soft); color: #3f4d49;
246
- font-size: 11px; font-weight: 740; white-space: nowrap;
247
- }
248
- .chip.green { background: var(--green-soft); color: var(--green); }
249
- .chip.red { background: var(--red-soft); color: var(--red); }
250
- .chip.amber { background: var(--amber-soft); color: var(--amber); }
251
- .chip.blue { background: var(--blue-soft); color: var(--blue); }
252
- .chip.stale { background: #ffe0d6; color: #a5330a; margin-left: 6px; }
253
- .empty { padding: 40px 16px; text-align: center; color: var(--muted); }
254
- @media (max-width: 760px) {
255
- form.filters { grid-template-columns: 1fr 1fr; }
256
- }
257
- </style>
258
- </head>
259
- <body>
260
- <div class="wrap">
261
- <header class="page">
262
- <h1>Operational activity timeline</h1>
263
- <p>
264
- {tenantLabel} · <code>{tenant.tenant_id}</code> · lifecycle events, newest first ·
265
- <a href={adminPath}>← Admin</a>
266
- </p>
267
- </header>
268
-
269
- {!viewerSession && (
270
- <div class="notice">
271
- Sign in as an owner/admin of {tenantLabel} to view the operational activity timeline.
272
- <a href="/auth/login">Sign in</a>.
273
- </div>
274
- )}
275
-
276
- {viewerSession && !authorized && (
277
- <div class="notice">
278
- {viewerEmail} is signed in but has no owner/admin standing for {tenantLabel}. The ops
279
- timeline is limited to owners/admins of this store and ToT staff.
280
- </div>
281
- )}
282
-
283
- {authorized && !dbAvailable && (
284
- <div class="notice">
285
- The activity timeline is stored in D1 (<code>STOREFRONT_APPS_DB</code>), which isn't
286
- bound in this environment — no rows to show here.
287
- </div>
288
- )}
289
-
290
- {authorized && alerts.length > 0 && (
291
- <div class="alerts" role="alert">
292
- {alerts.map((a) => (
293
- <div class={`alert ${a.severity}`}>
294
- <span aria-hidden="true">{a.severity === "page" || a.severity === "critical" ? "🚨" : "⚠️"}</span>
295
- <div>
296
- <strong>{a.title}</strong>
297
- <div>{a.summary}</div>
298
- <div class="meta">
299
- {a.changeId && <>Change <code>{a.changeId}</code> · </>}
300
- since {a.since}
301
- </div>
302
- </div>
303
- </div>
304
- ))}
305
- </div>
306
- )}
307
-
308
- {authorized && skew?.skewDetected && (
309
- <div class="skew" role="status">
310
- <span aria-hidden="true">⚠️</span>
311
- <div>
312
- <strong>Deploy skew detected</strong>
313
- <div class="versions">
314
- {skew.runningVersion
315
- ? <>Serving build <code>{skew.runningVersion}</code>. </>
316
- : <>Serving build unknown. </>}
317
- Observed builds:{" "}
318
- {skew.observedVersions.map((v, i) => (
319
- <>
320
- {i > 0 ? ", " : ""}
321
- <code>{v.version}</code>{v.env ? ` (${v.env})` : ""}
322
- {v.version === skew.latestObservedVersion ? " — latest" : " — stale"}
323
- </>
324
- ))}.
325
- This is a visibility signal only (alerting is a separate concern).
326
- </div>
327
- </div>
328
- </div>
329
- )}
330
-
331
- {authorized && (
332
- <form class="filters" method="get" action={timelinePath}>
333
- <div class="field">
334
- <label for="f-changeId">Change ID</label>
335
- <input id="f-changeId" type="text" name="changeId" value={query.changeId ?? ""} placeholder="chg-…" />
336
- </div>
337
- <div class="field">
338
- <label for="f-action">Action</label>
339
- <input id="f-action" type="text" name="action" value={query.action ?? ""} placeholder="change.ship" />
340
- </div>
341
- <div class="field">
342
- <label for="f-actionPrefix">Action family</label>
343
- <input id="f-actionPrefix" type="text" name="actionPrefix" value={query.actionPrefix ?? ""} placeholder="change." />
344
- </div>
345
- <div class="field">
346
- <label for="f-actorId">Actor ID</label>
347
- <input id="f-actorId" type="text" name="actorId" value={query.actorId ?? ""} placeholder="h_…" />
348
- </div>
349
- <div class="field">
350
- <label for="f-status">Status</label>
351
- <select id="f-status" name="status">
352
- {statusOptions.map((s) => (
353
- <option value={s} selected={query.status === s || (s === "" && !query.status)}>
354
- {s === "" ? "any" : s}
355
- </option>
356
- ))}
357
- </select>
358
- </div>
359
- <div class="field">
360
- <label for="f-since">Since (ISO)</label>
361
- <input id="f-since" type="text" name="since" value={query.since ?? ""} placeholder="2026-08-01" />
362
- </div>
363
- <div class="field">
364
- <label for="f-until">Until (ISO)</label>
365
- <input id="f-until" type="text" name="until" value={query.until ?? ""} placeholder="2026-08-31" />
366
- </div>
367
- <div class="field actions">
368
- <button class="btn" type="submit">Filter</button>
369
- <a class="btn ghost" href={timelinePath}>Reset</a>
370
- </div>
371
- </form>
372
- )}
373
-
374
- {authorized && dbAvailable && (
375
- <div class="table-wrap">
376
- <table>
377
- <thead>
378
- <tr>
379
- <th>When</th>
380
- <th>Actor</th>
381
- <th>Action</th>
382
- <th>Status</th>
383
- <th>Change</th>
384
- <th>Detail</th>
385
- </tr>
386
- </thead>
387
- <tbody>
388
- {rows.length === 0 && (
389
- <tr>
390
- <td class="empty" colspan="6">No activity matches these filters yet.</td>
391
- </tr>
392
- )}
393
- {rows.map((row) => {
394
- const version = versionOf(row);
395
- const isStale = version != null && staleVersions.has(version);
396
- return (
397
- <tr>
398
- <td class="at">{row.at}</td>
399
- <td class="actor"><code>{shortActor(row)}</code></td>
400
- <td class="action">
401
- <code>{row.action}</code>
402
- {row.actionDesc && <small>{row.actionDesc}</small>}
403
- </td>
404
- <td>
405
- <span class={`chip ${statusClass(row.status)}`}>{row.status}</span>
406
- {row.errorClass && <span class="chip amber">{row.errorClass}</span>}
407
- {isStale && <span class="chip stale">stale build</span>}
408
- </td>
409
- <td class="change">{row.changeId ? <code>{row.changeId}</code> : "—"}</td>
410
- <td class="detail">{detailFor(row) || "—"}</td>
411
- </tr>
412
- );
413
- })}
414
- </tbody>
415
- </table>
416
- </div>
417
- )}
418
- </div>
419
- </body>
420
- </html>
@@ -1,265 +0,0 @@
1
- ---
2
- /**
3
- * `/admin/review-release` — the merchant Review-and-Release workspace.
4
- *
5
- * The two-decision merchant surface (`docs/review-release-ux-contract.md`): a
6
- * merchant only ever (1) adds a ready change to the next release, and (2) publishes
7
- * the next release live. Everything mechanical — candidate prep, checks, retries,
8
- * reconciliation, conflict handling, exact-digest pins — runs UNDERNEATH and is
9
- * never a merchant control. This is an ADDITIVE surface; the operator/developer
10
- * "Publish" tab (`AdminPublishTab.astro`) keeps the retry/reject/rollback/
11
- * domain controls.
12
- *
13
- * ROUTING / AUTH — mirrors `admin.astro` + `admin/ops-timeline.astro`: middleware
14
- * resolves the tenant, injects CSP/security headers, and holds anonymous/non-member
15
- * visitors at the sign-in gate. Tenant context is already resolved by the time this
16
- * renders. Ship capability is stamped server-side (owner/admin, or an explicit
17
- * ship-on-behalf grant) — the SAME model the `/api/changes` + `/api/changes/ship`
18
- * endpoints re-authorize on every request; this is UX mirroring only.
19
- *
20
- * The page renders a static shell (three section containers, a persistent
21
- * ARIA-live announcer, an undo toast, and a focus-trapped publish-confirm dialog);
22
- * the client controller (`reviewReleaseWorkspace/controller.ts`) fetches
23
- * `GET /api/changes`, derives the two-decision view model with the pure
24
- * `lib/publish/reviewRelease` seam, and renders it.
25
- */
26
- export const prerender = false;
27
-
28
- import { readViewerSession } from "@/lib/auth/route";
29
- import { resolveViewerShipCapability } from "@/lib/change";
30
- import { withBase } from "@/lib/basePath";
31
- import { isInternalTestTenant } from "@tot/public-runtime";
32
-
33
- const { tenant, basePath, cspNonce } = Astro.locals;
34
- const adminPath = `${basePath || ""}/admin`;
35
- const storeName = tenant.displayName || tenant.appDomain || tenant.tenant_id;
36
-
37
- const viewerSession = await readViewerSession(Astro);
38
-
39
- // Ship capability, mirrored from the same session model the API routes gate on.
40
- // UX-only: the server re-authorizes every action. Owner/admin of THIS tenant, or a
41
- // signed-in developer holding a LIVE ship-on-behalf grant.
42
- const sessionCapability =
43
- viewerSession?.resource === tenant.appDomain ? viewerSession.capability : undefined;
44
- const viewerCapability = Astro.locals.viewer?.capability ?? sessionCapability ?? "none";
45
- let canShip = viewerCapability === "owner" || viewerCapability === "admin";
46
- if (!canShip && Astro.locals.viewer?.email) {
47
- const shipGrant = await resolveViewerShipCapability(Astro.locals.viewer.email, tenant.appDomain);
48
- if (shipGrant === "ship-on-behalf") canShip = true;
49
- }
50
-
51
- const internalTestStore = isInternalTestTenant(tenant);
52
-
53
- const changesEndpoint = withBase(basePath || "", "/api/changes");
54
- const acceptEndpoint = withBase(basePath || "", "/api/changes/accept");
55
- const shipEndpoint = withBase(basePath || "", "/api/changes/ship");
56
- const buildEndpoint = withBase(basePath || "", "/api/preview/build");
57
- const liveHref = withBase(basePath || "", "/");
58
- ---
59
-
60
- <!doctype html>
61
- <html lang="en">
62
- <head>
63
- <meta charset="utf-8" />
64
- <meta name="viewport" content="width=device-width, initial-scale=1" />
65
- <meta name="robots" content="noindex, nofollow" />
66
- <title>{storeName} · Review and Release</title>
67
- <style is:global nonce={cspNonce}>
68
- :root {
69
- color-scheme: light;
70
- --rr-bg: #f5f7f6;
71
- --rr-surface: #ffffff;
72
- --rr-surface-soft: #f9fbfa;
73
- --rr-ink: #16211f;
74
- --rr-muted: #5b6a65;
75
- --rr-line: #dce5e2;
76
- --rr-teal: #007f79;
77
- --rr-green-soft: #e3f5eb;
78
- --rr-green: #137a55;
79
- --rr-amber-soft: #fff0d5;
80
- --rr-amber: #a45d00;
81
- --rr-red-soft: #fde7e4;
82
- --rr-red: #b42318;
83
- --rr-blue-soft: #e7efff;
84
- --rr-blue: #2458c7;
85
- --rr-radius: 10px;
86
- --rr-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
87
- --rr-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
88
- }
89
- * { box-sizing: border-box; }
90
- body { margin: 0; background: var(--rr-bg); color: var(--rr-ink); font: 15px/1.55 var(--rr-font); }
91
- a { color: var(--rr-teal); }
92
- code.rr-mono { font-family: var(--rr-mono); font-size: 0.85em; }
93
- .rr-wrap { max-width: 860px; margin: 0 auto; padding: 28px 20px 72px; }
94
- header.rr-page { margin-bottom: 22px; }
95
- header.rr-page h1 { margin: 0; font-size: 24px; font-weight: 780; letter-spacing: -0.01em; }
96
- header.rr-page p { margin: 5px 0 0; color: var(--rr-muted); font-size: 13px; }
97
-
98
- .rr-notice {
99
- padding: 14px 16px; border: 1px solid var(--rr-amber); border-radius: var(--rr-radius);
100
- background: var(--rr-amber-soft); color: #5c3600; margin-bottom: 18px; font-size: 14px;
101
- }
102
-
103
- .rr-section {
104
- background: var(--rr-surface); border: 1px solid var(--rr-line);
105
- border-radius: var(--rr-radius); padding: 18px 20px; margin-bottom: 18px;
106
- }
107
- .rr-section-title { margin: 0 0 4px; font-size: 17px; font-weight: 720; }
108
- .rr-note { color: var(--rr-muted); font-size: 13px; margin: 8px 0 0; }
109
- .rr-empty { color: var(--rr-muted); font-size: 14px; margin: 12px 0 0; }
110
-
111
- /* Change list */
112
- .rr-change-list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 10px; }
113
- .rr-change {
114
- display: flex; align-items: flex-start; justify-content: space-between; gap: 14px;
115
- padding: 12px 14px; border: 1px solid var(--rr-line); border-radius: 8px;
116
- background: var(--rr-surface-soft);
117
- }
118
- .rr-change-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
119
- .rr-change-title { font-weight: 640; }
120
- .rr-subline { margin: 4px 0 0; color: var(--rr-muted); font-size: 13px; }
121
- .rr-change-actions { display: flex; gap: 8px; flex: none; }
122
-
123
- /* Status chips — ALWAYS carry the state WORD (never color alone, a11y §8). */
124
- .rr-chip {
125
- display: inline-flex; align-items: center; min-height: 20px; padding: 1px 9px;
126
- border-radius: 999px; font-size: 11px; font-weight: 720; white-space: nowrap;
127
- background: var(--rr-surface); border: 1px solid var(--rr-line); color: #3f4d49;
128
- }
129
- .rr-chip-preparing { background: var(--rr-blue-soft); color: var(--rr-blue); border-color: transparent; }
130
- .rr-chip-ready-to-add { background: var(--rr-green-soft); color: var(--rr-green); border-color: transparent; }
131
- .rr-chip-in-next-release { background: var(--rr-surface-soft); color: var(--rr-teal); border-color: var(--rr-teal); }
132
- .rr-chip-needs-attention { background: var(--rr-amber-soft); color: var(--rr-amber); border-color: transparent; }
133
- .rr-chip-published { background: var(--rr-green-soft); color: var(--rr-green); border-color: transparent; }
134
-
135
- /* Release panel */
136
- .rr-release-body { margin: 2px 0 0; color: var(--rr-muted); font-size: 14px; }
137
- .rr-contents { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--rr-line); }
138
- .rr-contents-title { margin: 0 0 8px; font-size: 13px; font-weight: 720; text-transform: uppercase; letter-spacing: 0.04em; color: var(--rr-muted); }
139
- .rr-contents-list { margin: 0; padding-left: 18px; display: grid; gap: 4px; font-size: 14px; }
140
- .rr-release-action { margin-top: 16px; display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
141
- .rr-paywall { flex-direction: column; align-items: flex-start; }
142
- .rr-success { margin: 12px 0 0; color: var(--rr-green); font-weight: 640; }
143
- .rr-live-body { margin: 4px 0 12px; color: var(--rr-muted); font-size: 14px; }
144
-
145
- /* Buttons */
146
- .rr-btn {
147
- min-height: 40px; display: inline-flex; align-items: center; justify-content: center;
148
- padding: 0 18px; border-radius: 8px; font: inherit; font-weight: 660; font-size: 14px;
149
- cursor: pointer; text-decoration: none; border: 1px solid var(--rr-teal);
150
- background: var(--rr-surface); color: var(--rr-teal);
151
- }
152
- .rr-btn-primary { background: var(--rr-teal); color: #fff; }
153
- .rr-btn-ghost { border-color: var(--rr-line); color: var(--rr-ink); }
154
- .rr-btn:disabled { opacity: 0.5; cursor: not-allowed; }
155
- .rr-btn:focus-visible { outline: 3px solid var(--rr-blue); outline-offset: 2px; }
156
- a.rr-btn:focus-visible { outline: 3px solid var(--rr-blue); outline-offset: 2px; }
157
-
158
- /* Announcer (SR-only) + undo toast */
159
- .rr-announce {
160
- position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
161
- overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
162
- }
163
- .rr-toast {
164
- position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
165
- background: var(--rr-ink); color: #fff; padding: 10px 16px; border-radius: 8px;
166
- font-size: 13px; box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18); z-index: 40;
167
- }
168
-
169
- /* Publish-confirm dialog */
170
- .rr-confirm-backdrop {
171
- position: fixed; inset: 0; background: rgba(22, 33, 31, 0.45);
172
- display: flex; align-items: center; justify-content: center; padding: 20px; z-index: 50;
173
- }
174
- /* `display: flex` above beats the UA `[hidden]{display:none}` rule at equal
175
- specificity (author styles always win) — without this, toggling the
176
- `hidden` attribute in JS has no visual effect and the dialog looks stuck. */
177
- .rr-confirm-backdrop[hidden] {
178
- display: none;
179
- }
180
- .rr-confirm-dialog {
181
- background: var(--rr-surface); border-radius: var(--rr-radius); padding: 22px 24px;
182
- max-width: 440px; width: 100%; box-shadow: 0 12px 48px rgba(0, 0, 0, 0.28);
183
- }
184
- .rr-confirm-dialog h2 { margin: 0 0 8px; font-size: 19px; font-weight: 740; }
185
- .rr-confirm-dialog p { margin: 0 0 20px; color: var(--rr-muted); font-size: 14px; }
186
- .rr-confirm-actions { display: flex; justify-content: flex-end; gap: 10px; }
187
-
188
- .rr-loading { color: var(--rr-muted); font-size: 14px; padding: 8px 0; }
189
-
190
- @media (max-width: 560px) {
191
- .rr-change { flex-direction: column; }
192
- .rr-change-actions { width: 100%; }
193
- .rr-btn { width: 100%; }
194
- }
195
- @media (prefers-reduced-motion: reduce) {
196
- * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
197
- }
198
- </style>
199
- </head>
200
- <body>
201
- <div class="rr-wrap">
202
- <header class="rr-page">
203
- <h1>Review and release</h1>
204
- <p>{storeName} · review changes and publish your next release · <a href={adminPath}>← Admin</a></p>
205
- </header>
206
-
207
- {!viewerSession && (
208
- <div class="rr-notice">
209
- Sign in as an owner or admin of {storeName} to review changes and publish releases.
210
- <a href="/auth/login">Sign in</a>.
211
- </div>
212
- )}
213
-
214
- {viewerSession && (
215
- <div
216
- data-rr-root
217
- data-changes-endpoint={changesEndpoint}
218
- data-accept-endpoint={acceptEndpoint}
219
- data-ship-endpoint={shipEndpoint}
220
- data-build-endpoint={buildEndpoint}
221
- data-live-href={liveHref}
222
- data-can-ship={canShip ? "true" : "false"}
223
- data-internal-test-store={internalTestStore ? "true" : "false"}
224
- data-store-name={storeName}
225
- >
226
- <div class="rr-announce" data-rr-announce role="status" aria-live="polite"></div>
227
-
228
- <div data-rr-workspace>
229
- <p class="rr-loading" data-rr-loading>Loading your changes and next release…</p>
230
- </div>
231
-
232
- <div class="rr-toast" data-rr-toast role="status" aria-live="polite" hidden></div>
233
-
234
- <!-- Publish-confirm dialog — a single plain-language step (the human go-live
235
- gate). role=dialog, focus-trapped, Esc cancels, focus returns to the
236
- invoking button. No typed-domain confirm, no digest to verify. -->
237
- <div class="rr-confirm-backdrop" data-rr-confirm hidden>
238
- <div
239
- class="rr-confirm-dialog"
240
- data-rr-confirm-dialog
241
- role="dialog"
242
- aria-modal="true"
243
- aria-labelledby="rr-confirm-heading"
244
- tabindex="-1"
245
- >
246
- <h2 id="rr-confirm-heading" data-rr-confirm-heading>Publish your next release?</h2>
247
- <p data-rr-confirm-body></p>
248
- <div class="rr-confirm-actions">
249
- <button type="button" class="rr-btn rr-btn-ghost" data-rr-confirm-cancel>Cancel</button>
250
- <button type="button" class="rr-btn rr-btn-primary" data-rr-confirm-ok>Publish now</button>
251
- </div>
252
- </div>
253
- </div>
254
- </div>
255
- )}
256
- </div>
257
-
258
- {viewerSession && (
259
- <script>
260
- import { initReviewReleaseWorkspace } from "@/lib/publish/reviewReleaseWorkspace/controller";
261
- initReviewReleaseWorkspace();
262
- </script>
263
- )}
264
- </body>
265
- </html>