@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,1548 +0,0 @@
1
- ---
2
- /**
3
- * /cockpit — the developer's first-run shell (what a granted developer lands on after
4
- * sign-in). Renamed from /dev 2026-07-20; middleware 302-redirects old /dev links here.
5
- *
6
- * Deliberately NOT the operator/reconcile control plane, and — post U1 (strip-to-shell) —
7
- * deliberately NOT the old marketing home either. This is the guided-cockpit's Step 1: a
8
- * single dominant action (copy the one command that gets your store running) plus the
9
- * developer's own tenant identity and an ⓘ explainer. The staged flow, live Monitor,
10
- * in-browser file tree/diff/editor, "Stuck? Ask", and the "coming soon" shelf land on top
11
- * of this shell in later chunks (U2/U3/U4, C1+) — this file is the frame they mount into.
12
- *
13
- * Brand: a STANDALONE document (bypasses Layout.astro) that ports the canonical /dev cockpit
14
- * mockup's visual system — the tokenoftrust tenant theme (navy ink #0A2540 + trust teal
15
- * #0E9F8E on cool-white #F7F9FC), Plus Jakarta Sans / Inter / JetBrains Mono, rounded cards —
16
- * now with full dark-mode (system + manual toggle) and reduced-motion support. Same CSP as
17
- * every storefront response; the shared typefaces are self-hosted same-origin (@fontsource,
18
- * imported via `@/styles/fonts.css`), and inline style/script carry this request's nonce.
19
- *
20
- * The one command honours the shipped browserless-signin fastpath: a developer who reached
21
- * this page from their magic link already has a session here, so the Copy button mints a
22
- * single-use `tot login --code … && tot start` command (via /api/dev/cli-signin-code) and
23
- * copies the whole thing — no browser sign-in in the terminal. Everyone else copies the
24
- * generic `npm i -g @tokenoftrust/cli && tot start`.
25
- */
26
- import { readViewerSession } from "@/lib/auth/route";
27
- import { staticTenants } from "@/config/tenants";
28
- import { resolvePreviewTenant } from "@/config/previewTenantLookup";
29
- import { readTenantPreviewStatus, isStoreNotSetUp } from "@/lib/dev/previewStatus";
30
- import { resolveDevApiBase } from "@/lib/dev/apiBase";
31
- import { resolveCockpitStoreResource } from "@/lib/dev/cockpitStore";
32
- import { normalizeEmailHint, redactEmailForHint } from "@/lib/privacy/emailHint";
33
- // Self-hosted web fonts (audit finding 6) — same-origin @fontsource, replacing
34
- // the render-blocking fonts.googleapis.com stylesheet. Covers Plus Jakarta Sans,
35
- // Inter, and JetBrains Mono used by this page's inline styles.
36
- import "@/styles/fonts.css";
37
-
38
- export const prerender = false;
39
- const cspNonce = Astro.locals.cspNonce;
40
- const url = new URL(Astro.request.url);
41
- // The tenant prefix ("" host-routed, "/<tenant>" path-routed) this cockpit is served
42
- // under — set by the middleware. Every self-link + same-store API call below carries
43
- // it so a `/<tenant>/cockpit` invite keeps its store across the login round-trip and
44
- // the command mint (see resolveCockpitStoreResource / the basePath-scoped fetches).
45
- const basePath = Astro.locals.basePath ?? "";
46
- const isCredentialRecovery = url.searchParams.get("recover") === "cli";
47
- const legacyRecoveryEmail = (url.searchParams.get("email") ?? "").trim();
48
- const recoveryEmailHint =
49
- normalizeEmailHint(url.searchParams.get("email_hint")) ??
50
- redactEmailForHint(legacyRecoveryEmail) ??
51
- "";
52
- if (isCredentialRecovery && url.searchParams.has("email")) {
53
- const canonical = new URLSearchParams(url.searchParams);
54
- canonical.delete("email");
55
- if (recoveryEmailHint) canonical.set("email_hint", recoveryEmailHint);
56
- return Astro.redirect(
57
- `${basePath}/cockpit${canonical.toString() ? `?${canonical.toString()}` : ""}`,
58
- 302,
59
- );
60
- }
61
- const cockpitNext = `${basePath}/cockpit${isCredentialRecovery ? "?recover=cli" : ""}`;
62
- const loginParams = new URLSearchParams({ next: cockpitNext });
63
- if (recoveryEmailHint) loginParams.set("email_hint", recoveryEmailHint);
64
- const loginHref = `/auth/login?${loginParams.toString()}`;
65
-
66
- const session = await readViewerSession({ cookies: Astro.cookies });
67
- const signedIn = !!session;
68
- // d1 (board: d1-unmask-invite-email) — the FULL invite address to show its OWNER in the
69
- // recovery notice, never a redacted hint. Prefer the validated session email; else a full
70
- // `?email=` if one ever survives to render. Empty when only a redacted `email_hint` is on
71
- // hand (the common case — the CLI masks the address upstream before it reaches this page),
72
- // and the notice then falls back to neutral, non-redacted copy rather than re-showing the
73
- // owner a masked form of their own address. redactEmailForHint/emailHint.ts stay intact for
74
- // every other caller (e.g. the /auth/login hint below) — we simply stop masking HERE.
75
- const recoveryEmailFull =
76
- session?.email ||
77
- (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(legacyRecoveryEmail) ? legacyRecoveryEmail.toLowerCase() : "");
78
- // The fastpath (browserless terminal sign-in) is gated behind the developer's own session +
79
- // capability — the same gate /api/dev/cli-signin-code enforces. Mirror it here so a
80
- // logged-out / no-capability visitor gets the generic command, never a broken mint.
81
- const hasStoreAccess = signedIn && !!session?.capability && session.capability !== "none";
82
-
83
- // Resolve which store this cockpit is for. The URL wins when a `/<tenant>/cockpit`
84
- // request named a specific store the signed-in developer can access (the trusted
85
- // host/path seam on `locals.tenant`, membership-checked) — this is the regression
86
- // fix: an invite to another store than the session's first membership no longer
87
- // "loses" that store. Otherwise: the developer's own first store (generic host), or
88
- // — for an ANONYMOUS viewer only — the path store's branding, else `?tenant=`. Never
89
- // a signed-in developer's store overridden by caller-supplied query input.
90
- // See resolveCockpitStoreResource; the same resolver gates the mint + /api/dev/*.
91
- const wanted = resolveCockpitStoreResource(Astro, session);
92
- const tenant =
93
- (wanted
94
- ? staticTenants.find((t) =>
95
- [t.appDomain, t.tenant_id, t.canonicalDomain, ...t.domains].some(
96
- (d) => d?.toLowerCase() === wanted,
97
- ),
98
- )
99
- : null) ??
100
- staticTenants.find((t) => t.tenant_id === "tokenoftrust") ??
101
- staticTenants[0];
102
-
103
- const appDomain = tenant?.appDomain ?? "tokenoftrust.com";
104
-
105
- // fix-staff-cockpit-403: `hasStoreAccess` above only proves the session carries SOME
106
- // eligible capability — the federated sign-in path stamps capability:"member"
107
- // unconditionally (auth/magic-exchange), so a ToT-staff viewer admitted by the
108
- // loginGate staff bypass (no real store membership) passes it too, even on the home
109
- // host where `wanted` never names a real store. Every /api/dev/* route instead gates
110
- // on this SAME tenant actually RESOLVING (resolvePreviewTenant — see
111
- // lib/dev/hostedCockpit.ts, api/dev/activity.ts) — mirror that here so we never mint a
112
- // setup command or start polling for a viewer who'd just 403-loop against it.
113
- const resolvedStoreTenant = hasStoreAccess && wanted ? await resolvePreviewTenant(wanted) : null;
114
- // Capability-eligible, but no store resolves for this viewer/host (the staff/home-host
115
- // case above). Gets its own calm "pick a store" state instead of the full flow.
116
- const noResolvableStore = hasStoreAccess && !resolvedStoreTenant;
117
-
118
- // STORE-NOT-SET-UP (#8425 invite-gated-auto-build): a developer WITH access + a
119
- // resolvable store, but whose forge repo isn't provisioned yet — the invite landed
120
- // before (or without) the store being set up. Offering the fastpath here would hand
121
- // them a `tot login --code … && tot start` that dies at checkout on "Repo … is not
122
- // provisioned" (James's first-run failure). Ask the credential-free MCP preview-status
123
- // seam; only a DEFINITIVE not-provisioned flips the state (an unconfigured / can't-answer
124
- // seam keeps the normal fastpath, with the CLI's own clean "not set up yet" error as the
125
- // backstop — so no regression on local/dev or a transient MCP blip). This is a DISTINCT
126
- // axis from noResolvableStore/noStoreAccess and gets its own honest notice below.
127
- const storeNotSetUp =
128
- hasStoreAccess && !noResolvableStore && isStoreNotSetUp(await readTenantPreviewStatus(appDomain));
129
-
130
- // The gate for rendering the setup-command + activity-poll flow: eligible AND a real
131
- // store to point it at AND that store is actually provisioned. Use this (never the raw
132
- // hasStoreAccess) for anything that mints a command or starts the activity poll.
133
- const canSetupStore = hasStoreAccess && !noResolvableStore && !storeNotSetUp;
134
-
135
- // vc-app-binding safety-net (08-18, §5): a BROWSER-FIRST developer reached this page
136
- // via the storefront's own sign-in (a tot_session cookie) without ever crossing an
137
- // MCP binding seam (rendezvous approve / native OAuth), so `appForCaller` doesn't
138
- // resolve them and the `tot start` command we're about to hand them would 403 with
139
- // "No version-control app is bound." Ensure the binding now — on-behalf of the
140
- // verified session identity, via the app's private_key_jwt channel. Best-effort +
141
- // fail-open (never blocks render), throttled to once per browser session so it stays
142
- // off the hot render path; the authority-seam binding + backfill remain the real
143
- // guarantee. Gated on canSetupStore (entitled + resolvable + provisioned), the exact
144
- // condition under which we render a runnable command.
145
- //
146
- // The binding itself is control-plane (authors a forge/MCP binding under the app
147
- // credential), so this renderer page reaches it only over the network — POST
148
- // /api/dev/ensure-vc-binding — never by static import (renderer-carve moat). The
149
- // endpoint re-derives the developer from the forwarded session cookie.
150
- if (canSetupStore && session && !Astro.cookies.has("tot_vc_bound")) {
151
- const vcBind = await fetch(new URL(`${basePath}/api/dev/ensure-vc-binding`, url.origin), {
152
- method: "POST",
153
- headers: { cookie: Astro.request.headers.get("cookie") ?? "" },
154
- })
155
- .then((r) => (r.ok ? r.json() : null))
156
- .catch(() => null);
157
- if (vcBind?.ok) {
158
- // Throttle only on a real success — a fail-open miss retries on the next load.
159
- Astro.cookies.set("tot_vc_bound", "1", {
160
- path: "/",
161
- httpOnly: true,
162
- sameSite: "lax",
163
- secure: true,
164
- maxAge: 3600, // re-check hourly so a lost/revoked binding self-heals
165
- });
166
- }
167
- }
168
-
169
- // Signed in, but either no developer capability on any store OR (staff bypass) no store
170
- // resolves for this viewer/host — an honest recovery state (not a 401): show what to
171
- // do, not the fastpath. NOTE: storeNotSetUp is deliberately NOT folded in here — it is
172
- // its own state with its own notice, so this stays computed from resolvable-store only.
173
- const noStoreAccess = signedIn && (!hasStoreAccess || noResolvableStore);
174
- // The generic command shown (and copied) when we can't mint a personalised one.
175
- // Pinned to the EXACT CLI version this build was cut alongside (`@tokenoftrust/cli@<version>`,
176
- // astro.config Vite define) so the paste installs precisely the CLI this cockpit expects —
177
- // never a floating dist-tag the terminal resolves. Used in the "prefer the steps" hint +
178
- // (env-derived) the minted fastpath command.
179
- const cliInstallSpec = __CLI_INSTALL_SPEC__;
180
-
181
- // isLocalDevLoop === "the STOREFRONT app itself is running under `astro dev`" (a
182
- // storefront engineer's local rig) — NOT the invited developer's dev loop (that's
183
- // `tot dev`, the runner, which excludes this page entirely). It gates ONLY the
184
- // local astro-dev cockpit — the Monitor/loopbar, in-browser Files editor, live
185
- // preview + click-to-source, and the local stepper — all of which read the
186
- // /__tot/* dev-plugins that exist ONLY under `astro dev`. import.meta.env.DEV is
187
- // statically replaced with false in the production Cloudflare Worker build, so all
188
- // of that is tree-shaken away there. Nothing that should reach a real developer on
189
- // the deployed panel may hang off this flag — hosted product surface is either
190
- // unconditional (see the "coming soon" shelf) or gated on `hostedActivity` below.
191
- const isLocalDevLoop = import.meta.env.DEV;
192
-
193
- // hostedActivity === the deployed hosted panel's activity-fed cockpit — the actual
194
- // product surface the invited developer sees. It renders on the Worker (never under
195
- // `astro dev`) for a signed-in developer with capability, and reflects what they're
196
- // doing in their LOCAL dev loop via the activity bridge (POST /api/dev/activity →
197
- // polled here). This is the "magic": the developer watches their own local rig work
198
- // on our hosted surface. It is NOT a port of the local-only Monitor/Files/preview
199
- // panels (those depend on /__tot/* endpoints that exist only under `astro dev`;
200
- // bringing those hosted is cp-tree/cp-file/cp-preview, tracked separately).
201
- const hostedActivity = !isLocalDevLoop && canSetupStore;
202
-
203
- // cp-env (cloud-dev-cockpit Phase 0): the file-tree/viewer/diff + click-to-source
204
- // cockpit modules (lib/dev/cockpit/files.ts, clickToSource.ts) are ENVIRONMENT-AWARE
205
- // — same client JS, different endpoint base. Local: the astro-dev-only /__tot/* plugins. Hosted: new
206
- // /api/dev/* Astro API routes, landing incrementally (cp-tree/cp-file/cp-write/cp-diff/
207
- // cp-locate — separate cards). Resolving it here, unconditionally, means those cards
208
- // don't each have to invent the same env check; on isLocalDevLoop this is a no-op
209
- // (resolves to the unchanged /__tot base). See decision architecture-environment-aware-cockpit.
210
- const apiBase = resolveDevApiBase(isLocalDevLoop, basePath);
211
-
212
- // Dev-only client widgets (Monitor, Stepper, Ask, Files, click-to-source) and
213
- // the hosted activity rail live in typed apps/storefront/src/lib/dev/cockpit/*
214
- // modules, imported from processed <script> tags below (ADR 0013) instead of
215
- // being hand-mirrored here as raw strings. apiBase/isLocalDevLoop/appDomain/
216
- // basePath cross into those modules via data-cockpit-* attrs on <body>.
217
- ---
218
-
219
- <html lang="en">
220
- <head>
221
- <meta charset="utf-8" />
222
- <meta name="viewport" content="width=device-width, initial-scale=1" />
223
- <meta name="robots" content="noindex, nofollow" />
224
- <title>Developer Cockpit — {appDomain}</title>
225
- {/* cockpit-theme-persist: seed data-theme from the STORED choice before the stylesheet/
226
- style below ever paint, so a reload of a manually-toggled theme doesn't flash the OS
227
- default first. Runs before the theme toggle's own script (end of body) exists — it
228
- only ever READS here; the toggle is what writes. No stored choice → leave the
229
- attribute unset, so the `@media (prefers-color-scheme)` block keeps deciding. */}
230
- <script nonce={cspNonce} is:inline>
231
- (function () {
232
- try {
233
- var v = localStorage.getItem("tot.theme");
234
- if (v === "dark" || v === "light") document.documentElement.setAttribute("data-theme", v);
235
- } catch (e) {}
236
- })();
237
- </script>
238
- {/* Web fonts are self-hosted via `@/styles/fonts.css` (imported above). */}
239
- <style nonce={cspNonce}>
240
- :root {
241
- --bg: #f7f9fc; --bg-2: #eef3f9; --surface: #ffffff;
242
- --ink: #0a2540; --ink-2: #33415b; --muted: #55637a; --faint: #8593a8;
243
- --accent: #0e9f8e; --accent-ink: #0b7a6d; --accent-wash: #e7f6f4; --accent-line: #b8e6df;
244
- --border: #e3e8ef; --border-2: #eef2f7;
245
- --warn: #c23b3b; --warn-wash: #fbecec; --warn-line: #f0cccc;
246
- --amber: #b7791f; --amber-wash: #fdf6e7; --amber-line: #f0dcae;
247
-
248
- --term: #0a1a2f; --term-2: #071426; --term-fg: #dbe6f2; --term-dim: #5f7189;
249
- --term-prompt: #2dd4bf; --term-ok: #34d399; --term-line: #14263f;
250
-
251
- --r-sm: 6px; --r-md: 10px; --r-lg: 16px; --r-xl: 22px; --r-full: 999px;
252
- --sh-sm: 0 1px 2px rgba(10,37,64,.06), 0 2px 6px -2px rgba(10,37,64,.08);
253
- --sh-md: 0 8px 28px -10px rgba(10,37,64,.16);
254
- --sh-lg: 0 28px 64px -26px rgba(10,37,64,.28);
255
-
256
- /* Guided-flow motion tokens (Steps 1–3 choreography). One place to retune
257
- the whole open/collapse/reveal feel — a future reskin changes the motion
258
- here, not rule-by-rule. Theme-independent, so declared once. */
259
- --flow-ease: cubic-bezier(.2, .9, .3, 1);
260
- --flow-dur: .42s;
261
- --flow-rise: 8px; /* how far a newly-revealed block settles in from */
262
-
263
- --display: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
264
- --body: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
265
- --mono: "JetBrains Mono", ui-monospace, "SF Mono", "Cascadia Code", Menlo, monospace;
266
- color-scheme: light;
267
- }
268
- @media (prefers-color-scheme: dark) {
269
- :root {
270
- --bg: #071320; --bg-2: #0a1a2b; --surface: #0e2134;
271
- --ink: #eaf1f8; --ink-2: #bdccdc; --muted: #90a3b7; --faint: #6c8098;
272
- --accent: #2fd4bf; --accent-ink: #7fe7da; --accent-wash: #0f2e30; --accent-line: #1e514b;
273
- --border: #1b3348; --border-2: #16293c;
274
- --warn: #f2857f; --warn-wash: #2a1414; --warn-line: #5a2626;
275
- --amber: #f0c56b; --amber-wash: #2a2110; --amber-line: #5a481f;
276
- color-scheme: dark;
277
- }
278
- }
279
- :root[data-theme="dark"] {
280
- --bg: #071320; --bg-2: #0a1a2b; --surface: #0e2134;
281
- --ink: #eaf1f8; --ink-2: #bdccdc; --muted: #90a3b7; --faint: #6c8098;
282
- --accent: #2fd4bf; --accent-ink: #7fe7da; --accent-wash: #0f2e30; --accent-line: #1e514b;
283
- --border: #1b3348; --border-2: #16293c;
284
- --warn: #f2857f; --warn-wash: #2a1414; --warn-line: #5a2626;
285
- --amber: #f0c56b; --amber-wash: #2a2110; --amber-line: #5a481f;
286
- color-scheme: dark;
287
- }
288
- :root[data-theme="light"] {
289
- --bg: #f7f9fc; --bg-2: #eef3f9; --surface: #ffffff;
290
- --ink: #0a2540; --ink-2: #33415b; --muted: #55637a; --faint: #8593a8;
291
- --accent: #0e9f8e; --accent-ink: #0b7a6d; --accent-wash: #e7f6f4; --accent-line: #b8e6df;
292
- --border: #e3e8ef; --border-2: #eef2f7;
293
- --warn: #c23b3b; --warn-wash: #fbecec; --warn-line: #f0cccc;
294
- --amber: #b7791f; --amber-wash: #fdf6e7; --amber-line: #f0dcae;
295
- color-scheme: light;
296
- }
297
-
298
- * { box-sizing: border-box; }
299
- html { -webkit-text-size-adjust: 100%; }
300
- body {
301
- margin: 0;
302
- background:
303
- radial-gradient(50rem 28rem at 100% -8%, var(--accent-wash) 0%, transparent 55%),
304
- var(--bg);
305
- background-repeat: no-repeat;
306
- color: var(--ink-2); font-family: var(--body); font-size: 16px; line-height: 1.6;
307
- letter-spacing: -.011em; -webkit-font-smoothing: antialiased;
308
- padding: 0 1.25rem 6rem;
309
- }
310
- a { color: var(--accent-ink); }
311
- a:focus-visible, button:focus-visible, summary:focus-visible {
312
- outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px;
313
- }
314
- .wrap { max-width: 58rem; margin: 0 auto; }
315
-
316
- /* ---- top bar (tenant identity) ---- */
317
- .topbar {
318
- display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
319
- padding: 1.15rem 0 1rem; border-bottom: 1px solid var(--border); margin-bottom: 1.8rem;
320
- }
321
- .brand { display: flex; align-items: center; gap: .65rem; }
322
- .brand .mark {
323
- width: 30px; height: 30px; border-radius: 9px;
324
- background: linear-gradient(150deg, var(--accent), var(--accent-ink));
325
- display: grid; place-items: center; color: #fff; flex: none;
326
- box-shadow: 0 3px 10px -3px rgba(14,159,142,.6);
327
- }
328
- .brand .mark svg { width: 17px; height: 17px; }
329
- .brand .who { display: flex; flex-direction: column; line-height: 1.15; }
330
- .brand .dom { font-family: var(--display); font-weight: 700; color: var(--ink); font-size: .98rem; letter-spacing: -.01em; }
331
- .brand .sub { font-family: var(--mono); font-size: .7rem; color: var(--faint); text-transform: uppercase; letter-spacing: .12em; }
332
- .topbar .right { display: flex; align-items: center; gap: .9rem; font-family: var(--mono); font-size: .78rem; color: var(--muted); }
333
- .cockpit-lede { margin: -0.6rem 0 1.6rem; font-family: var(--body); font-size: .92rem; color: var(--muted); max-width: 60ch; }
334
- .cockpit-lede strong { color: var(--ink); font-weight: 600; }
335
- .topbar .right .sess { display: inline-flex; align-items: center; gap: .45rem; }
336
- .topbar .right .sess::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash); }
337
- .themetog {
338
- font-family: var(--mono); font-size: .72rem; background: var(--surface); color: var(--muted);
339
- border: 1px solid var(--border); border-radius: var(--r-full); padding: .3rem .7rem; cursor: pointer;
340
- }
341
- .themetog:hover { color: var(--ink); border-color: var(--accent-line); }
342
-
343
- /* ---- recovery notice (signed out / no store access) ---- */
344
- .notice {
345
- background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
346
- box-shadow: var(--sh-sm); padding: 1.15rem 1.4rem; margin: 0 0 1.6rem;
347
- display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .7rem 1.4rem;
348
- }
349
- .notice p { margin: 0; color: var(--ink-2); font-size: .95rem; }
350
- .notice a.action {
351
- flex: none; display: inline-flex; align-items: center; gap: .4rem; background: var(--accent);
352
- color: #fff; text-decoration: none; font: 700 .92rem/1 var(--display); padding: .66rem 1.15rem;
353
- border-radius: var(--r-full); transition: background .18s ease, transform .12s ease;
354
- }
355
- .notice a.action:hover { background: var(--accent-ink); transform: translateY(-1px); }
356
-
357
- /* ---- hosted activity ledger (hosted-dev-activity-bridge) — reskinned to the
358
- signal-rail "activity" look: a compact 3-col table (time | action | file)
359
- with the newest row accented (.is-new). ---- */
360
- .hosted-activity {
361
- margin-top: .4rem; background: var(--surface); border: 1px solid var(--border);
362
- border-radius: var(--r-lg); box-shadow: var(--sh-sm); padding: 1rem 1.2rem .9rem; margin-bottom: 1.6rem;
363
- }
364
- .ha-head { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; margin-bottom: .5rem; }
365
- .ha-head h2 { font-family: var(--display); font-size: .95rem; font-weight: 700; color: var(--ink); margin: 0; }
366
- .ha-sub { font-size: .78rem; color: var(--muted); }
367
- .ha-sub code { font-family: var(--mono); }
368
- /* Activity TABLE — three real columns (time | action | file) with row spacing,
369
- so the fields are always visually separated and never run together. */
370
- .ha-table { width: 100%; border-collapse: separate; border-spacing: 0 .4rem; margin: 0; }
371
- .ha-row > td {
372
- padding: .55rem .8rem; background: var(--bg-2); font-size: .85rem; color: var(--ink-2);
373
- vertical-align: baseline;
374
- }
375
- .ha-row > td:first-child { border-radius: var(--r-md) 0 0 var(--r-md); }
376
- .ha-row > td:last-child { border-radius: 0 var(--r-md) var(--r-md) 0; }
377
- .ha-row.is-new > td { background: var(--accent-wash); }
378
- td.ha-when { font-family: var(--mono); font-size: .74rem; color: var(--faint); width: 6.8rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
379
- td.ha-desc { font-weight: 600; white-space: nowrap; width: 1%; padding-right: 1.1rem; }
380
- td.ha-file { font-family: var(--mono); font-size: .74rem; color: var(--muted); max-width: 0; width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
381
- .ha-toggle {
382
- margin-top: .5rem; background: none; border: none; padding: .3rem .1rem; cursor: pointer;
383
- font: 600 .8rem var(--body); color: var(--accent-ink); text-decoration: underline;
384
- }
385
- .ha-toggle:hover { color: var(--accent); }
386
- /* Encouragement banner shown atop Step 2 when you arrive. */
387
- .ha-encourage {
388
- display: flex; align-items: center; gap: .6rem; margin: 0 0 1rem;
389
- padding: .7rem .9rem; border-radius: var(--r-md);
390
- background: var(--accent-wash); border: 1px solid var(--accent-line); color: var(--accent-ink);
391
- font-weight: 600; font-size: .92rem;
392
- }
393
- .ha-encourage .chk { flex: none; }
394
- .ha-empty { margin: .2rem 0 .3rem; font-size: .84rem; color: var(--muted); }
395
- .ha-empty code { font-family: var(--mono); }
396
-
397
- /* ---- bridge strip (replaces the big live-dev-link card): the rail's power
398
- source, slimmed to a strip. Fed by the SAME activity poll's runtime. ---- */
399
- .bridge {
400
- display: flex; align-items: center; gap: .8rem; flex-wrap: wrap;
401
- border: 1px solid var(--accent-line); background: var(--surface);
402
- border-radius: var(--r-lg); padding: .7rem 1rem; margin-bottom: 1.4rem;
403
- box-shadow: var(--sh-sm);
404
- }
405
- .bridge[data-live="1"] { border-color: var(--accent); animation: bridgeGlow 2.6s ease-in-out infinite; }
406
- @keyframes bridgeGlow {
407
- 0%, 100% { box-shadow: 0 0 0 1px var(--accent-line), 0 0 12px -4px rgba(14,159,142,.3); }
408
- 50% { box-shadow: 0 0 0 2px var(--accent), 0 0 22px -2px rgba(14,159,142,.45); }
409
- }
410
- .bridge[data-live="0"] { border-color: var(--amber-line); background: var(--amber-wash); }
411
- .beacon { width: 10px; height: 10px; border-radius: 50%; flex: none; background: var(--accent); }
412
- .bridge[data-live="1"] .beacon { animation: beat 1.6s ease-in-out infinite; }
413
- .bridge[data-live="0"] .beacon { background: var(--amber); }
414
- .b-txt { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
415
- .b-kicker { font-family: var(--mono); font-size: .66rem; text-transform: uppercase; letter-spacing: .13em; color: var(--accent-ink); }
416
- .bridge[data-live="0"] .b-kicker { color: var(--amber); }
417
- .b-url { font-family: var(--mono); font-size: .9rem; font-weight: 600; color: var(--accent-ink); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
418
- .b-url:hover { text-decoration: underline; }
419
- .bridge[data-live="0"] .b-url { color: var(--muted); }
420
- .b-diag { margin-left: auto; font-family: var(--mono); font-size: .68rem; color: var(--faint); white-space: nowrap; }
421
- .b-go {
422
- flex: none; display: inline-flex; align-items: center; gap: .35rem;
423
- font: 700 .8rem/1 var(--display); background: var(--accent); color: #fff; text-decoration: none;
424
- padding: .5rem .95rem; border-radius: var(--r-full); transition: background .12s, transform .12s;
425
- }
426
- .b-go:hover { background: var(--accent-ink); transform: translateY(-1px); }
427
- .b-hint { flex-basis: 100%; margin: 0; font-size: .8rem; color: var(--amber); }
428
- .b-hint code { font-family: var(--mono); }
429
- /* Obstacle lane (v2 §6): a failed `tot start` — an ERROR, so it uses the
430
- existing --warn (red) token triple (NO new palette values), which also sets
431
- it apart from the amber "went quiet". Content is a stacked block, so the
432
- beacon aligns to the top rather than centering on a tall card. */
433
- .bridge[data-live="obstacle"] { align-items: flex-start; border-color: var(--warn-line); background: var(--warn-wash); }
434
- .bridge[data-live="obstacle"] .beacon { background: var(--warn); }
435
- .b-obstacle { flex: 1 1 100%; display: flex; flex-direction: column; gap: .3rem; min-width: 0; }
436
- .b-ob-title { color: var(--warn); }
437
- .b-ob-title::before { content: "\26A0 "; } /* ⚠ warning glyph */
438
- .b-ob-msg { margin: 0; font-size: .9rem; font-weight: 600; color: var(--ink); }
439
- .b-ob-fix { margin: 0; font-size: .82rem; color: var(--muted); line-height: 1.5; }
440
- .b-ob-lead { color: var(--warn); font-weight: 700; }
441
- .b-ob-cmd { font-family: var(--mono); font-size: .8rem; background: var(--bg-2); color: var(--ink); padding: .1rem .35rem; border-radius: var(--r-sm); }
442
- /* These three set their own `display`, which (author > UA) would beat the UA
443
- [hidden]{display:none} — so the script's `.hidden` toggles silently no-op on
444
- them. Re-assert [hidden] at higher specificity so hidden means hidden: the
445
- obstacle block and the live-strip text swap cleanly, AND the quiet-state CTA
446
- (b-go) actually hides. */
447
- .b-txt[hidden], .b-go[hidden], .b-obstacle[hidden] { display: none; }
448
- @media (prefers-reduced-motion: reduce) { .bridge[data-live="1"], .beacon, .b-go { animation: none !important; transition: none; } }
449
- @media (max-width: 34rem) { .b-diag { display: none; } }
450
-
451
- /* ---- the signal rail: breadcrumb + done-bars merged into stations on one
452
- spine. NO single .rail wrapper — each station is a top-level element and
453
- carries its own liveness flow via data-attributes on its .seg. ---- */
454
- .station { display: grid; grid-template-columns: 2.4rem 1fr; column-gap: 1rem; position: relative; }
455
- .station .nodecol { display: flex; flex-direction: column; align-items: center; }
456
- .node {
457
- width: 2.1rem; height: 2.1rem; border-radius: 50%; flex: none; z-index: 1;
458
- display: grid; place-items: center; position: relative;
459
- font-family: var(--mono); font-weight: 600; font-size: .85rem;
460
- background: var(--surface); border: 1.5px solid var(--border); color: var(--faint);
461
- transition: background .3s, border-color .3s, color .3s;
462
- }
463
- .node svg { width: 15px; height: 15px; display: none; }
464
- .station[data-st="done"] .node { background: var(--accent); border-color: var(--accent); color: #fff; }
465
- .station[data-st="done"] .node svg { display: block; }
466
- .station[data-st="done"] .node .num { display: none; }
467
- .station[data-st="open"] .node { border-color: var(--accent); color: var(--accent-ink); box-shadow: 0 0 0 4px var(--accent-wash); }
468
- .station[data-st="next"] .node { border-color: var(--accent-line); color: var(--accent-ink); background: var(--accent-wash); animation: nodeBreathe 1.8s ease-in-out infinite; }
469
- @keyframes nodeBreathe {
470
- 0%, 100% { box-shadow: 0 0 0 3px var(--accent-wash); }
471
- 50% { box-shadow: 0 0 0 8px var(--accent-wash); }
472
- }
473
- .station[data-st="locked"] .node { opacity: .55; }
474
- /* the segment of rail below each node — liveness flows down each lit segment */
475
- .seg { flex: 1; width: 2px; min-height: 1.2rem; background: var(--border); margin: 4px 0; border-radius: 1px; }
476
- #st-next .seg { visibility: hidden; } /* last station: no line below */
477
- .seg[data-lit="1"] { background: var(--accent-line); }
478
- .seg[data-lit="1"][data-flow="1"] {
479
- background: repeating-linear-gradient(180deg, var(--accent) 0 5px, var(--accent-line) 5px 11px);
480
- animation: flow 1.1s linear infinite;
481
- }
482
- @keyframes flow { to { background-position: 0 11px; } }
483
- .seg[data-lit="1"][data-flow="0"] {
484
- background: repeating-linear-gradient(180deg, var(--faint) 0 4px, transparent 4px 10px);
485
- opacity: .5;
486
- }
487
- .s-body { padding-bottom: 1.5rem; min-width: 0; }
488
-
489
- /* spark burst on celebration — radiates from the node itself */
490
- .spark { position: absolute; left: 50%; top: 50%; width: 6px; height: 6px; border-radius: 2px; pointer-events: none; opacity: 0; }
491
- .station.celebrating .spark { animation: sparkOut .9s ease-out forwards; }
492
- @keyframes sparkOut {
493
- 0% { opacity: 1; transform: translate(-50%, -50%); }
494
- 100% { opacity: 0; transform: translate(calc(-50% + var(--dx) * 34px), calc(-50% + var(--dy) * 34px)) rotate(140deg); }
495
- }
496
- .station.celebrating .node { animation: stamp .5s cubic-bezier(.2,.9,.3,1.4); }
497
- @keyframes stamp { 0% { transform: scale(.7); } 60% { transform: scale(1.18); } 100% { transform: scale(1); } }
498
-
499
- /* collapsed done row — lives IN sequence on the rail (replaces the done-bars) */
500
- .s-done {
501
- display: flex; align-items: baseline; gap: .6rem; width: 100%; text-align: left;
502
- background: none; border: none; border-radius: var(--r-sm); padding: .35rem .4rem .35rem 0; cursor: pointer;
503
- font-family: var(--body);
504
- }
505
- .s-done .lab { font-family: var(--display); font-weight: 600; color: var(--ink-2); font-size: .92rem; }
506
- .s-done .meta { font-family: var(--mono); font-size: .74rem; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
507
- .s-done .reopen { margin-left: auto; flex: none; font-family: var(--mono); font-size: .7rem; color: var(--accent-ink); opacity: 0; transition: opacity .12s; }
508
- .s-done:hover .reopen, .s-done:focus-visible .reopen { opacity: 1; }
509
- .s-done:hover .lab { color: var(--ink); }
510
-
511
- /* locked label */
512
- .s-locked { padding: .38rem 0; font-family: var(--display); font-weight: 600; font-size: .92rem; color: var(--faint); }
513
- .s-locked .sub { display: block; font: 400 .76rem/1.4 var(--body); color: var(--faint); }
514
-
515
- /* next-up teaser — sits at its own station on the rail */
516
- .s-tease {
517
- background: var(--accent-wash); border: 1px solid var(--accent-line); border-radius: var(--r-lg);
518
- padding: 1rem 1.15rem; box-shadow: var(--sh-sm); animation: flowRise var(--flow-dur) var(--flow-ease);
519
- }
520
- .t-kick { font-family: var(--mono); font-size: .66rem; text-transform: uppercase; letter-spacing: .13em; color: var(--accent-ink); }
521
- .s-tease h3 { font-family: var(--display); font-size: 1.1rem; font-weight: 700; color: var(--ink); margin: .3rem 0 .25rem; letter-spacing: -.02em; }
522
- .s-tease p { font-size: .88rem; color: var(--ink-2); margin: 0 0 .9rem; max-width: 46ch; }
523
- .s-tease p code { font-family: var(--mono); font-size: .86em; background: var(--bg-2); border-radius: var(--r-sm); padding: .06rem .3rem; color: var(--ink); }
524
- .t-cta {
525
- display: inline-flex; align-items: center; gap: .5rem;
526
- font: 700 .88rem/1 var(--display); background: var(--accent); color: #fff; border: none;
527
- padding: .66rem 1.15rem; border-radius: var(--r-full); cursor: pointer;
528
- box-shadow: 0 8px 22px -10px rgba(14,159,142,.8); transition: background .12s, transform .12s;
529
- animation: ctaPulse 1.8s ease-in-out infinite;
530
- }
531
- .t-cta:hover { background: var(--accent-ink); transform: translateY(-1px); }
532
-
533
- /* the Step-1 "went quiet" note, ported from the mockup (sits above the re-inflated Step 1) */
534
- .quiet-note { display: flex; gap: .5rem; align-items: baseline; margin: 0 0 1rem; padding: .6rem .8rem; border-radius: var(--r-md); background: var(--amber-wash); border: 1px solid var(--amber-line); color: var(--amber); font-size: .86rem; }
535
- .quiet-note code { font-family: var(--mono); }
536
-
537
- @media (prefers-reduced-motion: reduce) {
538
- .station[data-st="next"] .node, .seg[data-lit="1"][data-flow="1"],
539
- .t-cta, .s-tease, .station.celebrating .node, .station.celebrating .spark { animation: none !important; }
540
- }
541
-
542
- /* ---- Step 2 "lay it beside your editor" tip ---- */
543
- .tip-lay { margin: 1rem 0 0; font-size: .9rem; color: var(--muted); line-height: 1.5; }
544
- .tip-branch { margin: .5rem 0 0; font-size: .9rem; color: var(--muted); line-height: 1.5; }
545
- .tip-branch a { color: inherit; text-decoration: underline; }
546
-
547
- /* ---- Guided accordion: a step body animates IN when opened (collapse is instant
548
- + synchronous so the flow can't get stuck). Shared with isLocalDevLoop —
549
- left on its original timing so that variant is untouched. ---- */
550
- .stage.expanding { animation: stageExpand .42s ease; }
551
- @keyframes stageExpand {
552
- from { opacity: 0; transform: translateY(-8px); }
553
- to { opacity: 1; transform: none; }
554
- }
555
-
556
- /* Hosted guided-flow reveal (flowRise): the activity ledger settles into place
557
- with the same motion a step body uses to open — so moving between Steps 1–3
558
- reads as a single, deliberate transition. Scoped to the hosted id, so the
559
- shared isLocalDevLoop variant is left untouched. */
560
- #hostedActivity {
561
- animation: flowRise var(--flow-dur) var(--flow-ease);
562
- }
563
- @keyframes flowRise {
564
- from { opacity: 0; transform: translateY(calc(var(--flow-rise) * -1)); }
565
- to { opacity: 1; transform: none; }
566
- }
567
-
568
- /* CTA pulse — used by the rail teaser CTAs (.t-cta). */
569
- @keyframes ctaPulse {
570
- 0%, 100% { box-shadow: 0 0 0 0 rgba(14,159,142,.5); }
571
- 50% { box-shadow: 0 0 0 8px rgba(14,159,142,0); }
572
- }
573
- @media (prefers-reduced-motion: reduce) {
574
- .stage.expanding, #hostedActivity { animation: none; }
575
- }
576
-
577
- /* ---- "coming soon" dialog (Step 3 "Submit for preview") ---- */
578
- .cs-dialog {
579
- border: 1px solid var(--border); border-radius: var(--r-xl); padding: 0;
580
- max-width: 30rem; width: calc(100% - 2rem);
581
- background: var(--surface); color: var(--ink);
582
- box-shadow: var(--sh-lg, 0 24px 60px -20px rgba(10,37,64,.5));
583
- }
584
- .cs-dialog::backdrop { background: rgba(10,37,64,.42); }
585
- .cs-body { padding: 1.8rem 1.7rem; text-align: center; }
586
- .cs-emoji { font-size: 2rem; display: block; margin-bottom: .5rem; }
587
- .cs-body h2 { font-family: var(--display); font-size: 1.15rem; font-weight: 700; margin: 0 0 .5rem; color: var(--ink); }
588
- .cs-body p { font-size: .92rem; color: var(--muted); line-height: 1.55; margin: 0 0 1.3rem; }
589
-
590
- /* ---- Make an Edit (G2 — experimental, collapsed in-browser editor) ---- */
591
- .exp-badge {
592
- align-self: center; font-family: var(--mono); font-size: .6rem; font-weight: 500;
593
- text-transform: uppercase; letter-spacing: .12em; color: var(--amber);
594
- background: var(--amber-wash); border: 1px solid var(--amber-line);
595
- border-radius: var(--r-full); padding: .18rem .55rem;
596
- }
597
- .reveal-btn {
598
- margin-top: 1.1rem; display: inline-flex; align-items: center; gap: .5rem;
599
- font: 700 .9rem/1 var(--display); background: var(--accent); color: #fff; border: none;
600
- padding: .72rem 1.25rem; border-radius: var(--r-full); cursor: pointer;
601
- box-shadow: 0 8px 22px -10px rgba(14,159,142,.8); transition: background .12s, transform .12s;
602
- }
603
- .reveal-btn:hover { background: var(--accent-ink); transform: translateY(-1px); }
604
- .reveal-btn svg { width: 15px; height: 15px; }
605
- @media (prefers-reduced-motion: reduce) { .reveal-btn:hover { transform: none; } }
606
-
607
- /* ---- stage card ---- */
608
- .stage {
609
- background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-xl);
610
- box-shadow: var(--sh-md); overflow: hidden; position: relative;
611
- }
612
- .stage::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 3px; background: linear-gradient(90deg, var(--accent), var(--accent-line)); }
613
- .stage-body { padding: 1.6rem 1.6rem 1.7rem; }
614
- @media (max-width: 34rem) { .stage-body { padding: 1.2rem 1.1rem 1.3rem; } }
615
- .kicker {
616
- font-family: var(--mono); font-size: .72rem; font-weight: 500; text-transform: uppercase;
617
- letter-spacing: .15em; color: var(--accent-ink); display: inline-flex; align-items: center; gap: .5rem; margin: 0 0 .5rem;
618
- }
619
- .stage h1 {
620
- font-family: var(--display); font-weight: 800; color: var(--ink); letter-spacing: -.03em;
621
- font-size: clamp(1.5rem, 3.4vw, 1.95rem); margin: 0; text-wrap: balance;
622
- display: flex; align-items: center; gap: .55rem; flex-wrap: wrap;
623
- }
624
- .stage .goal { margin: .55rem 0 1.3rem; color: var(--muted); max-width: 48ch; }
625
-
626
- /* info icon + popover */
627
- .info { position: relative; display: inline-flex; }
628
- .info-btn {
629
- width: 1.4rem; height: 1.4rem; border-radius: 50%; border: 1.5px solid var(--border);
630
- background: var(--surface); color: var(--muted); font-family: var(--display); font-weight: 700;
631
- font-size: .82rem; cursor: pointer; display: grid; place-items: center; line-height: 1;
632
- }
633
- .info-btn:hover { border-color: var(--accent-line); color: var(--accent-ink); }
634
- .info-pop {
635
- position: absolute; top: calc(100% + .5rem); left: 0; width: min(21rem, 78vw); z-index: 30;
636
- background: var(--ink); color: #e7eef6; font-size: .82rem; line-height: 1.55;
637
- border-radius: var(--r-md); padding: .85rem 1rem; box-shadow: var(--sh-lg);
638
- opacity: 0; visibility: hidden; transform: translateY(-4px); transition: opacity .16s, transform .16s, visibility .16s;
639
- }
640
- .info-pop code { font-family: var(--mono); font-size: .78rem; color: var(--term-prompt); }
641
- .info.open .info-pop { opacity: 1; visibility: visible; transform: translateY(0); }
642
- @media (prefers-color-scheme: dark) { .info-pop { background: #04101c; } }
643
- :root[data-theme="dark"] .info-pop { background: #04101c; }
644
- :root[data-theme="light"] .info-pop { background: var(--ink); }
645
-
646
- /* ---- terminal ---- */
647
- .term {
648
- background: linear-gradient(180deg, var(--term) 0%, var(--term-2) 100%);
649
- border: 1px solid var(--term-line); border-radius: var(--r-lg); box-shadow: var(--sh-md);
650
- overflow: hidden; font-family: var(--mono); font-size: .82rem; line-height: 1.7;
651
- }
652
- .term-bar { display: flex; align-items: center; gap: .5rem; padding: .6rem .85rem; border-bottom: 1px solid var(--term-line); background: rgba(255,255,255,.02); }
653
- .term-bar .dots { display: flex; gap: .4rem; }
654
- .term-bar .dot { width: 11px; height: 11px; border-radius: 50%; }
655
- .dot.r { background: #ff5f56; } .dot.y { background: #ffbd2e; } .dot.g { background: #27c93f; }
656
- .term-bar .ttl { margin-left: .4rem; font-size: .72rem; color: var(--term-dim); }
657
- /* Standard "copy this code block" affordance, docked in the terminal chrome.
658
- Revealed once the command is minted; its icon→check swap is the obvious,
659
- inline copy confirmation (no reliance on the transient toast). */
660
- .term-copy {
661
- margin-left: auto; display: inline-flex; align-items: center; justify-content: center;
662
- width: 30px; height: 26px; padding: 0; border-radius: var(--r-sm); cursor: pointer;
663
- background: rgba(255,255,255,.04); border: 1px solid var(--term-line);
664
- color: var(--term-dim); transition: color .12s, background .12s, border-color .12s;
665
- }
666
- .term-copy:hover { color: var(--term-fg); background: rgba(255,255,255,.09); border-color: var(--term-dim); }
667
- .term-copy svg { width: 15px; height: 15px; }
668
- .term-copy .ic-check { display: none; }
669
- .term-copy[data-copied="1"] { color: var(--term-ok); border-color: var(--term-ok); }
670
- .term-copy[data-copied="1"] .ic-copy { display: none; }
671
- .term-copy[data-copied="1"] .ic-check { display: block; }
672
- /* Rendezvous approval widget — the confirm-your-terminal step after pasting. */
673
- .rzv { margin-top: 14px; }
674
- .rzv[hidden] { display: none; }
675
- .rzv-wait { display: flex; align-items: center; gap: 10px; font-size: .92rem; opacity: .8; }
676
- .rzv-confirm { border: 1px solid var(--term-ok, #0e9f8e); border-radius: 12px; padding: 16px 18px; background: color-mix(in srgb, var(--term-ok, #0e9f8e) 8%, transparent); }
677
- .rzv-q { margin: 0 0 6px; font-weight: 600; }
678
- .rzv-fp { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 1.75rem; letter-spacing: .12em; text-align: center; margin: 8px 0 16px; }
679
- .rzv-actions { display: flex; gap: 10px; flex-wrap: wrap; }
680
- .rzv-actions .cta { margin: 0; }
681
- .rzv-deny { background: none; border: 1px solid currentColor; opacity: .7; border-radius: 8px; padding: 8px 14px; cursor: pointer; color: inherit; font: inherit; }
682
- .rzv-deny:hover { opacity: 1; }
683
- .rzv-deny[disabled], .rzv-actions .cta[disabled] { opacity: .5; cursor: default; }
684
- .rzv-done { margin-top: 12px; font-weight: 600; }
685
- .term-body { padding: 1rem 1.05rem 1.1rem; color: var(--term-fg); white-space: pre-wrap; word-break: break-word; }
686
- /* block, not inline — Astro trims the whitespace text node between the spans, so the
687
- line break must be structural or the comment + command collapse onto one line. */
688
- .term-body .c { display: block; color: var(--term-dim); }
689
- .term-body .cmd { display: block; color: #fff; }
690
-
691
- .cta {
692
- display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
693
- font-family: var(--display); font-weight: 700; font-size: .95rem; background: var(--accent); color: #fff;
694
- border: none; padding: .82rem 1.3rem; border-radius: var(--r-full); cursor: pointer;
695
- box-shadow: 0 8px 22px -10px rgba(14,159,142,.8); transition: transform .12s, background .12s; width: 100%;
696
- }
697
- .cta:hover { transform: translateY(-1px); background: var(--accent-ink); }
698
- .cta:disabled { opacity: .7; cursor: default; transform: none; }
699
- .cta svg { width: 16px; height: 16px; }
700
- .cmd-actions { margin-top: .85rem; }
701
- .cmd-note { margin: .8rem 0 0; font: 500 .8rem/1.5 var(--mono); color: var(--muted); }
702
- .cmd-note em { color: var(--ink-2); font-style: normal; font-weight: 600; }
703
- .cmd-steps { margin: 1rem 0 0; font-size: .85rem; color: var(--muted); }
704
- .cmd-steps code { font-family: var(--mono); font-size: .86em; background: var(--bg-2); border-radius: var(--r-sm); padding: .08rem .34rem; color: var(--ink); }
705
- .cmd-steps a { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 2px; }
706
-
707
- /* ---- live loop status bar (the Monitor — C1) ---- */
708
- .loopbar {
709
- margin-top: 1.4rem; background: var(--surface); border: 1px solid var(--border);
710
- border-radius: var(--r-lg); box-shadow: var(--sh-sm); overflow: hidden;
711
- max-height: 0; opacity: 0; transition: max-height .4s ease, opacity .3s ease, margin .3s;
712
- }
713
- .loopbar.live { max-height: 40rem; opacity: 1; }
714
- .lb-head { display: flex; align-items: center; gap: .8rem; padding: .8rem 1.1rem; cursor: pointer; }
715
- .lb-head .pill {
716
- display: inline-flex; align-items: center; gap: .5rem; font-family: var(--mono); font-size: .74rem;
717
- padding: .32rem .7rem; border-radius: var(--r-full); border: 1px solid var(--accent-line);
718
- background: var(--accent-wash); color: var(--accent-ink); white-space: nowrap;
719
- }
720
- .lb-head .pill .beat { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
721
- .loopbar[data-state="running"] .pill .beat { animation: beat 1.6s ease-in-out infinite; }
722
- .loopbar[data-state="reloading"] .pill .beat { animation: beat .5s ease-in-out infinite; }
723
- .loopbar[data-state="stale"] .pill { border-color: var(--amber-line); background: var(--amber-wash); color: var(--amber); }
724
- .loopbar[data-state="stale"] .pill .beat { background: var(--amber); }
725
- @keyframes beat { 0%,100% { box-shadow: 0 0 0 0 rgba(14,159,142,.5); } 50% { box-shadow: 0 0 0 5px rgba(14,159,142,0); } }
726
- .lb-head .lu { font-family: var(--mono); font-size: .76rem; color: var(--muted); }
727
- .lb-head .lu b { color: var(--ink); font-weight: 500; }
728
- .lb-head .chev { margin-left: auto; color: var(--faint); transition: transform .18s; }
729
- .loopbar.open .lb-head .chev { transform: rotate(90deg); }
730
- .lb-detail { display: none; padding: 0 1.1rem; }
731
- .loopbar.open .lb-detail { display: block; padding-bottom: .9rem; }
732
- .lb-row { display: grid; grid-template-columns: 1rem 8rem 1fr; gap: .6rem; align-items: baseline; padding: .5rem 0; border-top: 1px solid var(--border-2); }
733
- .lb-row .ic { color: var(--accent); font-size: .78rem; }
734
- .lb-row .k { font-size: .8rem; color: var(--muted); }
735
- .lb-row .v { font-family: var(--mono); font-size: .8rem; color: var(--ink); font-variant-numeric: tabular-nums; word-break: break-all; }
736
- .lb-row .v.dim { color: var(--faint); }
737
- @media (prefers-reduced-motion: reduce) { .loopbar .pill .beat { animation: none !important; } }
738
-
739
- /* ---- stepper (U2) ---- */
740
- .stepper { display: flex; align-items: stretch; gap: 0; margin-bottom: 2rem; }
741
- .snode {
742
- flex: 1; display: flex; align-items: center; gap: .6rem; background: none; border: none;
743
- font-family: var(--body); text-align: left; cursor: default; padding: .2rem 0; position: relative; color: var(--faint);
744
- }
745
- .snode .dot {
746
- width: 2rem; height: 2rem; border-radius: 50%; flex: none; display: grid; place-items: center;
747
- font-family: var(--mono); font-weight: 600; font-size: .85rem;
748
- background: var(--surface); border: 1.5px solid var(--border); color: var(--faint);
749
- transition: background .25s ease, border-color .25s ease, color .25s ease;
750
- }
751
- .snode .lab { display: flex; flex-direction: column; line-height: 1.2; }
752
- .snode .lab .t { font-family: var(--display); font-weight: 600; font-size: .9rem; color: var(--muted); transition: color .25s; }
753
- .snode .lab .s { font-size: .72rem; color: var(--faint); }
754
- .snode .line { position: absolute; top: 1rem; left: 2.6rem; right: .4rem; height: 1.5px; background: var(--border); z-index: -1; }
755
- .snode:last-child .line { display: none; }
756
- .snode[data-st="active"] .dot { background: var(--accent); border-color: var(--accent); color: #fff; box-shadow: 0 0 0 4px var(--accent-wash); }
757
- .snode[data-st="active"] .lab .t { color: var(--ink); }
758
- .snode[data-st="done"] { cursor: pointer; }
759
- .snode[data-st="done"] .dot { background: var(--accent-wash); border-color: var(--accent-line); color: var(--accent-ink); }
760
- .snode[data-st="done"] .line { background: var(--accent-line); }
761
- .snode[data-st="done"] .lab .t { color: var(--ink-2); }
762
- .snode[data-st="done"]:hover .dot { border-color: var(--accent); }
763
- .snode[data-st="locked"] { opacity: .65; }
764
-
765
- /* ---- collapsed step bar (U2) ---- */
766
- .done-bar {
767
- display: flex; align-items: center; gap: .7rem; width: 100%; text-align: left;
768
- background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md);
769
- padding: .8rem 1rem; margin-bottom: 1rem; cursor: pointer; font-family: var(--body);
770
- transition: border-color .12s;
771
- }
772
- .done-bar:hover { border-color: var(--accent-line); }
773
- .done-bar .chk { width: 1.4rem; height: 1.4rem; border-radius: 50%; background: var(--accent-wash); border: 1px solid var(--accent-line); color: var(--accent-ink); display: grid; place-items: center; font-size: .8rem; flex: none; }
774
- .done-bar .lab { font-family: var(--display); font-weight: 600; color: var(--ink-2); font-size: .9rem; }
775
- .done-bar .meta { font-family: var(--mono); font-size: .75rem; color: var(--faint); }
776
- .done-bar .reopen { margin-left: auto; font-family: var(--mono); font-size: .72rem; color: var(--accent-ink); }
777
-
778
- /* ---- waiting / detect panel (U2) ---- */
779
- .detect {
780
- margin-top: 1.1rem; border: 1px dashed var(--border); border-radius: var(--r-md);
781
- padding: .95rem 1.05rem; display: flex; align-items: center; gap: .8rem; background: var(--bg-2);
782
- }
783
- .detect .radar { width: 12px; height: 12px; border-radius: 50%; background: var(--faint); flex: none; position: relative; }
784
- .detect .radar::after { content: ""; position: absolute; inset: -5px; border-radius: 50%; border: 1.5px solid var(--faint); opacity: 0; }
785
- .detect[data-listening="true"] .radar { background: var(--accent); }
786
- .detect[data-listening="true"] .radar::after { border-color: var(--accent); animation: ping 1.6s ease-out infinite; }
787
- @keyframes ping { 0% { transform: scale(.6); opacity: .7; } 100% { transform: scale(1.8); opacity: 0; } }
788
- .detect .txt { font-size: .86rem; color: var(--muted); flex: 1; }
789
- .detect .txt b { color: var(--ink-2); font-weight: 600; }
790
-
791
- /* ---- celebrate banner (U2) ---- */
792
- .celebrate {
793
- display: flex; align-items: center; gap: .7rem; background: var(--accent-wash);
794
- border: 1px solid var(--accent-line); color: var(--accent-ink); border-radius: var(--r-md);
795
- padding: .8rem 1rem; margin-bottom: 1.2rem; font-weight: 500; font-size: .92rem;
796
- }
797
- .celebrate .chk {
798
- width: 1.5rem; height: 1.5rem; border-radius: 50%; background: var(--accent); color: #fff; flex: none;
799
- display: grid; place-items: center; font-size: .85rem;
800
- }
801
- .pop-in { animation: popin .4s cubic-bezier(.2,.9,.3,1.3); }
802
- @keyframes popin { 0% { transform: scale(.9); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
803
-
804
- /* ---- stage nav buttons (U2) ---- */
805
- .btnrow { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1.3rem; align-items: center; }
806
- .btn {
807
- font-family: var(--display); font-weight: 600; font-size: .85rem; background: var(--surface); color: var(--ink-2);
808
- cursor: pointer; border: 1px solid var(--border); border-radius: var(--r-full); padding: .6rem 1rem;
809
- display: inline-flex; align-items: center; gap: .45rem; transition: border-color .12s, color .12s, background .12s;
810
- }
811
- .btn:hover { border-color: var(--accent-line); color: var(--ink); }
812
- .btn.ghost { background: transparent; }
813
- .btn:disabled { opacity: .5; cursor: not-allowed; }
814
- .btn svg { width: 15px; height: 15px; }
815
- .navspace { margin-left: auto; }
816
-
817
- /* ---- future / roadmap note (U2) ---- */
818
- .future {
819
- margin-top: 1.2rem; border: 1px dashed var(--accent-line); background: var(--accent-wash);
820
- border-radius: var(--r-md); padding: .85rem 1rem; font-size: .84rem; color: var(--accent-ink);
821
- display: flex; gap: .6rem; align-items: flex-start;
822
- }
823
- .future b { font-weight: 700; }
824
- .future .tag { font-family: var(--mono); font-size: .64rem; text-transform: uppercase; letter-spacing: .1em; border: 1px solid var(--accent-line); border-radius: var(--r-full); padding: .1rem .45rem; flex: none; }
825
-
826
- /* C8 · live-preview iframe — click an element here to jump to its source */
827
- .preview-embed { margin-top: 1.1rem; border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; background: var(--surface); }
828
- .pe-bar { display: flex; align-items: center; gap: .5rem; padding: .5rem .7rem; border-bottom: 1px solid var(--border-2); background: var(--bg-2); }
829
- .pe-bar .dots { display: flex; gap: .35rem; }
830
- .pe-bar .dot { width: 10px; height: 10px; border-radius: 50%; }
831
- .pe-url { flex: 1; font-family: var(--mono); font-size: .72rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
832
- .pe-expand { font-family: var(--display); font-weight: 600; font-size: .74rem; color: var(--ink-2); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-full); padding: .3rem .7rem; cursor: pointer; }
833
- .pe-expand:hover { border-color: var(--accent-line); color: var(--ink); }
834
- .pe-frame { width: 100%; height: 320px; border: 0; background: #fff; display: block; }
835
- .pe-hint { padding: .5rem .8rem; font-size: .78rem; color: var(--muted); border-top: 1px solid var(--border-2); background: var(--bg-2); }
836
- .pe-hint b { color: var(--ink-2); font-weight: 600; }
837
- dialog.pe-dialog { width: min(94vw, 1200px); height: min(90vh, 820px); max-width: none; max-height: none; padding: 0; border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--sh-lg); background: var(--surface); }
838
- dialog.pe-dialog[open] { display: flex; flex-direction: column; }
839
- dialog.pe-dialog::backdrop { background: rgba(10, 37, 64, .55); }
840
- .ped-frame { flex: 1; width: 100%; border: 0; background: #fff; }
841
-
842
- /* C3 · Files tab — in-browser file tree + read-only viewer */
843
- .devfiles { margin-top: 1.1rem; border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; background: var(--surface); }
844
- .df-head { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; padding: .55rem .85rem; border-bottom: 1px solid var(--border-2); background: var(--bg-2); }
845
- .df-title { font-weight: 700; font-size: .82rem; color: var(--ink); }
846
- .df-path { font-family: var(--mono); font-size: .72rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
847
- .df-body { display: grid; grid-template-columns: minmax(160px, 240px) 1fr; min-height: 260px; max-height: 420px; }
848
- .df-tree { overflow: auto; border-right: 1px solid var(--border-2); padding: .35rem 0; font-size: .8rem; }
849
- .df-main { display: flex; flex-direction: column; min-width: 0; background: var(--term); }
850
- .df-editbar { flex: none; display: flex; gap: .7rem; align-items: center; min-height: 30px; padding: .35rem .8rem; border-bottom: 1px solid var(--term-line); background: var(--term-2); font-size: .72rem; color: var(--term-dim); }
851
- .df-status { font-family: var(--mono); }
852
- .df-status.ok { color: var(--term-ok); }
853
- .df-status.warn { color: #f6b0b0; }
854
- .df-conflict { display: inline-flex; gap: .4rem; align-items: center; color: #f6b0b0; }
855
- .df-mini { font-family: var(--mono); font-size: .7rem; padding: .1rem .45rem; border-radius: var(--r-sm); border: 1px solid var(--term-line); background: transparent; color: var(--term-fg); cursor: pointer; }
856
- .df-mini:hover { background: var(--term-line); }
857
- .df-view { flex: 1 1 auto; width: 100%; box-sizing: border-box; display: block; margin: 0; border: 0; resize: none; outline: none; overflow: auto; padding: .75rem .9rem; font-family: var(--mono); font-size: .76rem; line-height: 1.55; color: var(--term-fg); background: var(--term); white-space: pre; tab-size: 2; }
858
- .df-view[readonly] { opacity: .9; }
859
- .df-diffbtn { margin-left: auto; }
860
- .df-publishbtn { border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
861
- .df-publishbtn:hover { background: var(--accent-wash); }
862
- .df-publishbtn:disabled { opacity: .6; cursor: default; }
863
- .df-diff { flex: 1 1 auto; margin: 0; overflow: auto; padding: .5rem 0; font-family: var(--mono); font-size: .76rem; line-height: 1.5; background: var(--term); color: var(--term-fg); }
864
- .df-diff .dl { display: block; padding: 0 .9rem; white-space: pre; }
865
- .df-diff .add { background: rgba(52, 211, 153, .16); color: #bff2e0; }
866
- .df-diff .del { background: rgba(246, 176, 176, .15); color: #f6c2c2; }
867
- .df-diff .ctx { color: var(--term-dim); }
868
- .ft-root, .ft-children { list-style: none; margin: 0; padding: 0; }
869
- .ft-row { display: flex; align-items: center; gap: .4rem; padding: .18rem .5rem .18rem 0; cursor: default; border-radius: var(--r-sm); }
870
- .ft-file .ft-row { cursor: pointer; }
871
- .ft-file .ft-row:hover { background: var(--accent-wash); }
872
- .ft-row.sel { background: var(--accent-wash); color: var(--accent-ink); font-weight: 600; }
873
- .ft-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
874
- .ft-ic { font-size: .78rem; flex: none; opacity: .8; }
875
- .ft-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
876
- .future-note { margin-top: .8rem; font-size: .8rem; color: var(--muted); display: flex; gap: .5rem; align-items: baseline; }
877
- .future-note .tag { font-family: var(--mono); font-size: .64rem; text-transform: uppercase; letter-spacing: .1em; border: 1px solid var(--accent-line); border-radius: var(--r-full); padding: .1rem .45rem; flex: none; color: var(--accent-ink); }
878
- @media (max-width: 560px) { .df-body { grid-template-columns: 1fr; } .df-view { max-height: 220px; } }
879
- @media (prefers-reduced-motion: reduce) {
880
- .detect[data-listening="true"] .radar::after, .pop-in { animation: none !important; }
881
- }
882
-
883
- /* ---- Ask (quiet loop-recovery help, U3) ---- */
884
- .ask-fab {
885
- position: fixed; right: 1.4rem; bottom: 1.4rem; z-index: 50;
886
- display: inline-flex; align-items: center; gap: .5rem;
887
- background: var(--surface); color: var(--ink-2); border: 1px solid var(--border);
888
- border-radius: var(--r-full); padding: .6rem .95rem; cursor: pointer; box-shadow: var(--sh-md);
889
- font-family: var(--body); font-size: .85rem; transition: border-color .12s, color .12s;
890
- }
891
- .ask-fab:hover { border-color: var(--accent-line); color: var(--ink); }
892
- .ask-fab .u { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--accent-line); }
893
- .ask-fab svg { width: 16px; height: 16px; color: var(--accent); }
894
- .ask-panel {
895
- position: fixed; right: 1.4rem; bottom: 4.6rem; z-index: 51; width: min(23rem, 90vw);
896
- background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
897
- box-shadow: var(--sh-lg); overflow: hidden; transform-origin: bottom right;
898
- opacity: 0; visibility: hidden; transform: translateY(8px) scale(.98); transition: opacity .16s, transform .16s, visibility .16s;
899
- }
900
- .ask-panel.open { opacity: 1; visibility: visible; transform: translateY(0) scale(1); }
901
- .ask-head { padding: .95rem 1.1rem; border-bottom: 1px solid var(--border-2); }
902
- .ask-head strong { font-family: var(--display); color: var(--ink); font-size: .98rem; display: block; }
903
- .ask-head span { font-size: .8rem; color: var(--muted); }
904
- .qa { padding: .3rem .5rem .6rem; max-height: 60vh; overflow-y: auto; }
905
- .qa details { border-bottom: 1px solid var(--border-2); }
906
- .qa details:last-child { border-bottom: none; }
907
- .qa summary { list-style: none; cursor: pointer; padding: .7rem .6rem; display: flex; align-items: center; gap: .6rem; font-size: .88rem; color: var(--ink); }
908
- .qa summary::-webkit-details-marker { display: none; }
909
- .qa summary .q { font-family: var(--mono); color: var(--accent); font-size: .78rem; flex: none; }
910
- .qa summary .chev { margin-left: auto; color: var(--faint); transition: transform .18s; }
911
- .qa details[open] summary .chev { transform: rotate(90deg); }
912
- .qa .ans { padding: 0 .6rem .85rem 1.9rem; color: var(--muted); font-size: .84rem; }
913
- .qa .ans code { font-family: var(--mono); font-size: .8rem; color: var(--ink-2); background: var(--bg-2); padding: .05em .4em; border-radius: 5px; }
914
- .qa .ans b { color: var(--ink-2); font-weight: 600; }
915
- .ask-foot { padding: .7rem 1.1rem; border-top: 1px solid var(--border-2); font-family: var(--mono); font-size: .68rem; color: var(--faint); }
916
- @media (prefers-reduced-motion: reduce) { .ask-panel { transition: none; } }
917
-
918
- /* ---- connect the MCP (d3-unhold-dev-step0-connect-mcp) ---- */
919
- #stage-mcp { margin-top: 2rem; }
920
- /* disable ligatures so `--transport` renders as literal characters, not a glyph */
921
- #mcpCmdText { font-variant-ligatures: none; font-feature-settings: "liga" 0, "calt" 0; }
922
-
923
- /* ---- what's next : coming soon shelf (U4) ---- */
924
- .next { margin-top: 3.4rem; padding-top: 2rem; border-top: 1px dashed var(--border); }
925
- .next h3 { font-family: var(--display); font-weight: 700; color: var(--ink-2); font-size: 1.1rem; margin: 0 0 .3rem; letter-spacing: -.02em; }
926
- .next .sub { margin: 0 0 1.2rem; font-size: .88rem; color: var(--muted); max-width: 52ch; }
927
- .nextgrid { display: grid; gap: .7rem; }
928
- @media (min-width: 40rem) { .nextgrid { grid-template-columns: 1fr 1fr; } }
929
- @media (min-width: 60rem) { .nextgrid { grid-template-columns: repeat(4, 1fr); } }
930
- .nextcard {
931
- display: flex; flex-direction: column; gap: .3rem; background: transparent;
932
- border: 1px solid var(--border); border-radius: var(--r-md); padding: .9rem 1rem; opacity: .78;
933
- }
934
- .nextcard .path { font-family: var(--mono); font-size: .8rem; color: var(--muted); }
935
- .nextcard .d { font-size: .8rem; color: var(--faint); }
936
- .nextcard .soon { align-self: flex-start; margin-top: .3rem; font-family: var(--mono); font-size: .64rem; text-transform: uppercase; letter-spacing: .1em; color: var(--faint); border: 1px solid var(--border); border-radius: var(--r-full); padding: .1rem .5rem; }
937
-
938
- /* ---- footer ---- */
939
- .foot {
940
- margin-top: 2.8rem; padding-top: 1.2rem; border-top: 1px solid var(--border);
941
- display: flex; flex-wrap: wrap; gap: 1rem 1.4rem; align-items: center;
942
- font-family: var(--mono); font-size: .78rem; color: var(--muted);
943
- }
944
- .foot .sp { margin-left: auto; }
945
- .foot a { color: var(--muted); text-decoration: none; }
946
- .foot a:hover { color: var(--accent-ink); }
947
-
948
- /* ---- toast ---- */
949
- .toast {
950
- position: fixed; left: 50%; bottom: 1.6rem; transform: translateX(-50%) translateY(160%);
951
- /* Terminal palette (dark bg + light fg in BOTH themes) — a plain var(--ink)
952
- background went white-on-white in dark mode. */
953
- background: var(--term); color: var(--term-fg); border: 1px solid var(--term-line);
954
- font-family: var(--mono); font-size: .8rem;
955
- padding: .7rem 1.1rem; border-radius: var(--r-full); box-shadow: var(--sh-lg);
956
- transition: transform .28s cubic-bezier(.2,.9,.3,1.2); z-index: 60; pointer-events: none;
957
- display: inline-flex; align-items: center; gap: .5rem;
958
- }
959
- .toast.show { transform: translateX(-50%) translateY(0); }
960
- .toast .ok { color: var(--term-ok); }
961
- @media (prefers-reduced-motion: reduce) {
962
- .cta, .toast, .notice a.action, .info-pop { transition: none; }
963
- }
964
- [hidden] { display: none !important; }
965
- </style>
966
- </head>
967
- <body
968
- data-cockpit-api-base={apiBase}
969
- data-cockpit-is-dev={isLocalDevLoop ? "true" : "false"}
970
- data-cockpit-app-domain={appDomain}
971
- data-cockpit-base-path={basePath}
972
- >
973
- <div class="wrap">
974
- <header class="topbar">
975
- <div class="brand">
976
- <span class="mark" aria-hidden="true">
977
- <svg viewBox="0 0 24 24" fill="none"><path d="M12 2.5 20 6v6c0 4.6-3.2 7.9-8 9.5-4.8-1.6-8-4.9-8-9.5V6l8-3.5Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="m8.5 12 2.4 2.4L15.8 9.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
978
- </span>
979
- <span class="who"><span class="dom">{appDomain}</span><span class="sub">Developer Cockpit</span></span>
980
- </div>
981
- <div class="right">
982
- {session?.email ? <span class="sess">{session.email}</span> : null}
983
- <button class="themetog" id="themeBtn" type="button">◐ Theme</button>
984
- </div>
985
- </header>
986
-
987
- {/* U1 anchor — name the surface so a developer knows which page is "home". */}
988
- <p class="cockpit-lede">This is your <strong>Developer Cockpit</strong> — your home base while your store spins up.</p>
989
-
990
- {signedIn === false && (
991
- <div class="notice">
992
- {/* d1 (board: d1-unmask-invite-email) — the invitee owns this address (the
993
- link was sent to it), so masking it back to them is pointless friction.
994
- Show the FULL invite email when we actually have it; NEVER show the redacted
995
- hint to its owner. This branch is signed-OUT, so there's no session.email to
996
- read; the recovery link arrives carrying only a redacted `email_hint` (the
997
- full address is masked UPSTREAM by the CLI before it ever reaches this page —
998
- see report), so the full email is genuinely unavailable here and we fall back
999
- to neutral copy rather than re-displaying the redacted form. `recoveryEmailFull`
1000
- lights up automatically the moment a full address is available. */}
1001
- <p>{isCredentialRecovery
1002
- ? recoveryEmailFull
1003
- ? <>Sign in with your invite email, <strong>{recoveryEmailFull}</strong>, to generate a fresh setup command for this machine.</>
1004
- : <>Sign in with the same email that accepted the invite to generate a fresh setup command for this machine.</>
1005
- : <>Sign in with the email your invite came to — then this page shows your store and your one command.</>}</p>
1006
- <a class="action" href={loginHref}>Sign in</a>
1007
- </div>
1008
- )}
1009
-
1010
- {noStoreAccess && (
1011
- <div class="notice">
1012
- {noResolvableStore
1013
- ? <>
1014
- <p>You're signed in{session?.email ? <>{" "}as <strong>{session.email}</strong></> : null}, but this page doesn't have a store to set up yet. Pick one from your dashboard to get its setup command.</p>
1015
- <a class="action" href="/dashboard">Pick a store</a>
1016
- </>
1017
- : <>
1018
- <p>You're signed in{session?.email ? <>{" "}as <strong>{session.email}</strong></> : null}, but this account isn't linked to a store yet. Ask whoever sent your invite to grant developer access — then reload this page.</p>
1019
- <a class="action" href="/auth/logout">Switch account</a>
1020
- </>}
1021
- </div>
1022
- )}
1023
-
1024
- {/* STORE-NOT-SET-UP notice — a real, signed-in developer whose store's repo isn't
1025
- provisioned yet. We deliberately withhold the setup command here (canSetupStore
1026
- is false, so the mint fastpath + activity rail below don't render) rather than
1027
- hand out a `tot start` that would die at checkout. Honest, low-friction copy:
1028
- nothing for them to do but wait + reload. */}
1029
- {storeNotSetUp && (
1030
- <div class="notice">
1031
- <p>You're all set{session?.email ? <>{" "}as <strong>{session.email}</strong></> : null} — but <strong>{appDomain}</strong>'s store is still being set up. Your one-paste setup command appears here the moment it's ready. Nothing to do on your end; check back shortly, or ask your Token of Trust contact if it's urgent.</p>
1032
- <a class="action" href="/cockpit">Reload</a>
1033
- </div>
1034
- )}
1035
-
1036
- {/* Bridge strip (replaces the big live-dev-link card) — the rail's power source,
1037
- fed by the activity poll's `runtime`. Hidden until the store has been seen
1038
- live this page-view (sawLive); localhost is the developer's OWN machine, so
1039
- clicking is a plain navigation. Pulses teal while live; turns amber with a
1040
- recovery hint when the loop goes quiet (data-live="0"). */}
1041
- {hostedActivity && (
1042
- <section class="bridge" id="bridge" data-live="1" hidden>
1043
- <span class="beacon" aria-hidden="true"></span>
1044
- <div class="b-txt" id="bTxt">
1045
- <span class="b-kicker" id="bKicker">Live on your machine</span>
1046
- <a class="b-url" id="bUrl" href="#" target="_blank" rel="noreferrer">http://localhost/</a>
1047
- </div>
1048
- <span class="b-diag" id="bDiag" hidden></span>
1049
- <a class="b-go" id="bGo" href="#" target="_blank" rel="noreferrer">Open your store ↗</a>
1050
- <p class="b-hint" id="bHint" hidden>Your store went quiet. Run <code>tot start</code> in your project to bring it back — Step 1 has the command.</p>
1051
- {/* Obstacle lane (v2 §6): a failed `tot start` (wrong Node, missing pnpm,
1052
- install/clone error) beacons why via the setup command's activity token;
1053
- the strip becomes an actionable fix card. Text is set via textContent from
1054
- the SERVER-composed remediation copy (obstacleStore) — never innerHTML. */}
1055
- <div class="b-obstacle" id="bObstacle" hidden>
1056
- <span class="b-kicker b-ob-title" id="bObTitle">Setup hit a snag</span>
1057
- <p class="b-ob-msg" id="bObMsg"></p>
1058
- <p class="b-ob-fix"><span class="b-ob-lead">Fix:</span> <code class="b-ob-cmd" id="bObCmd" hidden></code> <span id="bObNote"></span></p>
1059
- </div>
1060
- </section>
1061
- )}
1062
-
1063
- {isLocalDevLoop && (
1064
- <nav class="stepper" id="stepper" aria-label="Setup progress">
1065
- <button class="snode" type="button" data-go="run" data-st="active">
1066
- <span class="dot">1</span><span class="lab"><span class="t">Get running</span><span class="s">on your machine</span></span><span class="line"></span>
1067
- </button>
1068
- <button class="snode" type="button" data-go="edit" data-st="locked">
1069
- <span class="dot">2</span><span class="lab"><span class="t">Make an edit</span><span class="s">see it reload</span></span><span class="line"></span>
1070
- </button>
1071
- <button class="snode" type="button" data-go="next" data-st="locked">
1072
- <span class="dot">3</span><span class="lab"><span class="t">What's next</span><span class="s">coming soon</span></span>
1073
- </button>
1074
- </nav>
1075
- )}
1076
-
1077
- {isLocalDevLoop && (
1078
- <button class="done-bar" id="bar-run" type="button" data-go="run" hidden>
1079
- <span class="chk">✓</span><span class="lab">Store running</span><span class="meta" id="bar-run-meta">on your machine</span><span class="reopen">reopen</span>
1080
- </button>
1081
- )}
1082
- {isLocalDevLoop && (
1083
- <button class="done-bar" id="bar-edit" type="button" data-go="edit" hidden>
1084
- <span class="chk">✓</span><span class="lab">First edit landed</span><span class="meta" id="bar-edit-meta">content/home.html</span><span class="reopen">reopen</span>
1085
- </button>
1086
- )}
1087
-
1088
- {/* Station 1 — SHARED across all variants. #stage-run stays rendered exactly
1089
- once with its inner content unchanged; class:list gives non-hosted variants
1090
- neutral wrappers (.strun has no CSS), while hosted gets the rail station grid
1091
- + node column + collapsible done row. */}
1092
- <section id="st-run" class:list={["strun", { station: hostedActivity }]} data-st="open">
1093
- {hostedActivity && (
1094
- <div class="nodecol">
1095
- <span class="node" aria-hidden="true"><span class="num">1</span><svg viewBox="0 0 24 24" fill="none"><path d="m5 12.5 4.5 4.5L19 7.5" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg><span class="spark" style="--dx:1;--dy:-.4;background:#0e9f8e"></span><span class="spark" style="--dx:-1;--dy:-.7;background:#2fd4bf"></span><span class="spark" style="--dx:.6;--dy:1;background:#f0c56b"></span><span class="spark" style="--dx:-.7;--dy:.8;background:#0e9f8e"></span><span class="spark" style="--dx:0;--dy:-1.1;background:#2fd4bf"></span><span class="spark" style="--dx:1.1;--dy:.5;background:#b7791f"></span></span>
1096
- <span class="seg" id="seg-run" data-lit="0" data-flow="0"></span>
1097
- </div>
1098
- )}
1099
- <div class:list={{ "s-body": hostedActivity }}>
1100
- {hostedActivity && (
1101
- <button class="s-done" id="done-run" type="button" hidden>
1102
- <span class="lab">Store running</span><span class="meta" id="doneRunMeta"></span><span class="reopen">Reopen ↓</span>
1103
- </button>
1104
- )}
1105
- {hostedActivity && (
1106
- <p class="quiet-note" id="quietNote" hidden><span aria-hidden="true">◍</span><span>Your store went quiet — it stopped answering from your machine. <code>tot start</code> brings it back.</span></p>
1107
- )}
1108
- <section class="stage" id="stage-run">
1109
- <div class="stage-body">
1110
- <span class="kicker">Step 1</span>
1111
- <h1>Get your store running.
1112
- <span class="info" id="info1">
1113
- <button class="info-btn" type="button" aria-label="What does this command do?">i</button>
1114
- <span class="info-pop" role="tooltip">Installs the <code>tot</code> command, signs your terminal in with a single-use code, checks out your store into a new <code>{appDomain}</code> folder, runs it, and opens your browser. No Docker, no browser sign-in, nothing to provision. From then on it's just <code>tot start</code>.</span>
1115
- </span>
1116
- </h1>
1117
- <p class="goal">{canSetupStore
1118
- ? isCredentialRecovery
1119
- ? <>Generate a fresh personal setup command, then paste it into your terminal and press Enter.</>
1120
- : <>Generate your personal setup command, then paste it into your terminal and press Enter.</>
1121
- : storeNotSetUp
1122
- ? <>We're finishing setting up <strong>{appDomain}</strong>. Your one-paste setup command appears here the moment it's ready — check back shortly.</>
1123
- : <>Open the invite link we emailed you to unlock your personal setup command.</>}</p>
1124
-
1125
- <div class="term">
1126
- <div class="term-bar"><span class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></span><span class="ttl">your terminal</span>{canSetupStore && (
1127
- <button class="term-copy" id="copyIcon" type="button" aria-label="Copy the command" title="Copy the command" hidden>
1128
- <svg class="ic-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
1129
- <svg class="ic-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
1130
- </button>
1131
- )}</div>
1132
- <div class="term-body"><span class="c"># your personal, single-use setup command</span>
1133
- <span class="cmd is-placeholder" id="cli-cmd-text">{canSetupStore ? (isCredentialRecovery ? "→ click “Generate a fresh setup command” below" : "→ click “Generate my setup command” below") : storeNotSetUp ? "→ available once your store finishes setting up" : "→ available once you’re signed in via your invite"}</span></div>
1134
- </div>
1135
-
1136
- {canSetupStore && (
1137
- <div class="cmd-actions">
1138
- <button class="cta" id="copyCmd" type="button" data-fastpath="1" data-base-path={basePath}>
1139
- <svg viewBox="0 0 24 24" fill="none"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
1140
- {isCredentialRecovery ? "Generate a fresh setup command" : "Generate my setup command"}
1141
- </button>
1142
- </div>
1143
- )}
1144
-
1145
- {/* Rendezvous approval — after you paste the command, your terminal connects here and
1146
- you confirm the code it printed matches (the load-bearing security step: it's how
1147
- you prove it's YOUR terminal, not an interceptor's). Hidden until a command is
1148
- generated; the client script drives the wait → confirm → done states. */}
1149
- {canSetupStore && (
1150
- <div class="rzv" id="rzvApprove" hidden aria-live="polite">
1151
- <div class="rzv-wait" id="rzvWait" hidden>
1152
- <span class="radar" aria-hidden="true"></span>
1153
- <span>Waiting for your terminal to connect — run the pasted command, then come back here.</span>
1154
- </div>
1155
- <div class="rzv-confirm" id="rzvConfirm" hidden>
1156
- <p class="rzv-q">Your terminal is asking to sign in. Confirm the code it printed matches:</p>
1157
- <p class="rzv-fp" id="rzvFp">····-····</p>
1158
- <div class="rzv-actions">
1159
- <button class="cta" id="rzvApproveBtn" type="button">Yes — that's my terminal, approve</button>
1160
- <button class="rzv-deny" id="rzvDenyBtn" type="button">Not me — reject</button>
1161
- </div>
1162
- </div>
1163
- <p class="rzv-done" id="rzvDone" hidden></p>
1164
- </div>
1165
- )}
1166
- <p class="cmd-note" id="cli-cmd-note">{canSetupStore
1167
- ? <>This mints a <em>single-use, short-lived</em> sign-in code, shows the full command here, and copies it to your clipboard. Paste it <em>from the folder where you keep projects</em> — it creates a <code>{appDomain}</code> folder there with your store's code, signs you in, runs your store, and opens your browser. Nothing to retype.</>
1168
- : storeNotSetUp
1169
- ? <>Nothing to do on your end — a Token of Trust reviewer is finishing your store's first setup. Once it's ready, reload and this page hands you the one-paste command.</>
1170
- : <>You'll need <em>Node.js 22.12+ (24 LTS recommended) and your invite</em>. Sign in through your invite link and this page hands you a one-paste setup command — no Docker, nothing to provision.</>}</p>
1171
-
1172
- <p class="cmd-steps">Prefer the steps? <code>npm i -g {cliInstallSpec}</code>, then <code>tot clone {appDomain}</code>, <code>cd {appDomain}</code>, <code>tot dev</code>. New to Node? Get Node 24 (LTS) at <a href="https://nodejs.org">nodejs.org</a> — it includes <code>npm</code>.</p>
1173
-
1174
- {isLocalDevLoop && (
1175
- <div class="detect" id="detect" data-listening="true">
1176
- <span class="radar" aria-hidden="true"></span>
1177
- <span class="txt" id="detectTxt">Watching for your store to come online on this machine…</span>
1178
- </div>
1179
- )}
1180
- </div>
1181
- </section>
1182
- </div>
1183
- </section>
1184
-
1185
- {/* Station 2 — hosted-only. Locked label → live-gated teaser → open step body →
1186
- collapsed done row, all on the rail. */}
1187
- {hostedActivity && (
1188
- <section class="station" id="st-edit" data-st="locked">
1189
- <div class="nodecol">
1190
- <span class="node" aria-hidden="true"><span class="num">2</span><svg viewBox="0 0 24 24" fill="none"><path d="m5 12.5 4.5 4.5L19 7.5" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg><span class="spark" style="--dx:1;--dy:-.4;background:#0e9f8e"></span><span class="spark" style="--dx:-1;--dy:-.7;background:#2fd4bf"></span><span class="spark" style="--dx:.6;--dy:1;background:#f0c56b"></span><span class="spark" style="--dx:-.7;--dy:.8;background:#0e9f8e"></span><span class="spark" style="--dx:0;--dy:-1.1;background:#2fd4bf"></span><span class="spark" style="--dx:1.1;--dy:.5;background:#b7791f"></span></span>
1191
- <span class="seg" id="seg-edit" data-lit="0" data-flow="0"></span>
1192
- </div>
1193
- <div class="s-body">
1194
- <div class="s-locked" id="lock-edit">Change one line <span class="sub">unlocks when your store is running</span></div>
1195
- <button class="s-done" id="done-edit" type="button" hidden>
1196
- <span class="lab">First edit landed</span><span class="meta" id="doneEditMeta">content/home.html</span><span class="reopen">Reopen ↓</span>
1197
- </button>
1198
- <div class="s-tease" id="tease-edit" hidden>
1199
- <span class="t-kick">Next up · Step 2</span>
1200
- <h3>Change one line.</h3>
1201
- <p>Open <code>content/home.html</code>, edit anything, save. Your store reloads itself and the save shows up here.</p>
1202
- <button class="t-cta" id="cta-edit" type="button">Make your first edit →</button>
1203
- </div>
1204
- <section class="stage" id="hosted-stage-edit" hidden>
1205
- <div class="stage-body">
1206
- <p class="ha-encourage"><span class="chk">✓</span><span>You're running locally — nice. One more step: make an edit and watch it land here.</span></p>
1207
- <span class="kicker">Step 2</span>
1208
- <h1>Change one line.</h1>
1209
- <p class="goal">Open <code>content/home.html</code> in your editor, change one line, and save. Your store reloads on its own, and the save shows up in <strong>Your activity</strong> below within a couple of seconds.</p>
1210
- <div class="term">
1211
- <div class="term-bar"><span class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></span><span class="ttl">open the file</span></div>
1212
- <div class="term-body"><span class="c"># open your homepage content to edit</span>
1213
- <span class="cmd" id="editOpenCmd">$EDITOR content/home.html</span></div>
1214
- </div>
1215
- <p class="cmd-note" id="editOpenHint">Tip: set <code>$EDITOR</code> and we'll show the exact open command here.</p>
1216
- <p class="tip-lay">Lay this cockpit <em>beside</em> your editor (or an AI coding agent like Claude — same idea) and your running store — hot reload is on, so every save reloads the store and lands in <strong>Your activity</strong> here, live. Keep the terminal running <code>tot start</code>/<code>tot dev</code> open too, watching for build errors — need the command line for something else? Open a new terminal instead of closing that one.</p>
1217
- <p class="tip-branch">No need to think about git branches for this — just keep editing and running <code>tot preview</code>; it updates the same change every time. <a href="https://github.com/tokenoftrust/storefront/blob/feature/8500-storefront-integration/docs/kb/develop-ship-and-connect-domain.md#branching-is-an-advanced-feature--you-almost-never-need-it" target="_blank" rel="noopener">Working on two things at once? Read this first →</a></p>
1218
- </div>
1219
- </section>
1220
- </div>
1221
- </section>
1222
- )}
1223
-
1224
- {/* Station 3 — hosted-only. Its .seg is the last on the rail (hidden via CSS). */}
1225
- {hostedActivity && (
1226
- <section class="station" id="st-next" data-st="locked">
1227
- <div class="nodecol">
1228
- <span class="node" aria-hidden="true"><span class="num">3</span><svg viewBox="0 0 24 24" fill="none"><path d="m5 12.5 4.5 4.5L19 7.5" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
1229
- <span class="seg"></span>
1230
- </div>
1231
- <div class="s-body">
1232
- <div class="s-locked" id="lock-next">Send it for review <span class="sub">unlocks after your first edit</span></div>
1233
- <div class="s-tease" id="tease-next" hidden>
1234
- <span class="t-kick">Next up · Step 3</span>
1235
- <h3>Send your change for review.</h3>
1236
- <p>Bundle what you've edited into a compliance-reviewed preview build.</p>
1237
- <button class="t-cta" id="cta-next" type="button">Go to Step 3 →</button>
1238
- </div>
1239
- <section class="stage" id="hosted-stage-next" hidden>
1240
- <div class="stage-body">
1241
- <span class="kicker">Step 3</span>
1242
- <h1>Submit your change for preview.</h1>
1243
- <p class="goal">You've got the loop: edit locally, see it reflected here. When a change is ready, <code>tot preview</code> bundles it into a compliance-reviewed preview build; once that preview looks right, <code>tot ship</code> promotes it live.</p>
1244
- <div class="term">
1245
- <div class="term-bar"><span class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></span><span class="ttl">preview</span></div>
1246
- <div class="term-body"><span class="c"># bundle your local changes into a reviewable preview</span>
1247
- <span class="cmd">tot preview</span></div>
1248
- </div>
1249
- <div class="cmd-actions">
1250
- <button class="cta" id="submitPreview" type="button">
1251
- <svg viewBox="0 0 24 24" fill="none"><path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
1252
- Submit for preview
1253
- </button>
1254
- </div>
1255
- <p class="cmd-note">This packages your changes and kicks off a compliance-reviewed preview build. Once it reconciles cleanly, <code>tot ship</code> promotes it live — it always shows you the diff and asks for a confirm first.</p>
1256
- </div>
1257
- </section>
1258
- </div>
1259
- </section>
1260
- )}
1261
-
1262
- {/* Activity ledger — positioned AFTER the stations. Keeps the ids the activity
1263
- renderer targets (#haTable/#haBody/#haEmpty/#haToggle). Celebration is now the
1264
- Step-2 node stamp + toast, so the old inline banner is gone. */}
1265
- {hostedActivity && (
1266
- <section class="hosted-activity" id="hostedActivity" hidden>
1267
- <div class="ha-head">
1268
- <h2>Your activity</h2>
1269
- <span class="ha-sub">reported by <code>tot dev</code> on your machine</span>
1270
- </div>
1271
- <p class="ha-empty" id="haEmpty">No saves yet — that's Step 2. Edit a file locally and it appears here.</p>
1272
- <table class="ha-table" id="haTable" hidden>
1273
- <tbody id="haBody"></tbody>
1274
- </table>
1275
- <button class="ha-toggle" id="haToggle" type="button" hidden></button>
1276
- </section>
1277
- )}
1278
-
1279
- {isLocalDevLoop && (
1280
- <section class="stage" id="stage-edit" hidden>
1281
- <div class="stage-body">
1282
- <div class="celebrate pop-in" id="cel-run" hidden>
1283
- <span class="chk">✓</span><span>You're live — your store is running on this machine. Now let's change something.</span>
1284
- </div>
1285
- <span class="kicker">Step 2</span>
1286
- <h1>Make your first edit.</h1>
1287
- <p class="goal">Open a content file in your editor, change one line, and save. Your store reloads on its own — watch the loop react below.</p>
1288
-
1289
- <div class="term">
1290
- <div class="term-bar"><span class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></span><span class="ttl">your editor</span></div>
1291
- <div class="term-body"><span class="c"># open your store's homepage source, edit the headline, save</span>
1292
- <span class="cmd">content/home.html</span></div>
1293
- </div>
1294
-
1295
- <div class="detect" id="detect-edit" data-listening="true">
1296
- <span class="radar" aria-hidden="true"></span>
1297
- <span class="txt" id="detectEditTxt">Waiting for your first save… save a watched file and it shows up here instantly.</span>
1298
- </div>
1299
-
1300
- <div class="preview-embed" id="previewEmbed">
1301
- <div class="pe-bar">
1302
- <span class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></span>
1303
- <span class="pe-url" id="previewUrl">localhost preview</span>
1304
- <button type="button" class="pe-expand" id="previewExpand">⤢ Full screen</button>
1305
- </div>
1306
- {/* src is set client-side from /__tot/dev-loop-status (the DEV tenant) —
1307
- NOT the SSR appDomain, which runs in workerd and can't see TOT_DEV_TENANT. */}
1308
- <iframe class="pe-frame" id="previewFrame" title="live preview" loading="lazy"></iframe>
1309
- <p class="pe-hint">Your running store. Open the <b>✦</b> widget inside it, hit <b>◎ Locate</b>, then click any element — its source file opens below with the cursor on that line.</p>
1310
- </div>
1311
-
1312
- <div class="devfiles" id="devfiles">
1313
- <div class="df-head">
1314
- <span class="df-title">Your store's files</span>
1315
- <span class="df-path" id="filesPath">select a file to view</span>
1316
- </div>
1317
- <div class="df-body">
1318
- <div class="df-tree" id="filesTree">Loading your files…</div>
1319
- <div class="df-main">
1320
- <div class="df-editbar" id="filesEditbar" hidden>
1321
- <span class="df-status" id="filesStatus"></span>
1322
- <span class="df-conflict" id="filesConflict" hidden>Changed on disk.
1323
- <button type="button" class="df-mini" id="filesReload">Reload</button>
1324
- <button type="button" class="df-mini" id="filesForce">Overwrite</button>
1325
- </span>
1326
- <button type="button" class="df-mini df-diffbtn" id="filesDiffBtn" hidden>Diff</button>
1327
- <button type="button" class="df-mini df-publishbtn" id="filesPublishBtn" hidden>Publish</button>
1328
- </div>
1329
- <textarea class="df-view" id="filesView" readonly spellcheck="false" wrap="off">Pick a file on the left to view its source.</textarea>
1330
- <pre class="df-diff" id="filesDiff" hidden></pre>
1331
- </div>
1332
- </div>
1333
- </div>
1334
- <p class="future-note">
1335
- <span class="tag">Soon</span> Live editing is on. A visual diff and click-an-element-to-jump-to-source layer onto this panel next.
1336
- </p>
1337
-
1338
- <div class="btnrow">
1339
- <button class="btn ghost" id="backToRun" type="button" data-go="run">← Back</button>
1340
- <button class="btn ghost navspace" id="toNext" type="button" data-go="next" disabled>Next: what's next →</button>
1341
- </div>
1342
- </div>
1343
- </section>
1344
- )}
1345
-
1346
- {isLocalDevLoop && (
1347
- <section class="stage" id="stage-next" hidden>
1348
- <div class="stage-body">
1349
- <div class="celebrate pop-in" id="cel-edit" hidden>
1350
- <span class="chk">✓</span><span>You changed a file and saw it on your site. That's the loop — you're in control.</span>
1351
- </div>
1352
- <span class="kicker">You're in control</span>
1353
- <h1>You've got the loop.</h1>
1354
- <p class="goal">Edit, save, see it — with no terminal round-trip. Previews, publish checks, AI assistance, and diagnostics open up from here soon.</p>
1355
- <div class="btnrow">
1356
- <button class="btn ghost" id="backToEdit" type="button" data-go="edit">← Back to editing</button>
1357
- </div>
1358
- </div>
1359
- </section>
1360
- )}
1361
-
1362
- {isLocalDevLoop && (
1363
- <div class="loopbar" id="loopbar" data-state="running" aria-live="polite">
1364
- <div class="lb-head" id="lbHead">
1365
- <span class="pill"><span class="beat"></span><span id="lbStatus">Connecting…</span></span>
1366
- <span class="lu">watching <b id="lbWatch">your content</b> · <span id="lbLast">connecting…</span></span>
1367
- <span class="chev">›</span>
1368
- </div>
1369
- <div class="lb-detail">
1370
- <div class="lb-row"><span class="ic">✓</span><span class="k">Local server</span><span class="v dim" id="lbServer">—</span></div>
1371
- <div class="lb-row"><span class="ic">↗</span><span class="k">Current URL</span><span class="v dim" id="lbUrl">—</span></div>
1372
- <div class="lb-row"><span class="ic">✎</span><span class="k">Watched file</span><span class="v dim" id="lbFile">content/*.html</span></div>
1373
- <div class="lb-row"><span class="ic">◷</span><span class="k">Last save seen</span><span class="v dim" id="lbSave" data-ts="">no saves yet</span></div>
1374
- <div class="lb-row"><span class="ic">⟳</span><span class="k">Last reload sent</span><span class="v dim" id="lbReload" data-ts="">waiting for a save</span></div>
1375
- <div class="lb-row"><span class="ic">≡</span><span class="k">Source</span><span class="v dim">your working tree — not published</span></div>
1376
- </div>
1377
- </div>
1378
- )}
1379
-
1380
- {/* d3-unhold-dev-step0-connect-mcp: un-held now that MCP access follows the tot20
1381
- developer grant (federated sign-in / 8425 Stage B is live) — an invited developer
1382
- already has what this needs, so it's no longer aspirational. Copy/command carried
1383
- over from the pre-U1-strip /dev (dev.astro @ 110f73e); the "FUNCTIONALLY PENDING"
1384
- caveat from e58eab5 is dropped since the prerequisite is now real. Static, renders
1385
- unconditionally like the "next" teaser grid below — not gated on capability. */}
1386
- <section class="stage" id="stage-mcp">
1387
- <div class="stage-body">
1388
- <span class="kicker">Connect the MCP</span>
1389
- <h1>Bring your AI assistant into the loop.</h1>
1390
- <p class="goal">The Regulated Storefront is a native AI platform — connecting the Token of Trust MCP lets Claude, Cursor, or any MCP-speaking assistant work your store directly. It works with every major AI platform. On Claude:</p>
1391
- <div class="term">
1392
- <div class="term-bar">
1393
- <span class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></span><span class="ttl">Claude Code</span>
1394
- <button class="term-copy" id="mcpCopyIcon" type="button" aria-label="Copy the command" title="Copy the command">
1395
- <svg class="ic-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
1396
- <svg class="ic-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
1397
- </button>
1398
- </div>
1399
- <div class="term-body"><span class="c"># add the Token of Trust MCP</span>
1400
- <span class="cmd" id="mcpCmdText">claude mcp add --transport http tot https://mcp.tokenoftrust.com/mcp</span>
1401
- <span class="c">&nbsp;</span>
1402
- <span class="c"># then run /mcp in Claude and sign in with your invite email</span></div>
1403
- </div>
1404
- <p class="cmd-note">Other assistants: point them at <code>https://mcp.tokenoftrust.com/mcp</code>. Sign in with the email your invite came to — your access follows your developer grant on this store.</p>
1405
- </div>
1406
- </section>
1407
-
1408
- {/* Static "coming soon" cards — pure /dev rendering with no dependency on the
1409
- isLocalDevLoop-only /__tot/* endpoints and no need for the activity bridge, so
1410
- it renders unconditionally on the deployed hosted panel (and locally). Not
1411
- gated on capability either — it's a teaser of what's next. */}
1412
- <section class="next">
1413
- <h3>The rest of the workflow</h3>
1414
- <p class="sub">Previews, publish checks, AI assistance, and diagnostics. These open up once you've got the loop — coming soon.</p>
1415
- <div class="nextgrid">
1416
- <div class="nextcard"><span class="path">Share a preview</span><span class="d">Put a branch or PR up at a shareable URL.</span><span class="soon">Coming soon</span></div>
1417
- <div class="nextcard"><span class="path">Publish readiness</span><span class="d">What happens if you publish today — checks &amp; blockers.</span><span class="soon">Coming soon</span></div>
1418
- <div class="nextcard"><span class="path">Troubleshooting</span><span class="d">Doctor, logs, server health, and auth checks.</span><span class="soon">Coming soon</span></div>
1419
- </div>
1420
- </section>
1421
-
1422
- <footer class="foot">
1423
- <span>{session?.email ? <>Signed in as {session.email}</> : <>Not signed in</>}</span>
1424
- <span class="sp"></span>
1425
- {signedIn ? <a href="/auth/logout">Sign out</a> : null}
1426
- </footer>
1427
- </div>
1428
-
1429
- <div class="toast" id="toast"><span class="ok">✓</span><span id="toastMsg">Copied</span></div>
1430
-
1431
- {/* Item 7/10 — Step 3's "Submit for preview" CTA opens this "coming soon" dialog;
1432
- the compliance-reviewed preview pipeline isn't wired into this panel yet. */}
1433
- {hostedActivity && (
1434
- <dialog class="cs-dialog" id="comingSoon" aria-label="Submit for preview — coming soon">
1435
- <div class="cs-body">
1436
- <span class="cs-emoji" aria-hidden="true">🚧</span>
1437
- <h2>Preview submissions are coming soon</h2>
1438
- <p>Submitting a change for a compliance-reviewed preview build isn't wired into this panel yet. For now, keep iterating locally — your saves land in <strong>Your activity</strong> above in real time.</p>
1439
- <button type="button" class="cta" id="comingSoonClose">Got it</button>
1440
- </div>
1441
- </dialog>
1442
- )}
1443
-
1444
- {isLocalDevLoop && (
1445
- <dialog class="pe-dialog" id="previewDialog" aria-label={`${appDomain} — full-screen preview`}>
1446
- <div class="pe-bar">
1447
- <span class="pe-url">{isLocalDevLoop ? "localhost preview" : "your store"} · {appDomain}</span>
1448
- <button type="button" class="pe-expand" id="previewClose">✕ Close</button>
1449
- </div>
1450
- <iframe class="ped-frame" id="previewDialogFrame" title={`${appDomain} — full-screen preview`}></iframe>
1451
- </dialog>
1452
- )}
1453
-
1454
- {isLocalDevLoop && (
1455
- <button class="ask-fab" id="askFab" type="button">
1456
- <svg viewBox="0 0 24 24" fill="none"><path d="M4 5.5A1.5 1.5 0 0 1 5.5 4h13A1.5 1.5 0 0 1 20 5.5v9a1.5 1.5 0 0 1-1.5 1.5H9l-4 3.5v-3.5H5.5A1.5 1.5 0 0 1 4 14.5v-9Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg>
1457
- <span class="u">Stuck?</span> Ask
1458
- </button>
1459
- )}
1460
- {isLocalDevLoop && (
1461
- <div class="ask-panel" id="askPanel" role="dialog" aria-label="Help with the local loop">
1462
- <div class="ask-head"><strong>Ask about the loop</strong><span>Quick answers for "why isn't this working."</span></div>
1463
- <div class="qa">
1464
- <details>
1465
- <summary><span class="q">?</span>Is my local site running?<span class="chev">›</span></summary>
1466
- <div class="ans"><span id="ans-running">Checking…</span></div>
1467
- </details>
1468
- <details>
1469
- <summary><span class="q">?</span>Why didn't my change show up?<span class="chev">›</span></summary>
1470
- <div class="ans"><span id="ans-change">Save a watched file and I'll compare when it was seen vs when your store reloaded.</span></div>
1471
- </details>
1472
- <details>
1473
- <summary><span class="q">?</span>Did the watcher see my save?<span class="chev">›</span></summary>
1474
- <div class="ans"><span id="ans-watcher">The Monitor's <b>Last save seen</b> updates the instant a watched file changes. If it doesn't move after you save, the watcher missed it — usually after a rename or delete. Restart to re-scan.</span></div>
1475
- </details>
1476
- <details>
1477
- <summary><span class="q">?</span>Am I editing the rendered source?<span class="chev">›</span></summary>
1478
- <div class="ans">Yes — you're editing your <b>working tree</b>, the same file the page renders from. Nothing publishes; only your local preview sees it.</div>
1479
- </details>
1480
- <details>
1481
- <summary><span class="q">↻</span>Restart the local preview.<span class="chev">›</span></summary>
1482
- <div class="ans">Reconnects this page to the dev server and re-reads loop state — fixes a preview that's gone stale. If the server is fully down, run <code>tot start</code> in your terminal.
1483
- <div style="margin-top:.6rem"><button class="btn primary" id="restartBtn" type="button" style="background:var(--accent);color:#fff;border-color:transparent">Reconnect preview</button></div>
1484
- </div>
1485
- </details>
1486
- </div>
1487
- <div class="ask-foot">Scoped to the local loop.</div>
1488
- </div>
1489
- )}
1490
-
1491
- <script>
1492
- import { initThemeToggle } from "@/lib/dev/cockpit/themeToggle";
1493
- initThemeToggle();
1494
- </script>
1495
-
1496
- <script>
1497
- import { initMcpCopy } from "@/lib/dev/cockpit/mcpCopy";
1498
- initMcpCopy();
1499
- </script>
1500
-
1501
- <script>
1502
- import { initInfoPopover } from "@/lib/dev/cockpit/infoPopover";
1503
- initInfoPopover();
1504
- </script>
1505
-
1506
- <script>
1507
- import { initCopyCommand } from "@/lib/dev/cockpit/copyCommand";
1508
- initCopyCommand();
1509
- </script>
1510
-
1511
- {hostedActivity && (
1512
- <script>
1513
- import { initHostedActivity } from "@/lib/dev/cockpit/hostedActivity";
1514
- initHostedActivity();
1515
- </script>
1516
- )}
1517
- {isLocalDevLoop && (
1518
- <script>
1519
- import { initStepper } from "@/lib/dev/cockpit/stepper";
1520
- initStepper();
1521
- </script>
1522
- )}
1523
- {isLocalDevLoop && (
1524
- <script>
1525
- import { initAsk } from "@/lib/dev/cockpit/ask";
1526
- initAsk();
1527
- </script>
1528
- )}
1529
- {isLocalDevLoop && (
1530
- <script>
1531
- import { initMonitor } from "@/lib/dev/cockpit/monitor";
1532
- initMonitor();
1533
- </script>
1534
- )}
1535
- {isLocalDevLoop && (
1536
- <script>
1537
- import { initFiles } from "@/lib/dev/cockpit/files";
1538
- initFiles();
1539
- </script>
1540
- )}
1541
- {isLocalDevLoop && (
1542
- <script>
1543
- import { initClickToSource } from "@/lib/dev/cockpit/clickToSource";
1544
- initClickToSource();
1545
- </script>
1546
- )}
1547
- </body>
1548
- </html>