@tokenoftrust/storefront-runner 1.4.2-rc.0 → 2.0.0
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.
- package/apps/storefront/astro.config.mjs +3 -3
- package/apps/storefront/env.d.ts +54 -5
- package/apps/storefront/integrations/materialize-guard.mjs +12 -14
- package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
- package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
- package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
- package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
- package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
- package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
- package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
- package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
- package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
- package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
- package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
- package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
- package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
- package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
- package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
- package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
- package/apps/storefront/public/shared/commerce-chrome.css +20 -2
- package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
- package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
- package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
- package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
- package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
- package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
- package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
- package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
- package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
- package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
- package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
- package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
- package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
- package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
- package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
- package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
- package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
- package/apps/storefront/src/components/Img.astro +1 -1
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
- package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
- package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
- package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
- package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
- package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
- package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
- package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
- package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
- package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
- package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
- package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
- package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
- package/apps/storefront/src/components/membership/TierCards.astro +2 -2
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
- package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
- package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
- package/apps/storefront/src/config/devTenantSeed.ts +9 -9
- package/apps/storefront/src/config/resolver.ts +4 -4
- package/apps/storefront/src/layouts/Layout.astro +100 -20
- package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
- package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
- package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
- package/apps/storefront/src/lib/admin/envelope.ts +1 -2
- package/apps/storefront/src/lib/apps/adminService.ts +10 -0
- package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
- package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
- package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
- package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
- package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
- package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
- package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
- package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
- package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
- package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
- package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
- package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
- package/apps/storefront/src/lib/apps/registryService.ts +264 -10
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
- package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
- package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
- package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
- package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
- package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
- package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
- package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
- package/apps/storefront/src/lib/blog/collection.ts +97 -10
- package/apps/storefront/src/lib/blog/markdown.ts +1 -1
- package/apps/storefront/src/lib/blog/provider.ts +57 -3
- package/apps/storefront/src/lib/blog/types.ts +9 -9
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
- package/apps/storefront/src/lib/chrome/model.ts +48 -253
- package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
- package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
- package/apps/storefront/src/lib/chrome/variant.ts +39 -0
- package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
- package/apps/storefront/src/lib/content/index.ts +5 -5
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
- package/apps/storefront/src/lib/content-edit/route.ts +1 -1
- package/apps/storefront/src/lib/d1/catalog.ts +12 -0
- package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
- package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
- package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
- package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
- package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
- package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
- package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
- package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
- package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
- package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
- package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
- package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
- package/apps/storefront/src/lib/edgeCache.ts +18 -6
- package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
- package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
- package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
- package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
- package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
- package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
- package/apps/storefront/src/lib/membership/model.ts +1 -2
- package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
- package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
- package/apps/storefront/src/lib/newsletter/send.ts +3 -3
- package/apps/storefront/src/lib/previewReload.ts +57 -1
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
- package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
- package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
- package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
- package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
- package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
- package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
- package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
- package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
- package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
- package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
- package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
- package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
- package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
- package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
- package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
- package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
- package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
- package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
- package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
- package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
- package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
- package/apps/storefront/src/lib/rawChrome.ts +125 -47
- package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
- package/apps/storefront/src/lib/seo/alternates.ts +1 -1
- package/apps/storefront/src/lib/social/notify.ts +4 -4
- package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
- package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
- package/apps/storefront/src/lib/subscription/model.ts +4 -4
- package/apps/storefront/src/lib/the-build/provider.ts +9 -4
- package/apps/storefront/src/middleware/index.ts +273 -51
- package/apps/storefront/src/pages/[...slug].astro +9 -4
- package/apps/storefront/src/pages/admin/review-release.astro +265 -0
- package/apps/storefront/src/pages/admin/select.astro +96 -0
- package/apps/storefront/src/pages/admin.astro +16 -2
- package/apps/storefront/src/pages/api/activity.ts +8 -8
- package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
- package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
- package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
- package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
- package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
- package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
- package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
- package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
- package/apps/storefront/src/pages/blog/[slug].astro +5 -5
- package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
- package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
- package/apps/storefront/src/pages/blog/index.astro +2 -2
- package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
- package/apps/storefront/src/pages/cockpit.astro +61 -1362
- package/apps/storefront/src/pages/index.astro +10 -5
- package/apps/storefront/src/pages/llms.txt.ts +1 -1
- package/apps/storefront/src/pages/products/[handle].astro +7 -6
- package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
- package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
- package/apps/storefront/src/pages/the-build/index.astro +2 -2
- package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
- package/apps/storefront/src/styles/admin.css +27 -1
- package/apps/storefront/tsconfig.json +1 -1
- package/package.json +1 -1
- package/packages/public-runtime/package.json +2 -1
- package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
- package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
- package/packages/public-runtime/src/activity/catalog.ts +41 -37
- package/packages/public-runtime/src/activity/event.ts +6 -6
- package/packages/public-runtime/src/activity/index.ts +2 -2
- package/packages/public-runtime/src/activity/redaction.ts +4 -4
- package/packages/public-runtime/src/candidate-index.ts +42 -8
- package/packages/public-runtime/src/checkout-orders.ts +2 -2
- package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
- package/packages/public-runtime/src/checkout-style.ts +7 -7
- package/packages/public-runtime/src/checkout.ts +3 -3
- package/packages/public-runtime/src/chrome.ts +777 -0
- package/packages/public-runtime/src/compliance-obligations.ts +310 -0
- package/packages/public-runtime/src/csp.ts +99 -0
- package/packages/public-runtime/src/customization-runtime.ts +32 -1
- package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
- package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
- package/packages/public-runtime/src/embed-catalog.mjs +40 -0
- package/packages/public-runtime/src/evidence.ts +288 -0
- package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
- package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
- package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
- package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
- package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
- package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
- package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
- package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
- package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
- package/packages/public-runtime/src/index.ts +16 -1
- package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
- package/packages/public-runtime/src/integration/assurance.ts +228 -0
- package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
- package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
- package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
- package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
- package/packages/public-runtime/src/integration/consent-config.ts +260 -0
- package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
- package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
- package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
- package/packages/public-runtime/src/integration/csp.ts +105 -0
- package/packages/public-runtime/src/integration/digest.ts +43 -0
- package/packages/public-runtime/src/integration/environment.ts +228 -0
- package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
- package/packages/public-runtime/src/integration/events.ts +701 -0
- package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
- package/packages/public-runtime/src/integration/index.ts +90 -0
- package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
- package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
- package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
- package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
- package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
- package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
- package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
- package/packages/public-runtime/src/integration/manifest.ts +470 -0
- package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
- package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
- package/packages/public-runtime/src/integration/observability.ts +451 -0
- package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
- package/packages/public-runtime/src/integration/phases.ts +182 -0
- package/packages/public-runtime/src/integration/plan.ts +112 -0
- package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
- package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
- package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
- package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
- package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
- package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
- package/packages/public-runtime/src/integration/render-phases.ts +267 -0
- package/packages/public-runtime/src/integration/resolve.ts +465 -0
- package/packages/public-runtime/src/integration/result.ts +239 -0
- package/packages/public-runtime/src/integration/secrets.ts +92 -0
- package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
- package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
- package/packages/public-runtime/src/integration/version.ts +92 -0
- package/packages/public-runtime/src/integration-capabilities.ts +520 -0
- package/packages/public-runtime/src/membership.ts +2 -2
- package/packages/public-runtime/src/raw-html-policy.ts +2 -4
- package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
- package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
- package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
- package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
- package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
- package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
- package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
- package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
- package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
- package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
- package/packages/public-runtime/src/static-bundle.ts +5 -5
- package/packages/public-runtime/src/tenant-assets.ts +143 -4
- package/packages/public-runtime/src/tenant.ts +45 -9
- package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
- package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
- package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
- package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
- package/scripts/dev/file-browser.mjs +2 -2
- package/scripts/dev/file-writer.mjs +2 -2
- package/scripts/dev/integration-guardrails.mjs +498 -0
- package/scripts/dev/safe-path.mjs +2 -2
- package/scripts/dev/tenant-source-root.mjs +1 -1
- package/scripts/tot-dev.mjs +2 -2
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client controller for the merchant Review-and-Release workspace (`review-release.astro`).
|
|
3
|
+
*
|
|
4
|
+
* Reads server-stamped context from the root element, polls `GET /api/changes`,
|
|
5
|
+
* derives the two-decision view model with the pure `@/lib/publish/reviewRelease`
|
|
6
|
+
* seam, renders it with the pure `render.ts`, and wires the ONLY two actions —
|
|
7
|
+
* "Add to next release" and "Publish now". It owns the browser-only concerns the
|
|
8
|
+
* pure modules can't: poll cadence, the `shipInFlight` "Publishing…" transient, the
|
|
9
|
+
* publish confirmation dialog (focus trap + Esc + focus return), and ARIA-live
|
|
10
|
+
* discipline (one announcement per REAL state transition, never per poll tick).
|
|
11
|
+
*
|
|
12
|
+
* Not unit-tested (the storefront test env is `node`, no DOM) — the render + action
|
|
13
|
+
* logic it composes IS tested; this glue is verified on a live preview.
|
|
14
|
+
*/
|
|
15
|
+
import {
|
|
16
|
+
buildReviewReleaseViewModel,
|
|
17
|
+
formatReleaseDate,
|
|
18
|
+
READ_ONLY_NOTE,
|
|
19
|
+
UNDO_TOAST_COPY,
|
|
20
|
+
type ReviewReleaseViewModel,
|
|
21
|
+
type ReviewReleaseContext,
|
|
22
|
+
type ChangesPayload,
|
|
23
|
+
} from "@/lib/publish/reviewRelease";
|
|
24
|
+
import { renderWorkspace, type LiveReleaseView } from "./render.js";
|
|
25
|
+
import { addChangeToRelease, publishNextRelease } from "./actions.js";
|
|
26
|
+
|
|
27
|
+
interface WorkspaceContext {
|
|
28
|
+
changesEndpoint: string;
|
|
29
|
+
acceptEndpoint: string;
|
|
30
|
+
shipEndpoint: string;
|
|
31
|
+
buildEndpoint: string;
|
|
32
|
+
liveHref: string;
|
|
33
|
+
canShip: boolean;
|
|
34
|
+
internalTestStore: boolean;
|
|
35
|
+
storeName: string;
|
|
36
|
+
pollMs: number;
|
|
37
|
+
slowThresholdMs: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DEFAULT_POLL_MS = 8000;
|
|
41
|
+
|
|
42
|
+
export function initReviewReleaseWorkspace(): void {
|
|
43
|
+
const root = document.querySelector<HTMLElement>("[data-rr-root]");
|
|
44
|
+
if (!root) return;
|
|
45
|
+
const workspace = root.querySelector<HTMLElement>("[data-rr-workspace]");
|
|
46
|
+
const announcer = root.querySelector<HTMLElement>("[data-rr-announce]");
|
|
47
|
+
const toast = root.querySelector<HTMLElement>("[data-rr-toast]");
|
|
48
|
+
if (!workspace) return;
|
|
49
|
+
|
|
50
|
+
const ctx: WorkspaceContext = {
|
|
51
|
+
changesEndpoint: root.dataset.changesEndpoint ?? "/api/changes",
|
|
52
|
+
acceptEndpoint: root.dataset.acceptEndpoint ?? "/api/changes/accept",
|
|
53
|
+
shipEndpoint: root.dataset.shipEndpoint ?? "/api/changes/ship",
|
|
54
|
+
buildEndpoint: root.dataset.buildEndpoint ?? "/api/preview/build",
|
|
55
|
+
liveHref: root.dataset.liveHref ?? "/",
|
|
56
|
+
canShip: root.dataset.canShip === "true",
|
|
57
|
+
internalTestStore: root.dataset.internalTestStore === "true",
|
|
58
|
+
storeName: root.dataset.storeName ?? "your store",
|
|
59
|
+
pollMs: Number(root.dataset.pollMs) || DEFAULT_POLL_MS,
|
|
60
|
+
slowThresholdMs: Number(root.dataset.slowThresholdMs) || 120_000,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
let shipInFlight = false;
|
|
64
|
+
let publishSucceeded = false;
|
|
65
|
+
let prevVm: ReviewReleaseViewModel | null = null;
|
|
66
|
+
let lastPayload: ChangesPayload | null = null;
|
|
67
|
+
let busy = false; // an action POST is in flight — suppress a racing re-render
|
|
68
|
+
|
|
69
|
+
// ── Rendering ──────────────────────────────────────────────────────────────
|
|
70
|
+
function render(payload: ChangesPayload): void {
|
|
71
|
+
lastPayload = payload;
|
|
72
|
+
const modelCtx: ReviewReleaseContext = {
|
|
73
|
+
canShip: ctx.canShip,
|
|
74
|
+
internalTestStore: ctx.internalTestStore,
|
|
75
|
+
storeName: ctx.storeName,
|
|
76
|
+
shipInFlight,
|
|
77
|
+
preparingSlowThresholdMs: ctx.slowThresholdMs,
|
|
78
|
+
};
|
|
79
|
+
const vm = buildReviewReleaseViewModel(payload, modelCtx);
|
|
80
|
+
const current = payload.current ?? {};
|
|
81
|
+
const live: LiveReleaseView = {
|
|
82
|
+
versionId: current.versionId ?? null,
|
|
83
|
+
publishedDate: formatReleaseDate(current.createdAt ?? null),
|
|
84
|
+
liveHref: ctx.liveHref,
|
|
85
|
+
};
|
|
86
|
+
workspace!.innerHTML = renderWorkspace(vm, { liveHref: ctx.liveHref, live, publishSucceeded });
|
|
87
|
+
announceTransitions(prevVm, vm);
|
|
88
|
+
prevVm = vm;
|
|
89
|
+
triggerPreparingWork(vm);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── "Preparing" must mean something real is happening, not just a claim ────────
|
|
93
|
+
// A change sits in "preparing" (queued/reconciling/not-built) until something
|
|
94
|
+
// actually invokes the build/reconcile pipeline — no standing background job
|
|
95
|
+
// does that on its own. Kick it off automatically, once per change per page
|
|
96
|
+
// load, so the merchant's "we're working on this" copy is true rather than a
|
|
97
|
+
// dead label waiting on an external webhook that may never arrive.
|
|
98
|
+
const triggeredPreparing = new Set<string>();
|
|
99
|
+
function triggerPreparingWork(vm: ReviewReleaseViewModel): void {
|
|
100
|
+
if (!ctx.canShip) return;
|
|
101
|
+
for (const c of vm.changes) {
|
|
102
|
+
if (c.state !== "preparing") continue;
|
|
103
|
+
const key = c.changeId ?? (c.prNumber != null ? `pr:${c.prNumber}` : null);
|
|
104
|
+
if (!key || triggeredPreparing.has(key)) continue;
|
|
105
|
+
triggeredPreparing.add(key);
|
|
106
|
+
const body: Record<string, unknown> = {};
|
|
107
|
+
if (c.prNumber != null) body.pr = c.prNumber;
|
|
108
|
+
if (c.changeId) body.changeId = c.changeId;
|
|
109
|
+
fetch(ctx.buildEndpoint, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { "content-type": "application/json" },
|
|
112
|
+
body: JSON.stringify(body),
|
|
113
|
+
}).catch(() => {}); // best-effort; the next poll just still shows "Preparing" if it failed
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function renderReadOnlyFallback(): void {
|
|
118
|
+
workspace!.innerHTML =
|
|
119
|
+
`<section class="rr-section"><p class="rr-note">${escapeText(READ_ONLY_NOTE)}</p></section>`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Data ─────────────────────────────────────────────────────────────────────
|
|
123
|
+
async function load(): Promise<void> {
|
|
124
|
+
if (busy) return;
|
|
125
|
+
try {
|
|
126
|
+
const res = await fetch(ctx.changesEndpoint, { headers: { accept: "application/json" } });
|
|
127
|
+
if (res.status === 401 || res.status === 403) {
|
|
128
|
+
renderReadOnlyFallback();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (!res.ok) return; // transient; the next poll retries silently (no merchant error)
|
|
132
|
+
const payload = (await res.json()) as ChangesPayload;
|
|
133
|
+
// A completed ship clears the client transient once the server confirms live.
|
|
134
|
+
if (shipInFlight && payload.aggregate?.queueState === "shipped") shipInFlight = false;
|
|
135
|
+
render(payload);
|
|
136
|
+
} catch {
|
|
137
|
+
// Network blip — leave the last render in place; the poll loop retries.
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── ARIA-live: one announcement per REAL transition ───────────────────────────
|
|
142
|
+
function announceTransitions(prev: ReviewReleaseViewModel | null, next: ReviewReleaseViewModel): void {
|
|
143
|
+
if (!announcer) return;
|
|
144
|
+
const msgs: string[] = [];
|
|
145
|
+
if (!prev || prev.nextRelease.state !== next.nextRelease.state) {
|
|
146
|
+
msgs.push(next.nextRelease.heading);
|
|
147
|
+
}
|
|
148
|
+
if (prev) {
|
|
149
|
+
const before = new Map(prev.changes.map((c) => [c.changeId ?? `pr:${c.prNumber}`, c.state]));
|
|
150
|
+
for (const c of next.changes) {
|
|
151
|
+
const key = c.changeId ?? `pr:${c.prNumber}`;
|
|
152
|
+
const wasState = before.get(key);
|
|
153
|
+
if (wasState && wasState !== c.state) {
|
|
154
|
+
const title = c.title?.trim() || (c.prNumber != null ? `Change #${c.prNumber}` : "A change");
|
|
155
|
+
msgs.push(`${title} is now ${c.label}.`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (msgs.length) setAnnouncement(msgs.join(" "));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function setAnnouncement(text: string): void {
|
|
163
|
+
if (!announcer) return;
|
|
164
|
+
announcer.textContent = "";
|
|
165
|
+
// Force SR pickup of a possibly-repeated message on the next frame.
|
|
166
|
+
window.requestAnimationFrame(() => {
|
|
167
|
+
announcer.textContent = text;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function showToast(text: string): void {
|
|
172
|
+
if (!toast) return;
|
|
173
|
+
toast.textContent = text;
|
|
174
|
+
toast.hidden = false;
|
|
175
|
+
window.setTimeout(() => {
|
|
176
|
+
toast.hidden = true;
|
|
177
|
+
}, 5000);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── Action wiring (event delegation; survives innerHTML re-renders) ────────────
|
|
181
|
+
workspace.addEventListener("click", (ev) => {
|
|
182
|
+
const target = ev.target as HTMLElement;
|
|
183
|
+
const addBtn = target.closest<HTMLButtonElement>("[data-rr-add]");
|
|
184
|
+
if (addBtn && !addBtn.disabled) {
|
|
185
|
+
void handleAdd(addBtn);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (target.closest("[data-rr-publish]")) {
|
|
189
|
+
openConfirm();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
async function handleAdd(btn: HTMLButtonElement): Promise<void> {
|
|
194
|
+
const changeId = btn.dataset.changeId || null;
|
|
195
|
+
const prRaw = btn.dataset.prNumber;
|
|
196
|
+
const prNumber = prRaw && Number.isFinite(Number(prRaw)) ? Number(prRaw) : null;
|
|
197
|
+
btn.disabled = true;
|
|
198
|
+
btn.setAttribute("aria-busy", "true");
|
|
199
|
+
busy = true;
|
|
200
|
+
const result = await addChangeToRelease(
|
|
201
|
+
{ fetch: fetchLike, endpoint: ctx.acceptEndpoint },
|
|
202
|
+
{ changeId, prNumber },
|
|
203
|
+
);
|
|
204
|
+
busy = false;
|
|
205
|
+
if (result.kind === "added") {
|
|
206
|
+
setAnnouncement("Added to your next release.");
|
|
207
|
+
showToast(UNDO_TOAST_COPY);
|
|
208
|
+
} else if (result.kind === "needs-attention") {
|
|
209
|
+
setAnnouncement("That change needs a developer before it can go live. We've let them know.");
|
|
210
|
+
} else {
|
|
211
|
+
setAnnouncement("We couldn't add that just now — it will reappear here if it's still ready.");
|
|
212
|
+
}
|
|
213
|
+
await load();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── Publish confirmation dialog (focus-trapped) ───────────────────────────────
|
|
217
|
+
const confirmEl = root.querySelector<HTMLElement>("[data-rr-confirm]");
|
|
218
|
+
const dialogEl = root.querySelector<HTMLElement>("[data-rr-confirm-dialog]");
|
|
219
|
+
const confirmHeadingEl = root.querySelector<HTMLElement>("[data-rr-confirm-heading]");
|
|
220
|
+
const confirmBodyEl = root.querySelector<HTMLElement>("[data-rr-confirm-body]");
|
|
221
|
+
const confirmOkEl = root.querySelector<HTMLButtonElement>("[data-rr-confirm-ok]");
|
|
222
|
+
const confirmCancelEl = root.querySelector<HTMLButtonElement>("[data-rr-confirm-cancel]");
|
|
223
|
+
let invokingButton: HTMLElement | null = null;
|
|
224
|
+
|
|
225
|
+
function openConfirm(): void {
|
|
226
|
+
if (!confirmEl || !prevVm?.nextRelease.confirm) return;
|
|
227
|
+
const confirm = prevVm.nextRelease.confirm;
|
|
228
|
+
if (confirmHeadingEl) confirmHeadingEl.textContent = confirm.heading;
|
|
229
|
+
if (confirmBodyEl) confirmBodyEl.textContent = confirm.body;
|
|
230
|
+
if (confirmOkEl) confirmOkEl.textContent = confirm.confirm;
|
|
231
|
+
if (confirmCancelEl) confirmCancelEl.textContent = confirm.cancel;
|
|
232
|
+
invokingButton = document.activeElement as HTMLElement;
|
|
233
|
+
confirmEl.hidden = false;
|
|
234
|
+
(confirmOkEl ?? dialogEl)?.focus();
|
|
235
|
+
document.addEventListener("keydown", onDialogKeydown, true);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function closeConfirm(): void {
|
|
239
|
+
if (!confirmEl) return;
|
|
240
|
+
confirmEl.hidden = true;
|
|
241
|
+
document.removeEventListener("keydown", onDialogKeydown, true);
|
|
242
|
+
invokingButton?.focus();
|
|
243
|
+
invokingButton = null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function onDialogKeydown(ev: KeyboardEvent): void {
|
|
247
|
+
if (ev.key === "Escape") {
|
|
248
|
+
ev.preventDefault();
|
|
249
|
+
closeConfirm();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (ev.key === "Tab" && dialogEl) {
|
|
253
|
+
const focusable = dialogEl.querySelectorAll<HTMLElement>(
|
|
254
|
+
'button, [href], input, [tabindex]:not([tabindex="-1"])',
|
|
255
|
+
);
|
|
256
|
+
const first = focusable[0];
|
|
257
|
+
const last = focusable[focusable.length - 1];
|
|
258
|
+
if (!first || !last) return;
|
|
259
|
+
const active = document.activeElement;
|
|
260
|
+
if (ev.shiftKey && active === first) {
|
|
261
|
+
ev.preventDefault();
|
|
262
|
+
last.focus();
|
|
263
|
+
} else if (!ev.shiftKey && active === last) {
|
|
264
|
+
ev.preventDefault();
|
|
265
|
+
first.focus();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
confirmCancelEl?.addEventListener("click", closeConfirm);
|
|
271
|
+
confirmEl?.addEventListener("click", (ev) => {
|
|
272
|
+
if (ev.target === confirmEl) closeConfirm(); // backdrop click
|
|
273
|
+
});
|
|
274
|
+
confirmOkEl?.addEventListener("click", () => {
|
|
275
|
+
closeConfirm();
|
|
276
|
+
void handlePublish();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
async function handlePublish(): Promise<void> {
|
|
280
|
+
shipInFlight = true;
|
|
281
|
+
publishSucceeded = false;
|
|
282
|
+
// Re-render immediately from the last payload so the release shows "Publishing…"
|
|
283
|
+
// while the go-live POST is in flight (the client-owned transient, contract §3).
|
|
284
|
+
if (lastPayload) render(lastPayload);
|
|
285
|
+
busy = true;
|
|
286
|
+
const result = await publishNextRelease({ fetch: fetchLike, endpoint: ctx.shipEndpoint });
|
|
287
|
+
busy = false;
|
|
288
|
+
if (result.kind === "published") {
|
|
289
|
+
shipInFlight = false;
|
|
290
|
+
publishSucceeded = true;
|
|
291
|
+
setAnnouncement("Your release is live.");
|
|
292
|
+
} else if (result.kind === "paywall") {
|
|
293
|
+
shipInFlight = false;
|
|
294
|
+
setAnnouncement("Publishing to your live store is part of the paid plan.");
|
|
295
|
+
} else if (result.kind === "not-ready") {
|
|
296
|
+
shipInFlight = false;
|
|
297
|
+
setAnnouncement("We're getting your next release ready. Nothing you need to do.");
|
|
298
|
+
} else if (result.kind === "publish-failed") {
|
|
299
|
+
shipInFlight = false;
|
|
300
|
+
setAnnouncement("We couldn't finish publishing. We're retrying — nothing you need to do.");
|
|
301
|
+
} else {
|
|
302
|
+
shipInFlight = false;
|
|
303
|
+
setAnnouncement("We couldn't finish publishing just now. We're retrying automatically.");
|
|
304
|
+
}
|
|
305
|
+
await load();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ── Kickoff ───────────────────────────────────────────────────────────────────
|
|
309
|
+
void load();
|
|
310
|
+
window.setInterval(() => {
|
|
311
|
+
if (!confirmEl || confirmEl.hidden) void load();
|
|
312
|
+
}, ctx.pollMs);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Adapt the global `fetch` to the actions' minimal `FetchLike` shape. */
|
|
316
|
+
function fetchLike(
|
|
317
|
+
input: string,
|
|
318
|
+
init?: { method?: string; headers?: Record<string, string>; body?: string },
|
|
319
|
+
): Promise<{ ok: boolean; status: number; json: () => Promise<unknown> }> {
|
|
320
|
+
return fetch(input, init);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function escapeText(value: string): string {
|
|
324
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
325
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure HTML rendering for the merchant Review-and-Release workspace.
|
|
3
|
+
*
|
|
4
|
+
* Consumes the already-decided `ReviewReleaseViewModel` (from the
|
|
5
|
+
* `@/lib/publish/reviewRelease` seam) and returns serializable HTML strings — no
|
|
6
|
+
* DOM, no I/O — so it is exhaustively node-testable (the storefront test env is
|
|
7
|
+
* `node`, not jsdom; interaction/focus is verified on a live preview). The client
|
|
8
|
+
* controller injects these strings and wires events; this module invents NO copy
|
|
9
|
+
* (the seam's view model is the single source of truth) and adds NO merchant
|
|
10
|
+
* action beyond the two the model carries.
|
|
11
|
+
*/
|
|
12
|
+
import type {
|
|
13
|
+
ReviewReleaseViewModel,
|
|
14
|
+
ChangeCardView,
|
|
15
|
+
NextReleaseView,
|
|
16
|
+
MerchantPermissions,
|
|
17
|
+
} from "@/lib/publish/reviewRelease";
|
|
18
|
+
import { NO_CHANGES_COPY, PUBLISH_SUCCESS_COPY } from "@/lib/publish/reviewRelease";
|
|
19
|
+
|
|
20
|
+
/** Best-effort "what's live now" pointer (contract §9.7 history gap: no full history yet). */
|
|
21
|
+
export interface LiveReleaseView {
|
|
22
|
+
/** The live version id (short-rendered), or null when nothing is live. */
|
|
23
|
+
versionId: string | null;
|
|
24
|
+
/** Deterministic "Mon D, YYYY" published date, or "" when unknown. */
|
|
25
|
+
publishedDate: string;
|
|
26
|
+
/** basePath-aware href to the live store root. */
|
|
27
|
+
liveHref: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface WorkspaceRenderOptions {
|
|
31
|
+
/** basePath-aware href to the live store root (for the release "View your live store"). */
|
|
32
|
+
liveHref: string;
|
|
33
|
+
/** The current live release pointer, rendered as a persistent "Live" section. */
|
|
34
|
+
live: LiveReleaseView;
|
|
35
|
+
/** The post-publish success line + link is shown only right after a publish. */
|
|
36
|
+
publishSucceeded?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** HTML-escape an interpolated dynamic value (titles come from PR titles; store name from config). */
|
|
40
|
+
export function esc(value: string): string {
|
|
41
|
+
return value
|
|
42
|
+
.replace(/&/g, "&")
|
|
43
|
+
.replace(/</g, "<")
|
|
44
|
+
.replace(/>/g, ">")
|
|
45
|
+
.replace(/"/g, """)
|
|
46
|
+
.replace(/'/g, "'");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const READONLY_NOTE_ID = "rr-readonly-note";
|
|
50
|
+
const TESTSTORE_NOTE_ID = "rr-teststore-note";
|
|
51
|
+
const PAYWALL_NOTE_ID = "rr-paywall-note";
|
|
52
|
+
|
|
53
|
+
// ── Change cards ───────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/** The display title for a change: its human title, else a PR/change reference. */
|
|
56
|
+
function changeTitle(c: ChangeCardView): string {
|
|
57
|
+
if (c.title && c.title.trim()) return c.title.trim();
|
|
58
|
+
if (typeof c.prNumber === "number") return `Change #${c.prNumber}`;
|
|
59
|
+
if (c.changeId) return c.changeId;
|
|
60
|
+
return "Change";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* One change row. The status chip always carries its state WORD (color-independent,
|
|
65
|
+
* a11y §8). The "Add to next release" button appears only on `ready-to-add`; when the
|
|
66
|
+
* viewer is read-only it is rendered `disabled` with an `aria-describedby` pointing at
|
|
67
|
+
* the read-only note (never hidden — contract §6). `needs-attention` carries no
|
|
68
|
+
* action at all — its subline already states the developer was notified.
|
|
69
|
+
*/
|
|
70
|
+
export function renderChangeCard(c: ChangeCardView, perms: MerchantPermissions): string {
|
|
71
|
+
const title = esc(changeTitle(c));
|
|
72
|
+
const chip = `<span class="rr-chip rr-chip-${c.state}">${esc(c.label)}</span>`;
|
|
73
|
+
const subline = `<p class="rr-subline">${esc(c.subline)}</p>`;
|
|
74
|
+
|
|
75
|
+
let actions = "";
|
|
76
|
+
if (c.action && c.action.id === "add-to-next-release") {
|
|
77
|
+
const accessibleName = c.title ? `${c.action.label}: ${title}` : c.action.label;
|
|
78
|
+
if (perms.canAddAndPublish) {
|
|
79
|
+
actions += `<button type="button" class="rr-btn rr-btn-primary" data-rr-add`
|
|
80
|
+
+ ` data-change-id="${esc(c.changeId ?? "")}" data-pr-number="${c.prNumber ?? ""}"`
|
|
81
|
+
+ ` aria-label="${esc(accessibleName)}">${esc(c.action.label)}</button>`;
|
|
82
|
+
} else {
|
|
83
|
+
// Read-only: never hide the action — show it disabled with a programmatic reason.
|
|
84
|
+
actions += `<button type="button" class="rr-btn rr-btn-primary" disabled`
|
|
85
|
+
+ ` aria-describedby="${READONLY_NOTE_ID}"`
|
|
86
|
+
+ ` aria-label="${esc(accessibleName)}">${esc(c.action.label)}</button>`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return (
|
|
90
|
+
`<li class="rr-change" data-rr-change data-state="${c.state}"`
|
|
91
|
+
+ ` data-change-id="${esc(c.changeId ?? "")}" data-pr-number="${c.prNumber ?? ""}">`
|
|
92
|
+
+ `<div class="rr-change-main"><div class="rr-change-head">`
|
|
93
|
+
+ `<span class="rr-change-title">${title}</span>${chip}</div>${subline}</div>`
|
|
94
|
+
+ `<div class="rr-change-actions">${actions}</div>`
|
|
95
|
+
+ `</li>`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The "Changes to review" section. Honest zero-state when the queue is empty. */
|
|
100
|
+
export function renderChangesSection(vm: ReviewReleaseViewModel): string {
|
|
101
|
+
const items = vm.changes.map((c) => renderChangeCard(c, vm.permissions)).join("");
|
|
102
|
+
const body = vm.changes.length
|
|
103
|
+
? `<ul class="rr-change-list" data-rr-change-list>${items}</ul>`
|
|
104
|
+
: `<p class="rr-empty" data-rr-changes-empty>${esc(NO_CHANGES_COPY)}</p>`;
|
|
105
|
+
const readOnlyNote = vm.permissions.readOnly
|
|
106
|
+
? `<p class="rr-note" id="${READONLY_NOTE_ID}">${esc(vm.permissions.readOnlyNote ?? "")}</p>`
|
|
107
|
+
: "";
|
|
108
|
+
return (
|
|
109
|
+
`<section class="rr-section rr-changes" aria-labelledby="rr-changes-title">`
|
|
110
|
+
+ `<h2 id="rr-changes-title" class="rr-section-title">Changes to review</h2>`
|
|
111
|
+
+ readOnlyNote
|
|
112
|
+
+ body
|
|
113
|
+
+ `</section>`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Next release ─────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
/** The "What's in this release" plain-title list (Ready to publish) — no sha/digest/counts. */
|
|
120
|
+
function renderContents(next: NextReleaseView): string {
|
|
121
|
+
if (next.state !== "ready-to-publish") return "";
|
|
122
|
+
const items = next.contents.items.length
|
|
123
|
+
? `<ul class="rr-contents-list">${next.contents.items.map((t) => `<li>${esc(t)}</li>`).join("")}</ul>`
|
|
124
|
+
: `<p class="rr-note">${esc(next.contents.fallback)}</p>`;
|
|
125
|
+
return (
|
|
126
|
+
`<div class="rr-contents">`
|
|
127
|
+
+ `<h3 class="rr-contents-title">${esc(next.contents.heading)}</h3>${items}</div>`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The next-release action row. `Publish now` appears ONLY in `ready-to-publish` and
|
|
133
|
+
* ONLY when the model resolved the publish action (owner/admin/ship, entitled,
|
|
134
|
+
* not a test store). In `ready-to-publish` WITHOUT the action, the button is replaced
|
|
135
|
+
* — never hidden — by the internal-test-store notice, the paywall upgrade prompt, or
|
|
136
|
+
* the read-only note, in that precedence. `live` offers only "View your live store".
|
|
137
|
+
*/
|
|
138
|
+
function renderReleaseAction(
|
|
139
|
+
next: NextReleaseView,
|
|
140
|
+
perms: MerchantPermissions,
|
|
141
|
+
liveHref: string,
|
|
142
|
+
): string {
|
|
143
|
+
if (next.action && next.action.id === "publish-now") {
|
|
144
|
+
return (
|
|
145
|
+
`<div class="rr-release-action">`
|
|
146
|
+
+ `<button type="button" class="rr-btn rr-btn-primary rr-publish" data-rr-publish>`
|
|
147
|
+
+ `${esc(next.action.label)}</button></div>`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (next.action && next.action.id === "view-live-store") {
|
|
151
|
+
return (
|
|
152
|
+
`<div class="rr-release-action">`
|
|
153
|
+
+ `<a class="rr-btn rr-btn-primary" href="${esc(liveHref)}" data-rr-view-live>`
|
|
154
|
+
+ `${esc(next.action.label)}</a></div>`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
// Ready-to-publish but the action was withheld — show WHY (never a bare hidden button).
|
|
158
|
+
if (next.state === "ready-to-publish") {
|
|
159
|
+
if (perms.internalTestStore) {
|
|
160
|
+
return (
|
|
161
|
+
`<div class="rr-release-action rr-blocked">`
|
|
162
|
+
+ `<p class="rr-note" id="${TESTSTORE_NOTE_ID}" role="note">`
|
|
163
|
+
+ `${esc(perms.internalTestStoreNote ?? "")}</p></div>`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (perms.paywall.active) {
|
|
167
|
+
return (
|
|
168
|
+
`<div class="rr-release-action rr-paywall" data-rr-paywall>`
|
|
169
|
+
+ `<p class="rr-note" id="${PAYWALL_NOTE_ID}">Publishing to your live store is part of the paid plan. `
|
|
170
|
+
+ `Previewing and reviewing stays free.</p>`
|
|
171
|
+
+ `<a class="rr-btn rr-btn-primary" href="mailto:success@tokenoftrust.com?subject=Upgrade%20store%20publishing"`
|
|
172
|
+
+ ` data-rr-upgrade>Upgrade to publish</a></div>`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (perms.readOnly) {
|
|
176
|
+
return (
|
|
177
|
+
`<div class="rr-release-action">`
|
|
178
|
+
+ `<button type="button" class="rr-btn rr-btn-primary" disabled aria-describedby="${READONLY_NOTE_ID}">`
|
|
179
|
+
+ `Publish now</button>`
|
|
180
|
+
+ `<p class="rr-note" id="${READONLY_NOTE_ID}">${esc(perms.readOnlyNote ?? "")}</p></div>`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return "";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The next-release panel: heading + body (the state), the contents list, and the action. */
|
|
188
|
+
export function renderNextRelease(
|
|
189
|
+
next: NextReleaseView,
|
|
190
|
+
perms: MerchantPermissions,
|
|
191
|
+
liveHref: string,
|
|
192
|
+
publishSucceeded = false,
|
|
193
|
+
): string {
|
|
194
|
+
const success = publishSucceeded
|
|
195
|
+
? `<p class="rr-success" data-rr-publish-success>${esc(PUBLISH_SUCCESS_COPY.message)} `
|
|
196
|
+
+ `<a href="${esc(liveHref)}">${esc(PUBLISH_SUCCESS_COPY.link)}</a></p>`
|
|
197
|
+
: "";
|
|
198
|
+
return (
|
|
199
|
+
`<section class="rr-section rr-release" data-rr-release data-state="${next.state}"`
|
|
200
|
+
+ ` aria-labelledby="rr-release-title">`
|
|
201
|
+
+ `<div class="rr-release-status">`
|
|
202
|
+
+ `<h2 id="rr-release-title" class="rr-section-title">${esc(next.heading)}</h2>`
|
|
203
|
+
+ `<p class="rr-release-body">${esc(next.body)}</p></div>`
|
|
204
|
+
+ renderContents(next)
|
|
205
|
+
+ success
|
|
206
|
+
+ renderReleaseAction(next, perms, liveHref)
|
|
207
|
+
+ `</section>`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── Live (best-effort "what's live now") ─────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The "Live" section — the current live release. Best-effort: the open-queue
|
|
215
|
+
* `/api/changes` payload carries only the live-version pointer (no published
|
|
216
|
+
* history yet, contract §9.7), so this shows the live version + date + a link, not a
|
|
217
|
+
* historical list. Omitted entirely when nothing is live.
|
|
218
|
+
*/
|
|
219
|
+
export function renderLiveSection(live: LiveReleaseView): string {
|
|
220
|
+
if (!live.versionId) return "";
|
|
221
|
+
const shortId = esc(live.versionId.slice(0, 12));
|
|
222
|
+
const when = live.publishedDate ? ` · Published ${esc(live.publishedDate)}` : "";
|
|
223
|
+
return (
|
|
224
|
+
`<section class="rr-section rr-live" aria-labelledby="rr-live-title">`
|
|
225
|
+
+ `<h2 id="rr-live-title" class="rr-section-title">Live</h2>`
|
|
226
|
+
+ `<p class="rr-live-body">Your store is serving <code class="rr-mono">${shortId}</code>${when}.</p>`
|
|
227
|
+
+ `<a class="rr-btn rr-btn-ghost" href="${esc(live.liveHref)}">View your live store</a>`
|
|
228
|
+
+ `</section>`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── Whole workspace ──────────────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
/** Compose the three sections the controller injects into the workspace container. */
|
|
235
|
+
export function renderWorkspace(vm: ReviewReleaseViewModel, opts: WorkspaceRenderOptions): string {
|
|
236
|
+
return (
|
|
237
|
+
renderChangesSection(vm)
|
|
238
|
+
+ renderNextRelease(vm.nextRelease, vm.permissions, opts.liveHref, opts.publishSucceeded)
|
|
239
|
+
+ renderLiveSection(opts.live)
|
|
240
|
+
);
|
|
241
|
+
}
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
* digest column (surfaced for a static tenant) differs.
|
|
47
47
|
*/
|
|
48
48
|
import { reviewDashboardRevisionHref } from "@/lib/preview/reviewDashboard";
|
|
49
|
+
import { assignVersionNumbers } from "@/lib/publish/versionNumber";
|
|
49
50
|
import {
|
|
50
51
|
candidateEvidenceAuthority,
|
|
51
52
|
classifyCandidateForRender,
|
|
@@ -57,8 +58,10 @@ import type { ReviewEnvironment, PromotionEvent } from "@tot/public-runtime";
|
|
|
57
58
|
// ── Versions / promotion history ───────────────────────────────────────────
|
|
58
59
|
|
|
59
60
|
/** The plain-language "what shipped in this version" model (same shape the staged
|
|
60
|
-
* release summary uses — a lead sentence + one highlight per included change). */
|
|
61
|
+
* release summary uses — a name + a lead sentence + one highlight per included change). */
|
|
61
62
|
export interface VersionReleaseSummary {
|
|
63
|
+
/** The human-readable release name (see `deriveReleaseName`), or null. */
|
|
64
|
+
name: string | null;
|
|
62
65
|
lead: string;
|
|
63
66
|
highlights: { title: string; detail: string }[];
|
|
64
67
|
}
|
|
@@ -87,6 +90,12 @@ export interface VersionReleaseInfo {
|
|
|
87
90
|
export interface WorkspaceVersion {
|
|
88
91
|
/** The tenant Gitea SHA the version serves — the content identity. */
|
|
89
92
|
versionId: string;
|
|
93
|
+
/**
|
|
94
|
+
* The merchant-facing monotonic version number ("v42") — derived from first
|
|
95
|
+
* promotion order (see `versionNumber.ts`), shared with the "Live version"
|
|
96
|
+
* metric. The human label; the SHA is demoted to a technical disclosure.
|
|
97
|
+
*/
|
|
98
|
+
number: number | null;
|
|
90
99
|
/** How the version was published (a Gitea commit, or a content hash). */
|
|
91
100
|
source: "commit" | "hash" | null;
|
|
92
101
|
/** ISO time the version was PROMOTED onto `live` (the promotion event). */
|
|
@@ -133,7 +142,7 @@ export type RollbackMode = "control-plane" | "pointer";
|
|
|
133
142
|
/** The versions-panel view model. */
|
|
134
143
|
export interface VersionsView {
|
|
135
144
|
/** The current live version + (static) the digest `live/` actually serves. */
|
|
136
|
-
current: { versionId: string | null; digest: string | null };
|
|
145
|
+
current: { versionId: string | null; number: number | null; digest: string | null };
|
|
137
146
|
/** Promotion history, newest first, current marked. */
|
|
138
147
|
versions: WorkspaceVersion[];
|
|
139
148
|
/** Whether the tenant publishes its public site to an external static target. */
|
|
@@ -189,12 +198,18 @@ export function buildVersionsView(input: BuildVersionsViewInput): VersionsView {
|
|
|
189
198
|
staticPublish,
|
|
190
199
|
} = input;
|
|
191
200
|
|
|
201
|
+
// The merchant-facing "v42" counter, derived from first-promotion order — the
|
|
202
|
+
// SAME derivation `resolveLiveVersion` feeds the "Live version" metric, so the
|
|
203
|
+
// panel and the metric always agree on a version's number.
|
|
204
|
+
const numbers = assignVersionNumbers(promotions);
|
|
205
|
+
|
|
192
206
|
const versions: WorkspaceVersion[] = promotions.map((p) => {
|
|
193
207
|
const facts = manifests.get(p.versionId);
|
|
194
208
|
const release = releases?.get(p.versionId);
|
|
195
209
|
const isCurrent = currentVersionId != null && p.versionId === currentVersionId;
|
|
196
210
|
return {
|
|
197
211
|
versionId: p.versionId,
|
|
212
|
+
number: numbers.get(p.versionId) ?? null,
|
|
198
213
|
source: facts?.source ?? null,
|
|
199
214
|
promotedAt: p.at,
|
|
200
215
|
createdAt: facts?.createdAt ?? null,
|
|
@@ -210,7 +225,11 @@ export function buildVersionsView(input: BuildVersionsViewInput): VersionsView {
|
|
|
210
225
|
});
|
|
211
226
|
|
|
212
227
|
return {
|
|
213
|
-
current: {
|
|
228
|
+
current: {
|
|
229
|
+
versionId: currentVersionId,
|
|
230
|
+
number: currentVersionId != null ? numbers.get(currentVersionId) ?? null : null,
|
|
231
|
+
digest: staticPublish ? currentDigest : null,
|
|
232
|
+
},
|
|
214
233
|
versions,
|
|
215
234
|
staticPublish,
|
|
216
235
|
rollbackMode: staticPublish ? "pointer" : "control-plane",
|