@tokenoftrust/storefront-runner 2.0.1 → 2.0.3

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