@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
|
@@ -201,1095 +201,19 @@ const isLocalDevLoop = import.meta.env.DEV;
|
|
|
201
201
|
const hostedActivity = !isLocalDevLoop && canSetupStore;
|
|
202
202
|
|
|
203
203
|
// cp-env (cloud-dev-cockpit Phase 0): the file-tree/viewer/diff + click-to-source
|
|
204
|
-
// cockpit
|
|
205
|
-
// JS, different endpoint base. Local: the astro-dev-only /__tot/* plugins. Hosted: new
|
|
204
|
+
// cockpit modules (lib/dev/cockpit/files.ts, clickToSource.ts) are ENVIRONMENT-AWARE
|
|
205
|
+
// — same client JS, different endpoint base. Local: the astro-dev-only /__tot/* plugins. Hosted: new
|
|
206
206
|
// /api/dev/* Astro API routes, landing incrementally (cp-tree/cp-file/cp-write/cp-diff/
|
|
207
207
|
// cp-locate — separate cards). Resolving it here, unconditionally, means those cards
|
|
208
208
|
// don't each have to invent the same env check; on isLocalDevLoop this is a no-op
|
|
209
209
|
// (resolves to the unchanged /__tot base). See decision architecture-environment-aware-cockpit.
|
|
210
210
|
const apiBase = resolveDevApiBase(isLocalDevLoop, basePath);
|
|
211
211
|
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
// (not the WS custom event) keeps this a plain inline script with no Vite-module
|
|
218
|
-
// coupling, and it survives a full page reload — the server holds loopState, so
|
|
219
|
-
// the bar re-hydrates to the true state on every load. All dynamic values go
|
|
220
|
-
// through textContent, never innerHTML.
|
|
221
|
-
const monitorScript = `
|
|
222
|
-
(function () {
|
|
223
|
-
var bar = document.getElementById("loopbar");
|
|
224
|
-
if (!bar) return;
|
|
225
|
-
var elStatus = document.getElementById("lbStatus");
|
|
226
|
-
var elWatch = document.getElementById("lbWatch");
|
|
227
|
-
var elLast = document.getElementById("lbLast");
|
|
228
|
-
var elServer = document.getElementById("lbServer");
|
|
229
|
-
var elUrl = document.getElementById("lbUrl");
|
|
230
|
-
var elFile = document.getElementById("lbFile");
|
|
231
|
-
var elSave = document.getElementById("lbSave");
|
|
232
|
-
var elReload = document.getElementById("lbReload");
|
|
233
|
-
var head = document.getElementById("lbHead");
|
|
234
|
-
|
|
235
|
-
if (head) head.addEventListener("click", function () { bar.classList.toggle("open"); });
|
|
236
|
-
|
|
237
|
-
function rel(ts) {
|
|
238
|
-
var d = Math.max(0, Math.round((Date.now() - Number(ts)) / 1000));
|
|
239
|
-
return d < 2 ? "just now" : d < 60 ? d + "s ago" : Math.floor(d / 60) + "m ago";
|
|
240
|
-
}
|
|
241
|
-
function setTs(el, ts, emptyText) {
|
|
242
|
-
if (ts) {
|
|
243
|
-
el.setAttribute("data-ts", String(ts));
|
|
244
|
-
el.classList.remove("dim");
|
|
245
|
-
el.textContent = rel(ts);
|
|
246
|
-
} else {
|
|
247
|
-
el.removeAttribute("data-ts");
|
|
248
|
-
el.classList.add("dim");
|
|
249
|
-
el.textContent = emptyText;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
setInterval(function () {
|
|
253
|
-
[elSave, elReload].forEach(function (el) {
|
|
254
|
-
var ts = el.getAttribute("data-ts");
|
|
255
|
-
if (ts) el.textContent = rel(ts);
|
|
256
|
-
});
|
|
257
|
-
}, 1000);
|
|
258
|
-
|
|
259
|
-
function render(s) {
|
|
260
|
-
bar.classList.add("live");
|
|
261
|
-
var port = s.port || null;
|
|
262
|
-
var url = port ? ("localhost:" + port + (s.tenant ? "/" + s.tenant + "/" : "/")) : "—";
|
|
263
|
-
elServer.textContent = s.server ? ("running" + (port ? " · port " + port : "")) : "not running";
|
|
264
|
-
elServer.classList.toggle("dim", !s.server);
|
|
265
|
-
elUrl.textContent = url;
|
|
266
|
-
elUrl.classList.toggle("dim", !port);
|
|
267
|
-
elFile.textContent = s.watchedFile || "content/*.html";
|
|
268
|
-
elFile.classList.toggle("dim", !s.watchedFile);
|
|
269
|
-
elWatch.textContent = s.watchedFile || "your content";
|
|
270
|
-
setTs(elSave, s.lastSaveAt, "no saves yet");
|
|
271
|
-
setTs(elReload, s.lastReloadAt, "waiting for a save");
|
|
272
|
-
|
|
273
|
-
var saved = s.lastSaveAt || 0;
|
|
274
|
-
var reloaded = s.lastReloadAt || 0;
|
|
275
|
-
var state, status, last;
|
|
276
|
-
if (!saved) {
|
|
277
|
-
state = "running"; status = "Running"; last = "reloads on save";
|
|
278
|
-
} else if (reloaded >= saved) {
|
|
279
|
-
state = "running"; status = "Running"; last = "reloaded";
|
|
280
|
-
} else if (Date.now() - saved < 3000) {
|
|
281
|
-
state = "reloading"; status = "Reloading…"; last = "change detected";
|
|
282
|
-
} else {
|
|
283
|
-
state = "stale"; status = "Stale"; last = "save not reflected — restart?";
|
|
284
|
-
}
|
|
285
|
-
bar.setAttribute("data-state", state);
|
|
286
|
-
elStatus.textContent = status;
|
|
287
|
-
elLast.textContent = last;
|
|
288
|
-
// Broadcast the snapshot so other consumers (U2 stepper, U3 Ask) share this
|
|
289
|
-
// one poll instead of each opening their own. window.__totLoop holds the
|
|
290
|
-
// latest for listeners that attach late.
|
|
291
|
-
window.__totLoop = s;
|
|
292
|
-
window.dispatchEvent(new CustomEvent("tot:loop-state", { detail: s }));
|
|
293
|
-
}
|
|
294
|
-
function offline() {
|
|
295
|
-
bar.classList.add("live");
|
|
296
|
-
bar.setAttribute("data-state", "stale");
|
|
297
|
-
elStatus.textContent = "Offline";
|
|
298
|
-
elLast.textContent = "dev server not reachable";
|
|
299
|
-
elServer.textContent = "not reachable";
|
|
300
|
-
var off = { server: false, offline: true };
|
|
301
|
-
window.__totLoop = off;
|
|
302
|
-
window.dispatchEvent(new CustomEvent("tot:loop-state", { detail: off }));
|
|
303
|
-
}
|
|
304
|
-
function poll() {
|
|
305
|
-
fetch("/__tot/dev-loop-status", { cache: "no-store" })
|
|
306
|
-
.then(function (r) { return r.ok ? r.json() : Promise.reject(); })
|
|
307
|
-
.then(render)
|
|
308
|
-
.catch(offline);
|
|
309
|
-
}
|
|
310
|
-
poll();
|
|
311
|
-
setInterval(poll, 1000);
|
|
312
|
-
})();
|
|
313
|
-
`;
|
|
314
|
-
|
|
315
|
-
// Progressive stepper + staged flow (U2). Drives Run → Edit → What's next with
|
|
316
|
-
// progressive disclosure: only the current stage shows, completed stages collapse
|
|
317
|
-
// to a done-bar, and the flow AUTO-ADVANCES on C1's REAL loop signal (never a
|
|
318
|
-
// timer) — Step 1 completes when the local server is live, Step 2 when a real
|
|
319
|
-
// save→reload is observed since the edit step began. Consumes the shared
|
|
320
|
-
// tot:loop-state event from monitorScript (one poll for the whole page).
|
|
321
|
-
//
|
|
322
|
-
// Progress is persisted in sessionStorage. This is REQUIRED, not a nicety: saving
|
|
323
|
-
// a watched file triggers Vite's global full-reload, which reloads THIS /dev tab
|
|
324
|
-
// too — resetting in-memory state and re-baselining the very save that should
|
|
325
|
-
// advance Edit→Next. Persisting done-flags + current stage + the edit baseline
|
|
326
|
-
// (the lastSaveAt watermark captured when the edit step began, BEFORE the
|
|
327
|
-
// qualifying save) lets the flow survive that reload and credit the save.
|
|
328
|
-
//
|
|
329
|
-
// Dev-only, rendered via set:html inside an isLocalDevLoop-gated is:inline tag. All dynamic
|
|
330
|
-
// values via textContent.
|
|
331
|
-
const stepperScript = `
|
|
332
|
-
(function () {
|
|
333
|
-
var stepper = document.getElementById("stepper");
|
|
334
|
-
if (!stepper) return;
|
|
335
|
-
var stages = { run: document.getElementById("stage-run"), edit: document.getElementById("stage-edit"), next: document.getElementById("stage-next") };
|
|
336
|
-
var bars = { run: document.getElementById("bar-run"), edit: document.getElementById("bar-edit") };
|
|
337
|
-
var celRun = document.getElementById("cel-run");
|
|
338
|
-
var celEdit = document.getElementById("cel-edit");
|
|
339
|
-
var toNext = document.getElementById("toNext");
|
|
340
|
-
var detect = document.getElementById("detect");
|
|
341
|
-
var detectTxt = document.getElementById("detectTxt");
|
|
342
|
-
var detectEdit = document.getElementById("detect-edit");
|
|
343
|
-
var detectEditTxt = document.getElementById("detectEditTxt");
|
|
344
|
-
var barRunMeta = document.getElementById("bar-run-meta");
|
|
345
|
-
var barEditMeta = document.getElementById("bar-edit-meta");
|
|
346
|
-
var toast = document.getElementById("toast"), toastMsg = document.getElementById("toastMsg"), tT;
|
|
347
|
-
var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
348
|
-
|
|
349
|
-
var SS = window.sessionStorage;
|
|
350
|
-
function load(k, d) { try { var v = SS.getItem(k); return v === null ? d : JSON.parse(v); } catch (e) { return d; } }
|
|
351
|
-
function put(k, v) { try { SS.setItem(k, JSON.stringify(v)); } catch (e) {} }
|
|
352
|
-
|
|
353
|
-
function ping(m, once) {
|
|
354
|
-
if (once && load(once, false)) return;
|
|
355
|
-
if (once) put(once, true);
|
|
356
|
-
if (!toast || !toastMsg) return;
|
|
357
|
-
toastMsg.textContent = m; toast.classList.add("show");
|
|
358
|
-
clearTimeout(tT); tT = setTimeout(function () { toast.classList.remove("show"); }, 1900);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
var state = {
|
|
362
|
-
unlocked: load("tot.unlocked", { run: true, edit: false, next: false }),
|
|
363
|
-
done: load("tot.done", { run: false, edit: false }),
|
|
364
|
-
current: load("tot.current", "run"),
|
|
365
|
-
};
|
|
366
|
-
var editBase = load("tot.editBase", null);
|
|
367
|
-
function persist() { put("tot.unlocked", state.unlocked); put("tot.done", state.done); put("tot.current", state.current); }
|
|
368
|
-
|
|
369
|
-
function render() {
|
|
370
|
-
Array.prototype.forEach.call(stepper.querySelectorAll(".snode"), function (n) {
|
|
371
|
-
var s = n.getAttribute("data-go");
|
|
372
|
-
n.setAttribute("data-st", state.current === s ? "active" : state.done[s] ? "done" : state.unlocked[s] ? "done" : "locked");
|
|
373
|
-
});
|
|
374
|
-
Object.keys(stages).forEach(function (s) { if (stages[s]) stages[s].hidden = state.current !== s; });
|
|
375
|
-
if (bars.run) bars.run.hidden = !(state.done.run && state.current !== "run");
|
|
376
|
-
if (bars.edit) bars.edit.hidden = !(state.done.edit && state.current !== "edit");
|
|
377
|
-
// Contextual celebrate banners reflect achieved state (survive reloads).
|
|
378
|
-
if (celRun && state.done.run) celRun.hidden = false;
|
|
379
|
-
if (celEdit && state.done.edit) celEdit.hidden = false;
|
|
380
|
-
if (toNext) toNext.disabled = !state.unlocked.next;
|
|
381
|
-
}
|
|
382
|
-
function goTo(s) {
|
|
383
|
-
if (!state.unlocked[s]) return;
|
|
384
|
-
state.current = s; persist(); render();
|
|
385
|
-
window.scrollTo({ top: 0, behavior: reduce ? "auto" : "smooth" });
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function onSignal(s) {
|
|
389
|
-
if (!s || s.offline) return;
|
|
390
|
-
|
|
391
|
-
// Step 1 — local server is live.
|
|
392
|
-
if (s.server && !state.done.run) {
|
|
393
|
-
state.done.run = true; state.unlocked.edit = true;
|
|
394
|
-
if (editBase === null) { editBase = s.lastSaveAt || 0; put("tot.editBase", editBase); }
|
|
395
|
-
persist();
|
|
396
|
-
if (barRunMeta && s.port) barRunMeta.textContent = "localhost:" + s.port + (s.tenant ? "/" + s.tenant + "/" : "/");
|
|
397
|
-
if (detect) detect.setAttribute("data-listening", "false");
|
|
398
|
-
if (detectTxt) detectTxt.textContent = "Your store is live on this machine.";
|
|
399
|
-
if (state.current === "run") { goTo("edit"); } else { render(); }
|
|
400
|
-
ping("Your store is live", "tot.pinged.run");
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Step 2 — a NEW save (since the edit step began) has been reflected by a reload.
|
|
404
|
-
if (state.done.run && !state.done.edit && editBase !== null) {
|
|
405
|
-
var newSave = (s.lastSaveAt || 0) > editBase;
|
|
406
|
-
var reflected = (s.lastSaveAt || 0) > 0 && (s.lastReloadAt || 0) >= (s.lastSaveAt || 0);
|
|
407
|
-
if (newSave && !reflected && detectEditTxt) {
|
|
408
|
-
detectEditTxt.textContent = "Change detected — reloading your store…";
|
|
409
|
-
}
|
|
410
|
-
if (newSave && reflected) {
|
|
411
|
-
state.done.edit = true; state.unlocked.next = true; persist();
|
|
412
|
-
if (barEditMeta && s.watchedFile) barEditMeta.textContent = s.watchedFile;
|
|
413
|
-
if (detectEdit) detectEdit.setAttribute("data-listening", "false");
|
|
414
|
-
if (detectEditTxt) detectEditTxt.textContent = "Saved — your store reloaded. That's the loop.";
|
|
415
|
-
if (state.current === "edit") { goTo("next"); } else { render(); }
|
|
416
|
-
ping("Saved — your page reloaded", "tot.pinged.edit");
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
render();
|
|
422
|
-
Array.prototype.forEach.call(document.querySelectorAll("[data-go]"), function (el) {
|
|
423
|
-
el.addEventListener("click", function () { goTo(el.getAttribute("data-go")); });
|
|
424
|
-
});
|
|
425
|
-
window.addEventListener("tot:loop-state", function (e) { onSignal(e.detail); });
|
|
426
|
-
if (window.__totLoop) onSignal(window.__totLoop);
|
|
427
|
-
})();
|
|
428
|
-
`;
|
|
429
|
-
|
|
430
|
-
// "Stuck? Ask" recovery affordance (U3). A quiet FAB that opens a scoped Q&A for
|
|
431
|
-
// "why isn't the loop working" — NOT general AI editing. Static answers ship as
|
|
432
|
-
// markup; the two stateful answers (is it running? / save-vs-reload) update from
|
|
433
|
-
// the shared tot:loop-state signal + window.__totLoop (C1/U2 — no second poll).
|
|
434
|
-
// "Reconnect preview" reloads the page, re-establishing the HMR connection and
|
|
435
|
-
// re-reading loop state (recovers a stale client); a fully-down server is handled
|
|
436
|
-
// by the answer text (run tot start). Dev-only, set:html in an isLocalDevLoop-gated tag.
|
|
437
|
-
const askScript = `
|
|
438
|
-
(function () {
|
|
439
|
-
var fab = document.getElementById("askFab");
|
|
440
|
-
var panel = document.getElementById("askPanel");
|
|
441
|
-
if (!fab || !panel) return;
|
|
442
|
-
var ansRunning = document.getElementById("ans-running");
|
|
443
|
-
var ansChange = document.getElementById("ans-change");
|
|
444
|
-
var restart = document.getElementById("restartBtn");
|
|
445
|
-
|
|
446
|
-
fab.addEventListener("click", function (e) { e.stopPropagation(); panel.classList.toggle("open"); });
|
|
447
|
-
document.addEventListener("click", function (e) {
|
|
448
|
-
if (!panel.contains(e.target) && e.target !== fab && !fab.contains(e.target)) panel.classList.remove("open");
|
|
449
|
-
});
|
|
450
|
-
if (restart) restart.addEventListener("click", function () { location.reload(); });
|
|
451
|
-
|
|
452
|
-
function rel(ts) {
|
|
453
|
-
var d = Math.max(0, Math.round((Date.now() - Number(ts)) / 1000));
|
|
454
|
-
return d < 2 ? "just now" : d < 60 ? d + "s ago" : Math.floor(d / 60) + "m ago";
|
|
455
|
-
}
|
|
456
|
-
function update(s) {
|
|
457
|
-
if (!s) return;
|
|
458
|
-
if (ansRunning) {
|
|
459
|
-
ansRunning.textContent = (s.offline || !s.server)
|
|
460
|
-
? "Your dev server isn't reachable right now — run tot start in your terminal, then reload this page."
|
|
461
|
-
: "Yes — your server is running" + (s.port ? " on port " + s.port : "") + ". This page is connected to it.";
|
|
462
|
-
}
|
|
463
|
-
if (ansChange) {
|
|
464
|
-
if (!s.lastSaveAt) {
|
|
465
|
-
ansChange.textContent = "No saves seen yet. Edit a watched file (like content/home.html) and save — it'll show up here.";
|
|
466
|
-
} else if ((s.lastReloadAt || 0) >= s.lastSaveAt) {
|
|
467
|
-
ansChange.textContent = "Working: your last save (" + rel(s.lastSaveAt) + ") triggered a reload (" + rel(s.lastReloadAt) + "). If the page didn't visibly change, make sure you edited the file the page renders from.";
|
|
468
|
-
} else {
|
|
469
|
-
ansChange.textContent = "Your save (" + rel(s.lastSaveAt) + ") hasn't been reflected by a reload yet — the preview may be stale. Try Reconnect preview below.";
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
window.addEventListener("tot:loop-state", function (e) { update(e.detail); });
|
|
474
|
-
if (window.__totLoop) update(window.__totLoop);
|
|
475
|
-
})();
|
|
476
|
-
`;
|
|
477
|
-
|
|
478
|
-
// C5 · Files tab — in-browser file tree + LIVE EDITOR. Dev-only, rendered via
|
|
479
|
-
// set:html inside an isLocalDevLoop-gated is:inline nonce'd tag. Reads /__tot/tree +
|
|
480
|
-
// /__tot/file and writes /__tot/write (dev-plugins/dev-file-browser + dev-write).
|
|
481
|
-
// Writable files (content HTML + theme.json) become an editable textarea with
|
|
482
|
-
// ~600ms debounce autosave + Cmd/Ctrl+S; the save carries the C6 baseVersion so a
|
|
483
|
-
// concurrent $EDITOR/git change is caught (409) instead of clobbered. Dynamic
|
|
484
|
-
// values go through .value / textContent (never innerHTML) per the C1 decision.
|
|
485
|
-
const filesScript = `
|
|
486
|
-
(function () {
|
|
487
|
-
var apiBase = ${JSON.stringify(apiBase)};
|
|
488
|
-
var isDev = ${JSON.stringify(isLocalDevLoop)};
|
|
489
|
-
var offlineMsg = isDev ? "Could not reach the dev server." : "Not available on the hosted panel yet \\u2014 check back soon.";
|
|
490
|
-
var treeEl = document.getElementById("filesTree");
|
|
491
|
-
var viewEl = document.getElementById("filesView");
|
|
492
|
-
var pathEl = document.getElementById("filesPath");
|
|
493
|
-
var editbar = document.getElementById("filesEditbar");
|
|
494
|
-
var statusEl = document.getElementById("filesStatus");
|
|
495
|
-
var conflictEl = document.getElementById("filesConflict");
|
|
496
|
-
var reloadBtn = document.getElementById("filesReload");
|
|
497
|
-
var forceBtn = document.getElementById("filesForce");
|
|
498
|
-
var diffBtn = document.getElementById("filesDiffBtn");
|
|
499
|
-
var diffEl = document.getElementById("filesDiff");
|
|
500
|
-
var publishBtn = document.getElementById("filesPublishBtn");
|
|
501
|
-
if (!treeEl || !viewEl) return;
|
|
502
|
-
var selected = null;
|
|
503
|
-
var cur = null;
|
|
504
|
-
var saveTimer = null;
|
|
505
|
-
var diffOn = false;
|
|
506
|
-
var rowByPath = {}; // path -> tree row element, for programmatic open (C8 jump)
|
|
507
|
-
|
|
508
|
-
function setStatus(txt, cls) {
|
|
509
|
-
if (!statusEl) return;
|
|
510
|
-
statusEl.textContent = txt || "";
|
|
511
|
-
statusEl.className = "df-status" + (cls ? " " + cls : "");
|
|
512
|
-
}
|
|
513
|
-
function showConflict(show) { if (conflictEl) conflictEl.hidden = !show; }
|
|
514
|
-
function showBar(show) { if (editbar) editbar.hidden = !show; }
|
|
515
|
-
|
|
516
|
-
function openFile(path, row, afterLoad) {
|
|
517
|
-
if (selected) selected.classList.remove("sel");
|
|
518
|
-
selected = row; row.classList.add("sel");
|
|
519
|
-
if (pathEl) pathEl.textContent = path;
|
|
520
|
-
showConflict(false); showBar(true);
|
|
521
|
-
setDiffMode(false); if (diffBtn) diffBtn.hidden = true;
|
|
522
|
-
if (publishBtn) publishBtn.hidden = true;
|
|
523
|
-
viewEl.readOnly = true;
|
|
524
|
-
setStatus("Loading\\u2026");
|
|
525
|
-
fetch(apiBase + "/file?path=" + encodeURIComponent(path))
|
|
526
|
-
.then(function (r) { return r.json(); })
|
|
527
|
-
.then(function (d) {
|
|
528
|
-
if (!d || !d.ok) { viewEl.value = ""; cur = null; setStatus((d && d.message) || "Could not open that file.", "warn"); return; }
|
|
529
|
-
if (d.binary) { viewEl.value = "(binary file \\u2014 " + (d.size || 0) + " bytes)"; cur = null; setStatus("Binary \\u2014 not editable"); return; }
|
|
530
|
-
if (d.tooBig) { viewEl.value = "(file too large to show \\u2014 " + (d.size || 0) + " bytes)"; cur = null; setStatus("Too large \\u2014 not editable"); return; }
|
|
531
|
-
viewEl.value = d.content == null ? "" : d.content;
|
|
532
|
-
cur = { path: path, baseVersion: (typeof d.mtimeMs === "number" ? d.mtimeMs : null), writable: !!d.writable };
|
|
533
|
-
viewEl.readOnly = !cur.writable;
|
|
534
|
-
if (diffBtn) diffBtn.hidden = false;
|
|
535
|
-
if (publishBtn) publishBtn.hidden = !cur.writable;
|
|
536
|
-
setStatus(cur.writable ? "Editable \\u2014 edits save + reload your store" : "Read-only");
|
|
537
|
-
if (typeof afterLoad === "function") afterLoad();
|
|
538
|
-
})
|
|
539
|
-
.catch(function () { viewEl.value = ""; cur = null; setStatus(offlineMsg, "warn"); });
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Place the textarea caret at a 1-based line:col and scroll it into view. Used by
|
|
543
|
-
// the C8 click-to-source jump; a textarea has no CodeMirror EditorView, so we map
|
|
544
|
-
// (line,col) to a character offset + setSelectionRange, then center by scrollTop.
|
|
545
|
-
function placeCursor(line, col) {
|
|
546
|
-
var text = viewEl.value;
|
|
547
|
-
var lines = text.split("\\n");
|
|
548
|
-
var ln = Math.min(Math.max(1, line || 1), lines.length);
|
|
549
|
-
var offset = 0;
|
|
550
|
-
for (var i = 0; i < ln - 1; i++) offset += lines[i].length + 1; // +1 for the newline
|
|
551
|
-
offset += Math.min(Math.max(1, col || 1), (lines[ln - 1] || "").length + 1) - 1;
|
|
552
|
-
viewEl.focus();
|
|
553
|
-
try { viewEl.setSelectionRange(offset, offset); } catch (e) { /* not focusable yet */ }
|
|
554
|
-
// Center the target line: approximate line height from the rendered content.
|
|
555
|
-
var lh = viewEl.scrollHeight / Math.max(1, lines.length);
|
|
556
|
-
viewEl.scrollTop = Math.max(0, (ln - 1) * lh - viewEl.clientHeight / 2);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// C8 capstone entry point: open <path> and drop the caret at line:col. Invoked by
|
|
560
|
-
// the cockpit's origin-checked tot:locate listener when a click lands in the
|
|
561
|
-
// live-preview iframe. Finds the file's tree row (built by renderNodes); if the
|
|
562
|
-
// tree hasn't loaded yet, no-ops gracefully.
|
|
563
|
-
window.__totOpenFileAt = function (path, line, col) {
|
|
564
|
-
var row = rowByPath[path];
|
|
565
|
-
if (!row) return false;
|
|
566
|
-
if (typeof row.scrollIntoView === "function") row.scrollIntoView({ block: "nearest" });
|
|
567
|
-
openFile(path, row, function () { placeCursor(line, col); });
|
|
568
|
-
return true;
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
function doSave(force) {
|
|
572
|
-
if (!cur || !cur.writable) return;
|
|
573
|
-
setStatus("Saving\\u2026");
|
|
574
|
-
var body = { path: cur.path, content: viewEl.value };
|
|
575
|
-
if (!force && typeof cur.baseVersion === "number") body.baseVersion = cur.baseVersion;
|
|
576
|
-
fetch(apiBase + "/write", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
|
|
577
|
-
.then(function (r) { return r.json().then(function (j) { return { s: r.status, j: j }; }); })
|
|
578
|
-
.then(function (res) {
|
|
579
|
-
if (res.s === 200 && res.j && res.j.ok) { cur.baseVersion = res.j.mtimeMs; showConflict(false); setStatus("Saved \\u2713", "ok"); return; }
|
|
580
|
-
if (res.s === 409) { showConflict(true); setStatus("Changed on disk since you opened it", "warn"); return; }
|
|
581
|
-
if (res.s === 403) { setStatus("This file isn't editable.", "warn"); return; }
|
|
582
|
-
setStatus((res.j && res.j.message) || "Save failed", "warn");
|
|
583
|
-
})
|
|
584
|
-
.catch(function () { setStatus(isDev ? "Save failed \\u2014 dev server unreachable" : offlineMsg, "warn"); });
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
viewEl.addEventListener("input", function () {
|
|
588
|
-
if (!cur || !cur.writable) return;
|
|
589
|
-
setStatus("Editing\\u2026");
|
|
590
|
-
if (saveTimer) clearTimeout(saveTimer);
|
|
591
|
-
saveTimer = setTimeout(function () { doSave(false); }, 600);
|
|
592
|
-
});
|
|
593
|
-
viewEl.addEventListener("keydown", function (e) {
|
|
594
|
-
if ((e.metaKey || e.ctrlKey) && (e.key === "s" || e.key === "S")) {
|
|
595
|
-
e.preventDefault();
|
|
596
|
-
if (saveTimer) clearTimeout(saveTimer);
|
|
597
|
-
doSave(false);
|
|
598
|
-
}
|
|
599
|
-
});
|
|
600
|
-
if (reloadBtn) reloadBtn.addEventListener("click", function () { if (cur && selected) openFile(cur.path, selected); });
|
|
601
|
-
if (forceBtn) forceBtn.addEventListener("click", function () { showConflict(false); doSave(true); });
|
|
602
|
-
|
|
603
|
-
function setDiffMode(on) {
|
|
604
|
-
diffOn = on;
|
|
605
|
-
if (diffEl) diffEl.hidden = !on;
|
|
606
|
-
viewEl.hidden = on;
|
|
607
|
-
if (diffBtn) diffBtn.textContent = on ? "Edit" : "Diff";
|
|
608
|
-
}
|
|
609
|
-
function renderDiffText(t) { if (diffEl) diffEl.textContent = t; }
|
|
610
|
-
function lineDiff(before, after) {
|
|
611
|
-
var a = before.split("\\n"), b = after.split("\\n");
|
|
612
|
-
var m = a.length, n = b.length;
|
|
613
|
-
if (m * n > 4000000) return null;
|
|
614
|
-
var dp = []; for (var i = 0; i <= m; i++) { dp.push(new Array(n + 1).fill(0)); }
|
|
615
|
-
for (var i = m - 1; i >= 0; i--) for (var j = n - 1; j >= 0; j--) dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : (dp[i + 1][j] >= dp[i][j + 1] ? dp[i + 1][j] : dp[i][j + 1]);
|
|
616
|
-
var out = [], x = 0, y = 0;
|
|
617
|
-
while (x < m && y < n) {
|
|
618
|
-
if (a[x] === b[y]) { out.push([" ", a[x]]); x++; y++; }
|
|
619
|
-
else if (dp[x + 1][y] >= dp[x][y + 1]) { out.push(["-", a[x]]); x++; }
|
|
620
|
-
else { out.push(["+", b[y]]); y++; }
|
|
621
|
-
}
|
|
622
|
-
while (x < m) { out.push(["-", a[x]]); x++; }
|
|
623
|
-
while (y < n) { out.push(["+", b[y]]); y++; }
|
|
624
|
-
return out;
|
|
625
|
-
}
|
|
626
|
-
function renderDiff(before, after) {
|
|
627
|
-
if (!diffEl) return;
|
|
628
|
-
diffEl.textContent = "";
|
|
629
|
-
var rows = lineDiff(before, after);
|
|
630
|
-
if (!rows) { renderDiffText("(file too large to diff)"); return; }
|
|
631
|
-
var frag = document.createDocumentFragment();
|
|
632
|
-
rows.forEach(function (row) {
|
|
633
|
-
var ln = document.createElement("span");
|
|
634
|
-
ln.className = "dl " + (row[0] === "+" ? "add" : row[0] === "-" ? "del" : "ctx");
|
|
635
|
-
ln.textContent = row[0] + " " + row[1];
|
|
636
|
-
frag.appendChild(ln);
|
|
637
|
-
});
|
|
638
|
-
diffEl.appendChild(frag);
|
|
639
|
-
}
|
|
640
|
-
function doDiff() {
|
|
641
|
-
if (!cur) return;
|
|
642
|
-
if (diffOn) { setDiffMode(false); setStatus(cur.writable ? "Editable" : "Read-only"); return; }
|
|
643
|
-
setStatus("Loading diff\\u2026");
|
|
644
|
-
fetch(apiBase + "/diff?path=" + encodeURIComponent(cur.path))
|
|
645
|
-
.then(function (r) { return r.json(); })
|
|
646
|
-
.then(function (d) {
|
|
647
|
-
if (!d || !d.ok) { setStatus((d && d.message) || "Could not diff.", "warn"); return; }
|
|
648
|
-
if (!d.shown) { renderDiffText("(diff not shown \\u2014 " + (d.reason || "") + ")"); setDiffMode(true); setStatus(d.status); return; }
|
|
649
|
-
renderDiff(d.before || "", d.after || "");
|
|
650
|
-
setDiffMode(true);
|
|
651
|
-
setStatus(d.status === "unchanged" ? "No changes vs your last commit" : (d.status + " vs HEAD"));
|
|
652
|
-
})
|
|
653
|
-
.catch(function () { setStatus(offlineMsg, "warn"); });
|
|
654
|
-
}
|
|
655
|
-
if (diffBtn) diffBtn.addEventListener("click", doDiff);
|
|
656
|
-
|
|
657
|
-
// cp-publish: draft -> PR-backed candidate for review (unit s6, milestone
|
|
658
|
-
// "PR-Backed Hosted Review Loop") — never straight to live, and no longer
|
|
659
|
-
// straight to the test channel either; it opens/updates a pull request an
|
|
660
|
-
// owner reviews and accepts. Confirm-before-submit per the card.
|
|
661
|
-
function doPublish() {
|
|
662
|
-
if (!cur || !cur.writable || !publishBtn) return;
|
|
663
|
-
if (!window.confirm("Submit your saved edit for review? (Opens/updates a pull request \\u2014 not your live site.)")) return;
|
|
664
|
-
var prevLabel = publishBtn.textContent;
|
|
665
|
-
publishBtn.disabled = true;
|
|
666
|
-
setStatus("Submitting\\u2026");
|
|
667
|
-
fetch(apiBase + "/publish", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ path: cur.path }) })
|
|
668
|
-
.then(function (r) { return r.json().then(function (j) { return { s: r.status, j: j }; }); })
|
|
669
|
-
.then(function (res) {
|
|
670
|
-
if (res.s === 200 && res.j && res.j.ok) { setStatus("Submitted for review \\u2713" + (res.j.prNumber ? " (PR #" + res.j.prNumber + ")" : ""), "ok"); return; }
|
|
671
|
-
if (res.s === 409 && res.j && res.j.status === "no_draft") { setStatus("No unpublished edit to submit.", "warn"); return; }
|
|
672
|
-
setStatus((res.j && res.j.message) || "Submit failed", "warn");
|
|
673
|
-
})
|
|
674
|
-
.catch(function () { setStatus(offlineMsg, "warn"); })
|
|
675
|
-
.then(function () { publishBtn.disabled = false; publishBtn.textContent = prevLabel; });
|
|
676
|
-
}
|
|
677
|
-
if (publishBtn) publishBtn.addEventListener("click", doPublish);
|
|
678
|
-
|
|
679
|
-
function renderNodes(nodes, parentUl, depth) {
|
|
680
|
-
nodes.forEach(function (n) {
|
|
681
|
-
var li = document.createElement("li");
|
|
682
|
-
li.className = "ft-" + n.type;
|
|
683
|
-
var row = document.createElement("div");
|
|
684
|
-
row.className = "ft-row";
|
|
685
|
-
row.style.paddingLeft = (depth * 12 + 10) + "px";
|
|
686
|
-
var icon = document.createElement("span");
|
|
687
|
-
icon.className = "ft-ic";
|
|
688
|
-
icon.textContent = n.type === "dir" ? "\\uD83D\\uDCC1" : "\\uD83D\\uDCC4";
|
|
689
|
-
var label = document.createElement("span");
|
|
690
|
-
label.className = "ft-name";
|
|
691
|
-
label.textContent = n.name;
|
|
692
|
-
row.appendChild(icon); row.appendChild(label); li.appendChild(row);
|
|
693
|
-
if (n.type === "dir") {
|
|
694
|
-
var childUl = document.createElement("ul");
|
|
695
|
-
childUl.className = "ft-children";
|
|
696
|
-
renderNodes(n.children || [], childUl, depth + 1);
|
|
697
|
-
li.appendChild(childUl);
|
|
698
|
-
} else {
|
|
699
|
-
row.tabIndex = 0;
|
|
700
|
-
rowByPath[n.path] = row; // for the C8 programmatic jump
|
|
701
|
-
row.addEventListener("click", function () { openFile(n.path, row); });
|
|
702
|
-
row.addEventListener("keydown", function (e) { if (e.key === "Enter") { e.preventDefault(); openFile(n.path, row); } });
|
|
703
|
-
}
|
|
704
|
-
parentUl.appendChild(li);
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
function load() {
|
|
709
|
-
fetch(apiBase + "/tree")
|
|
710
|
-
.then(function (r) { return r.json(); })
|
|
711
|
-
.then(function (d) {
|
|
712
|
-
treeEl.textContent = "";
|
|
713
|
-
rowByPath = {}; // rebuild the path→row index for this fresh tree
|
|
714
|
-
if (!d || !d.ok) { treeEl.textContent = (d && d.message) || "Could not load your files."; return; }
|
|
715
|
-
var ul = document.createElement("ul");
|
|
716
|
-
ul.className = "ft-root";
|
|
717
|
-
renderNodes(d.tree || [], ul, 0);
|
|
718
|
-
treeEl.appendChild(ul);
|
|
719
|
-
})
|
|
720
|
-
.catch(function () { treeEl.textContent = offlineMsg; });
|
|
721
|
-
}
|
|
722
|
-
load();
|
|
723
|
-
})();
|
|
724
|
-
`;
|
|
725
|
-
|
|
726
|
-
// C8 · Click-to-source capstone — the cockpit side. Runs the live-preview iframe
|
|
727
|
-
// (mini + full-screen dialog) and LISTENS for the framed picker's tot:locate
|
|
728
|
-
// postMessage. The picker (ai-widget-client.js, auto-injected into the same-origin
|
|
729
|
-
// preview) resolves a click to {path,line,col} via /__tot/locate, then — because it
|
|
730
|
-
// is framed — postMessages the result here instead of showing its own toast. We
|
|
731
|
-
// verify event.origin === our origin, then hand off to the Files panel's
|
|
732
|
-
// window.__totOpenFileAt to open the source + drop the caret. Only content-region
|
|
733
|
-
// hits ever arrive (chrome/refused clicks stay silent in the frame), per decision
|
|
734
|
-
// click-to-source-anchors-at-content-region.
|
|
735
|
-
const c8Script = `
|
|
736
|
-
(function () {
|
|
737
|
-
var isDev = ${JSON.stringify(isLocalDevLoop)};
|
|
738
|
-
var hostedAppDomain = ${JSON.stringify(appDomain)};
|
|
739
|
-
var dlg = document.getElementById("previewDialog");
|
|
740
|
-
var expandBtn = document.getElementById("previewExpand");
|
|
741
|
-
var closeBtn = document.getElementById("previewClose");
|
|
742
|
-
var dlgFrame = document.getElementById("previewDialogFrame");
|
|
743
|
-
var miniFrame = document.getElementById("previewFrame");
|
|
744
|
-
var urlEl = document.getElementById("previewUrl");
|
|
745
|
-
|
|
746
|
-
if (miniFrame) {
|
|
747
|
-
if (isDev) {
|
|
748
|
-
// Point the preview at the DEV tenant (TOT_DEV_TENANT), read from the server
|
|
749
|
-
// via /__tot/dev-loop-status — NOT the SSR appDomain (workerd can't see
|
|
750
|
-
// TOT_DEV_*). Same-origin, so the auto-injected picker can postMessage back.
|
|
751
|
-
fetch("/__tot/dev-loop-status")
|
|
752
|
-
.then(function (r) { return r.json(); })
|
|
753
|
-
.then(function (s) {
|
|
754
|
-
var tenant = s && s.tenant;
|
|
755
|
-
if (!tenant) return;
|
|
756
|
-
var src = "/" + tenant + "/";
|
|
757
|
-
miniFrame.src = src;
|
|
758
|
-
if (urlEl) urlEl.textContent = "localhost preview · " + tenant;
|
|
759
|
-
})
|
|
760
|
-
.catch(function () { /* dev server unreachable — preview stays blank */ });
|
|
761
|
-
} else if (hostedAppDomain) {
|
|
762
|
-
// Hosted: the SSR-resolved tenant IS the signed-in developer's own store
|
|
763
|
-
// (cp-env's capability gate already scoped it) — same-origin, so a cp-write
|
|
764
|
-
// save + this reload shows the developer's own draft overlay via the
|
|
765
|
-
// middleware's tot_dev_draft cookie check.
|
|
766
|
-
miniFrame.src = "/" + hostedAppDomain + "/";
|
|
767
|
-
if (urlEl) urlEl.textContent = "your store · " + hostedAppDomain;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
if (expandBtn && dlg) {
|
|
772
|
-
expandBtn.addEventListener("click", function () {
|
|
773
|
-
if (dlgFrame && !dlgFrame.getAttribute("src") && miniFrame) dlgFrame.src = miniFrame.getAttribute("src") || miniFrame.src;
|
|
774
|
-
if (typeof dlg.showModal === "function") dlg.showModal(); else dlg.setAttribute("open", "");
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
if (closeBtn && dlg) closeBtn.addEventListener("click", function () { dlg.close ? dlg.close() : dlg.removeAttribute("open"); });
|
|
778
|
-
if (dlg) dlg.addEventListener("click", function (e) { if (e.target === dlg) { dlg.close ? dlg.close() : dlg.removeAttribute("open"); } });
|
|
779
|
-
|
|
780
|
-
// The one privileged inbound message: a source location from OUR OWN origin's
|
|
781
|
-
// framed preview. Reject any other origin, shape, or type outright.
|
|
782
|
-
window.addEventListener("message", function (e) {
|
|
783
|
-
if (e.origin !== window.location.origin) return;
|
|
784
|
-
var d = e.data;
|
|
785
|
-
if (!d || d.type !== "tot:locate" || typeof d.path !== "string") return;
|
|
786
|
-
if (typeof window.__totOpenFileAt === "function") {
|
|
787
|
-
var opened = window.__totOpenFileAt(d.path, d.line, d.col);
|
|
788
|
-
// If the preview was expanded full-screen, close it so the editor + caret are visible.
|
|
789
|
-
if (opened && dlg && dlg.open) { dlg.close ? dlg.close() : dlg.removeAttribute("open"); }
|
|
790
|
-
}
|
|
791
|
-
});
|
|
792
|
-
})();
|
|
793
|
-
`;
|
|
794
|
-
|
|
795
|
-
// Hosted /dev activity log (hosted-dev-activity-bridge). Polls the NEW
|
|
796
|
-
// GET /api/dev/activity — a translated, file-level feed of what the local dev
|
|
797
|
-
// loop reported — and renders it newest-first. Also drives the "your edit
|
|
798
|
-
// landed" celebration: the first poll primes a baseline (sessionStorage, same
|
|
799
|
-
// survive-a-reload rationale as the local stepper's editBase), and any LATER
|
|
800
|
-
// event past that baseline triggers the banner + a lightweight confetti burst.
|
|
801
|
-
// POLL (not SSE): Workers are stateless per request, so a redeploy mid-session
|
|
802
|
-
// needs no reconnect logic here. Rendered only when hostedActivity (never
|
|
803
|
-
// alongside the isLocalDevLoop-gated local scripts above).
|
|
804
|
-
const hostedActivityScript = `
|
|
805
|
-
(function () {
|
|
806
|
-
var root = document.getElementById("hostedActivity");
|
|
807
|
-
if (!root) return;
|
|
808
|
-
|
|
809
|
-
// The request's tenant prefix ("" host-routed, "/<tenant>" path-routed). Every
|
|
810
|
-
// /api/dev/* call below carries it so the activity feed + the CLI sign-in-code mint
|
|
811
|
-
// run in THIS store's scope (locals.tenant on the API request) — the same store this
|
|
812
|
-
// /<tenant>/cockpit rendered for, never the session's first membership.
|
|
813
|
-
var basePath = ${JSON.stringify(basePath)};
|
|
814
|
-
|
|
815
|
-
// ?reset — a TRUE reset for re-testing the onboarding loop: clear THIS browser's
|
|
816
|
-
// stepper progress AND the server-side KV (live-runtime record + activity buffer),
|
|
817
|
-
// then reload clean. Clearing the server too means a stopped local loop stops reading
|
|
818
|
-
// as "up" immediately instead of lingering until the runtime TTL/live-window expires.
|
|
819
|
-
try {
|
|
820
|
-
if (/[?&]reset\\b/.test(location.search)) {
|
|
821
|
-
["tot.hosted.steps", "tot.hosted.current", "tot.hosted.editBase"].forEach(function (k) {
|
|
822
|
-
try { window.sessionStorage.removeItem(k); } catch (e) {}
|
|
823
|
-
});
|
|
824
|
-
var reloaded = false;
|
|
825
|
-
var doneReset = function () { if (reloaded) return; reloaded = true; location.replace(location.pathname); };
|
|
826
|
-
setTimeout(doneReset, 2000); // fallback so a hung request still lands us fresh
|
|
827
|
-
try { fetch(basePath + "/api/dev/activity", { method: "DELETE", cache: "no-store" }).then(doneReset, doneReset); }
|
|
828
|
-
catch (e) { doneReset(); }
|
|
829
|
-
return; // the post-clear reload re-runs this script against a fresh, empty state
|
|
830
|
-
}
|
|
831
|
-
} catch (e) {}
|
|
832
|
-
|
|
833
|
-
var tableEl = document.getElementById("haTable");
|
|
834
|
-
var bodyEl = document.getElementById("haBody");
|
|
835
|
-
var emptyEl = document.getElementById("haEmpty");
|
|
836
|
-
|
|
837
|
-
// Bridge strip — the rail's power source, fed by the SAME poll's \`runtime\` object
|
|
838
|
-
// (no second poller). \`runtime\` is { cliVersion, runnerVersion, url, aliveAt, live }
|
|
839
|
-
// reported by the developer's local \`tot\` CLI; absent until its data layer lands,
|
|
840
|
-
// so every read here is defensive (missing runtime -> the strip stays hidden).
|
|
841
|
-
var bridge = document.getElementById("bridge");
|
|
842
|
-
var bUrl = document.getElementById("bUrl");
|
|
843
|
-
var bGo = document.getElementById("bGo");
|
|
844
|
-
var bKicker = document.getElementById("bKicker");
|
|
845
|
-
var bHint = document.getElementById("bHint");
|
|
846
|
-
var bDiag = document.getElementById("bDiag");
|
|
847
|
-
var bTxt = document.getElementById("bTxt");
|
|
848
|
-
var bObstacle = document.getElementById("bObstacle");
|
|
849
|
-
var bObTitle = document.getElementById("bObTitle");
|
|
850
|
-
var bObMsg = document.getElementById("bObMsg");
|
|
851
|
-
var bObCmd = document.getElementById("bObCmd");
|
|
852
|
-
var bObNote = document.getElementById("bObNote");
|
|
853
|
-
var doneRunMeta = document.getElementById("doneRunMeta");
|
|
854
|
-
function updateBridge(rt, obstacle) {
|
|
855
|
-
if (!bridge) return;
|
|
856
|
-
var live = !!(rt && rt.live);
|
|
857
|
-
if (live) sawLive = true;
|
|
858
|
-
// Obstacle lane (v2 §6): a failed \`tot start\` is the ONE reason the strip
|
|
859
|
-
// shows BEFORE the loop was ever live this page-view — the developer needs the
|
|
860
|
-
// fix, not a silent "watching…". The server returns \`obstacle\` only when there
|
|
861
|
-
// is no live heartbeat, so a live loop supersedes it. Fill from server copy.
|
|
862
|
-
if (obstacle && !live) {
|
|
863
|
-
bridge.hidden = false;
|
|
864
|
-
bridge.setAttribute("data-live", "obstacle");
|
|
865
|
-
if (bTxt) bTxt.hidden = true;
|
|
866
|
-
if (bDiag) bDiag.hidden = true;
|
|
867
|
-
if (bGo) bGo.hidden = true;
|
|
868
|
-
if (bHint) bHint.hidden = true;
|
|
869
|
-
if (bObstacle) bObstacle.hidden = false;
|
|
870
|
-
if (bObTitle) bObTitle.textContent = obstacle.title || "Setup hit a snag";
|
|
871
|
-
if (bObMsg) bObMsg.textContent = obstacle.message || "";
|
|
872
|
-
if (bObCmd) {
|
|
873
|
-
if (obstacle.fixCommand) { bObCmd.textContent = obstacle.fixCommand; bObCmd.hidden = false; }
|
|
874
|
-
else bObCmd.hidden = true;
|
|
875
|
-
}
|
|
876
|
-
if (bObNote) bObNote.textContent = obstacle.fixNote || "";
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
// Back to the normal live/quiet strip: restore the pieces the obstacle state hid.
|
|
880
|
-
if (bObstacle) bObstacle.hidden = true;
|
|
881
|
-
if (bTxt) bTxt.hidden = false;
|
|
882
|
-
if (!rt || !rt.url) { bridge.hidden = true; return; }
|
|
883
|
-
// On FIRST arrival, a stale runtime record (offline, never seen live this
|
|
884
|
-
// page-view — e.g. left over from a prior run, still within the heartbeat TTL)
|
|
885
|
-
// must NOT show — let Step 1 "Get your store running" do the work. Only surface
|
|
886
|
-
// the strip once it's live, or after it's gone quiet HAVING been live this page-view.
|
|
887
|
-
if (!live && !sawLive) { bridge.hidden = true; return; }
|
|
888
|
-
bridge.hidden = false;
|
|
889
|
-
bridge.setAttribute("data-live", live ? "1" : "0");
|
|
890
|
-
if (bUrl) { bUrl.textContent = rt.url; bUrl.href = rt.url; }
|
|
891
|
-
// No point offering "Open your store" to a server that stopped answering.
|
|
892
|
-
if (bGo) { bGo.href = rt.url; bGo.hidden = !live; }
|
|
893
|
-
if (bKicker) bKicker.textContent = live ? "Live on your machine" : "Went quiet";
|
|
894
|
-
if (bHint) bHint.hidden = live; // recovery hint only while offline
|
|
895
|
-
if (doneRunMeta) doneRunMeta.textContent = rt.url; // the collapsed Step-1 row's meta
|
|
896
|
-
if (bDiag) {
|
|
897
|
-
if (rt.cliVersion) {
|
|
898
|
-
bDiag.textContent = "tot CLI v" + rt.cliVersion + (rt.runnerVersion ? " \\u00b7 runner v" + rt.runnerVersion : "");
|
|
899
|
-
bDiag.hidden = false;
|
|
900
|
-
} else {
|
|
901
|
-
bDiag.hidden = true;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
updateEditHint(rt); // suggest the exact "open this file" command on Step 2
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
// Item 6 — once the CLI reports the developer's $EDITOR, show the EXACT command to
|
|
908
|
-
// open the first file to edit (e.g. \`code content/home.html\`). Without one, guess
|
|
909
|
-
// from the browser's OS (a reasonable proxy for the local dev machine in the common
|
|
910
|
-
// case of viewing the cockpit on the same laptop) and default to VS Code (\`code\`) —
|
|
911
|
-
// the most common case — with an alternate + AI-agent note underneath.
|
|
912
|
-
var editOpenCmd = document.getElementById("editOpenCmd");
|
|
913
|
-
var editOpenHint = document.getElementById("editOpenHint");
|
|
914
|
-
function platformOpenDirCmd(dirArg) {
|
|
915
|
-
var plat = String((navigator.userAgentData && navigator.userAgentData.platform) || navigator.platform || navigator.userAgent || "");
|
|
916
|
-
if (/win/i.test(plat)) return "explorer " + dirArg;
|
|
917
|
-
if (/mac/i.test(plat)) return "open " + dirArg;
|
|
918
|
-
return "xdg-open " + dirArg;
|
|
919
|
-
}
|
|
920
|
-
function updateEditHint(rt) {
|
|
921
|
-
if (!editOpenCmd) return;
|
|
922
|
-
var editor = rt && typeof rt.editor === "string" ? rt.editor : "";
|
|
923
|
-
var cwd = rt && typeof rt.cwd === "string" ? rt.cwd : "";
|
|
924
|
-
// Prefer the FULL path (works from any directory) when the CLI reports the
|
|
925
|
-
// project dir; else a project-relative path with a "run it from there" note.
|
|
926
|
-
var file = cwd ? (cwd.replace(/\\/+$/, "") + "/content/home.html") : "content/home.html";
|
|
927
|
-
editOpenCmd.textContent = (editor || "code") + " " + file;
|
|
928
|
-
if (editOpenHint) {
|
|
929
|
-
if (editor) {
|
|
930
|
-
editOpenHint.textContent = cwd
|
|
931
|
-
? "Run that anywhere to open the file — then change a line and save."
|
|
932
|
-
: "Run that from your project directory — then change a line and save.";
|
|
933
|
-
} else {
|
|
934
|
-
var altCmd = platformOpenDirCmd(cwd || ".");
|
|
935
|
-
editOpenHint.textContent = "Using VS Code? Run that. Prefer Cursor, another editor, or an AI coding " +
|
|
936
|
-
"agent like Claude? Same idea — open the folder (\`" + altCmd + "\`) or file above and edit there. " +
|
|
937
|
-
"Set $EDITOR and we'll show your exact command instead.";
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
var SS = window.sessionStorage;
|
|
943
|
-
function load(k, d) { try { var v = SS.getItem(k); return v === null ? d : JSON.parse(v); } catch (e) { return d; } }
|
|
944
|
-
function put(k, v) { try { SS.setItem(k, JSON.stringify(v)); } catch (e) {} }
|
|
945
|
-
|
|
946
|
-
var baseline = load("tot.hosted.editBase", null); // null = not yet primed by a first poll
|
|
947
|
-
var expanded = false; // activity list: last 3 by default, expand to all
|
|
948
|
-
// Have we seen the store go live during THIS PAGE VIEW? In-memory ONLY (not
|
|
949
|
-
// persisted): a fresh land or a reload starts false, so a stale/offline record can
|
|
950
|
-
// never greet the developer with "went quiet" — Step 1 owns that moment. The
|
|
951
|
-
// recovery ("went quiet") state shows only after we actually watched it live → drop.
|
|
952
|
-
var sawLive = false;
|
|
953
|
-
|
|
954
|
-
// Guided accordion. Each step opens with its content; when its activity is detected
|
|
955
|
-
// it animates CLOSED to a reopenable done-bar, and a single "Next Up" panel at the
|
|
956
|
-
// bottom expands to tease the following step with ONE CTA that opens it.
|
|
957
|
-
// steps = completion flags {run, edit}
|
|
958
|
-
// current = the OPEN step key, or null between steps (Next Up showing)
|
|
959
|
-
// Both persisted so a reload restores the exact place in the flow.
|
|
960
|
-
var steps = load("tot.hosted.steps", { run: false, edit: false });
|
|
961
|
-
// Live-driven flow. current is the EXPLICITLY-opened step ("run" when Step 1 was
|
|
962
|
-
// reopened, "edit"/"next" for those) or null — the default view, where Step 1's
|
|
963
|
-
// open/minimized state is driven by whether localhost is LIVE. NOT persisted: a
|
|
964
|
-
// refresh lands in this default view (progress in steps still persists).
|
|
965
|
-
var current = null;
|
|
966
|
-
var liveNow = false; // is localhost live right now? set each poll from runtime.live
|
|
967
|
-
var bodies = {
|
|
968
|
-
run: document.getElementById("stage-run"),
|
|
969
|
-
edit: document.getElementById("hosted-stage-edit"),
|
|
970
|
-
next: document.getElementById("hosted-stage-next"),
|
|
971
|
-
};
|
|
972
|
-
// Rail station elements — the reskin's DOM (applyView targets these).
|
|
973
|
-
var stEls = {
|
|
974
|
-
run: document.getElementById("st-run"),
|
|
975
|
-
edit: document.getElementById("st-edit"),
|
|
976
|
-
next: document.getElementById("st-next"),
|
|
977
|
-
};
|
|
978
|
-
var segRun = document.getElementById("seg-run");
|
|
979
|
-
var segEdit = document.getElementById("seg-edit");
|
|
980
|
-
var doneRunEl = document.getElementById("done-run");
|
|
981
|
-
var doneEditEl = document.getElementById("done-edit");
|
|
982
|
-
var doneEditMeta = document.getElementById("doneEditMeta");
|
|
983
|
-
var lockEditEl = document.getElementById("lock-edit");
|
|
984
|
-
var lockNextEl = document.getElementById("lock-next");
|
|
985
|
-
var teaseEditEl = document.getElementById("tease-edit");
|
|
986
|
-
var teaseNextEl = document.getElementById("tease-next");
|
|
987
|
-
var quietNoteEl = document.getElementById("quietNote");
|
|
988
|
-
|
|
989
|
-
// Persisted state mutations funnel through here so the sessionStorage write can
|
|
990
|
-
// never be forgotten at a call site (progress in steps + the edit baseline are
|
|
991
|
-
// the only things that survive a reload).
|
|
992
|
-
function completeStep(key) {
|
|
993
|
-
if (steps[key]) return false; // already done — no-op
|
|
994
|
-
steps[key] = true; put("tot.hosted.steps", steps);
|
|
995
|
-
return true; // "just completed" — caller may celebrate
|
|
996
|
-
}
|
|
997
|
-
function setBaseline(v) { baseline = v; put("tot.hosted.editBase", baseline); }
|
|
998
|
-
|
|
999
|
-
// ============================================================================
|
|
1000
|
-
// STATE → VIEW → DOM. The flow's behaviour is split into three layers so the
|
|
1001
|
-
// rules live in ONE readable place and every render goes through one path:
|
|
1002
|
-
// 1. derive — computeView(): pure, reads state (steps/current/liveNow),
|
|
1003
|
-
// returns a view model. EVERY visibility/emphasis rule is here.
|
|
1004
|
-
// 2. apply — applyView(): the ONLY code that mutates the rail DOM (station
|
|
1005
|
-
// statuses, segment lit/flow, step bodies, done rows, locked
|
|
1006
|
-
// labels, teasers, quiet note, activity).
|
|
1007
|
-
// 3. renderSteps(): apply(compute()). Call after ANY state change — never poke
|
|
1008
|
-
// an element directly from a handler or the poll.
|
|
1009
|
-
// The signal-rail reskin kept layer 1's RULES verbatim (only added derived
|
|
1010
|
-
// fields) and rewrote applyView() + the markup/CSS it targets.
|
|
1011
|
-
// ============================================================================
|
|
1012
|
-
|
|
1013
|
-
// --- derive ---------------------------------------------------------------
|
|
1014
|
-
function unlocked(step) {
|
|
1015
|
-
return step === "run" || (step === "edit" && steps.run) || (step === "next" && steps.edit);
|
|
1016
|
-
}
|
|
1017
|
-
// Step 1 is OPEN while you still need to get the store running (localhost not live) or
|
|
1018
|
-
// when you explicitly reopened it; it MINIMIZES to a done row once localhost is live —
|
|
1019
|
-
// in opposition to revealing the Step-2 teaser.
|
|
1020
|
-
function step1Open() {
|
|
1021
|
-
return current === "run" || (current === null && !(steps.run && liveNow));
|
|
1022
|
-
}
|
|
1023
|
-
// The complete view model — no DOM access, so the rules can be read top to bottom:
|
|
1024
|
-
// open which step BODY is expanded {run, edit, next}
|
|
1025
|
-
// seg which rail segment is LIT {run, edit}
|
|
1026
|
-
// flow do lit segments flow downward (live) boolean
|
|
1027
|
-
// stRun/Edit/Next each station's status "open" | "done" | "next" | "locked"
|
|
1028
|
-
// teaseEdit/Next is that step's forward teaser shown (live-gated)
|
|
1029
|
-
// doneRun/Edit is that step's collapsed done row shown
|
|
1030
|
-
// lockEdit/Next is that step's locked label shown
|
|
1031
|
-
// quietNote the Step-1 "went quiet" note (re-inflated after a drop)
|
|
1032
|
-
// showActivity is "Your activity" visible (NEVER while Step 1 is open)
|
|
1033
|
-
function computeView() {
|
|
1034
|
-
var s1 = step1Open();
|
|
1035
|
-
// Forward teasers show ONLY while localhost is live — a forward CTA only makes
|
|
1036
|
-
// sense while the loop is alive. When the loop goes quiet Step 1 re-inflates to own
|
|
1037
|
-
// that moment, so the teaser retracts. Either is also hidden while the step it
|
|
1038
|
-
// teases is already open.
|
|
1039
|
-
var teaseEdit = steps.run && !steps.edit && liveNow && current !== "edit";
|
|
1040
|
-
var teaseNext = steps.edit && liveNow && current !== "next";
|
|
1041
|
-
return {
|
|
1042
|
-
open: { run: s1, edit: current === "edit", next: current === "next" },
|
|
1043
|
-
seg: { run: steps.run, edit: steps.edit },
|
|
1044
|
-
flow: liveNow,
|
|
1045
|
-
stRun: s1 ? "open" : "done",
|
|
1046
|
-
stEdit: steps.edit ? "done" : (current === "edit") ? "open" : teaseEdit ? "next" : "locked",
|
|
1047
|
-
stNext: (current === "next") ? "open" : teaseNext ? "next" : "locked",
|
|
1048
|
-
teaseEdit: teaseEdit,
|
|
1049
|
-
teaseNext: teaseNext,
|
|
1050
|
-
doneRun: !s1 && steps.run,
|
|
1051
|
-
doneEdit: steps.edit && current !== "edit",
|
|
1052
|
-
lockEdit: !steps.run,
|
|
1053
|
-
lockNext: !steps.edit,
|
|
1054
|
-
quietNote: s1 && steps.run && !liveNow && sawLive,
|
|
1055
|
-
showActivity: !s1 && steps.run,
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
// --- apply (targets the rail DOM; the rules live in computeView above) -----
|
|
1060
|
-
function applyView(v) {
|
|
1061
|
-
if (stEls.run) stEls.run.setAttribute("data-st", v.stRun);
|
|
1062
|
-
if (stEls.edit) stEls.edit.setAttribute("data-st", v.stEdit);
|
|
1063
|
-
if (stEls.next) stEls.next.setAttribute("data-st", v.stNext);
|
|
1064
|
-
if (segRun) { segRun.setAttribute("data-lit", v.seg.run ? "1" : "0"); segRun.setAttribute("data-flow", v.flow ? "1" : "0"); }
|
|
1065
|
-
if (segEdit) { segEdit.setAttribute("data-lit", v.seg.edit ? "1" : "0"); segEdit.setAttribute("data-flow", v.flow ? "1" : "0"); }
|
|
1066
|
-
if (bodies.run) bodies.run.hidden = !v.open.run;
|
|
1067
|
-
if (bodies.edit) bodies.edit.hidden = !v.open.edit;
|
|
1068
|
-
if (bodies.next) bodies.next.hidden = !v.open.next;
|
|
1069
|
-
if (doneRunEl) doneRunEl.hidden = !v.doneRun;
|
|
1070
|
-
if (doneEditEl) doneEditEl.hidden = !v.doneEdit;
|
|
1071
|
-
if (lockEditEl) lockEditEl.hidden = !v.lockEdit;
|
|
1072
|
-
if (lockNextEl) lockNextEl.hidden = !v.lockNext;
|
|
1073
|
-
if (teaseEditEl) teaseEditEl.hidden = !v.teaseEdit;
|
|
1074
|
-
if (teaseNextEl) teaseNextEl.hidden = !v.teaseNext;
|
|
1075
|
-
if (quietNoteEl) quietNoteEl.hidden = !v.quietNote;
|
|
1076
|
-
if (root) root.hidden = !v.showActivity;
|
|
1077
|
-
}
|
|
1078
|
-
// Single render entry point — derive, then apply. (Named renderSteps so the
|
|
1079
|
-
// existing call sites keep working; distinct from the activity render(events).)
|
|
1080
|
-
function renderSteps() { applyView(computeView()); }
|
|
1081
|
-
|
|
1082
|
-
function expandStage(el) {
|
|
1083
|
-
if (!el) return;
|
|
1084
|
-
el.hidden = false;
|
|
1085
|
-
el.classList.add("expanding");
|
|
1086
|
-
setTimeout(function () { if (el && el.classList) el.classList.remove("expanding"); }, 420);
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
// Open a step (from a teaser CTA or a collapsed done row) — animates it in.
|
|
1090
|
-
function goTo(step) {
|
|
1091
|
-
if (!unlocked(step)) return;
|
|
1092
|
-
current = step;
|
|
1093
|
-
renderSteps();
|
|
1094
|
-
expandStage(bodies[step]);
|
|
1095
|
-
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
// Rail click wiring: done rows reopen their step; teaser CTAs open the teased step.
|
|
1099
|
-
if (doneRunEl) doneRunEl.addEventListener("click", function () { goTo("run"); });
|
|
1100
|
-
if (doneEditEl) doneEditEl.addEventListener("click", function () { goTo("edit"); });
|
|
1101
|
-
var ctaEdit = document.getElementById("cta-edit");
|
|
1102
|
-
if (ctaEdit) ctaEdit.addEventListener("click", function () { goTo("edit"); });
|
|
1103
|
-
var ctaNext = document.getElementById("cta-next");
|
|
1104
|
-
if (ctaNext) ctaNext.addEventListener("click", function () { goTo("next"); });
|
|
1105
|
-
|
|
1106
|
-
// Absolute clock time with seconds (e.g. "2:57:14 PM") — a fixed point, not a
|
|
1107
|
-
// churning duration. Full date+time stays on the hover title (render()).
|
|
1108
|
-
function rel(ts) {
|
|
1109
|
-
try {
|
|
1110
|
-
return new Date(Number(ts)).toLocaleTimeString([], { hour: "numeric", minute: "2-digit", second: "2-digit" });
|
|
1111
|
-
} catch (x) {
|
|
1112
|
-
return "";
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
// Toast — reuses the shared #toast element (also driven by the copy script).
|
|
1117
|
-
var toastEl = document.getElementById("toast");
|
|
1118
|
-
var toastMsgEl = document.getElementById("toastMsg");
|
|
1119
|
-
var toastT;
|
|
1120
|
-
function toast(msg) {
|
|
1121
|
-
if (!toastEl || !toastMsgEl) return;
|
|
1122
|
-
toastMsgEl.textContent = msg;
|
|
1123
|
-
toastEl.classList.add("show");
|
|
1124
|
-
clearTimeout(toastT);
|
|
1125
|
-
toastT = setTimeout(function () { toastEl.classList.remove("show"); }, 2600);
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
// Celebration — replaces the full-screen confetti. Stamps the Step-2 node (spark
|
|
1129
|
-
// burst + stamp, via the .celebrating class) and shows a toast. Reduced-motion
|
|
1130
|
-
// disables the burst/stamp in CSS; the toast still shows.
|
|
1131
|
-
function celebrate(file) {
|
|
1132
|
-
var st = stEls.edit;
|
|
1133
|
-
if (st) {
|
|
1134
|
-
st.classList.remove("celebrating");
|
|
1135
|
-
void st.offsetWidth; // restart the burst animation
|
|
1136
|
-
st.classList.add("celebrating");
|
|
1137
|
-
setTimeout(function () { if (st && st.classList) st.classList.remove("celebrating"); }, 1100);
|
|
1138
|
-
}
|
|
1139
|
-
if (doneEditMeta && file) doneEditMeta.textContent = file;
|
|
1140
|
-
toast("Saved " + (file || "your file") + " \\u2014 your store reloaded. That's the loop.");
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
var toggleEl = document.getElementById("haToggle");
|
|
1144
|
-
var VISIBLE = 3; // show the latest 3 by default; expand for the rest
|
|
1145
|
-
if (toggleEl) {
|
|
1146
|
-
toggleEl.addEventListener("click", function () { expanded = !expanded; render(lastEvents); });
|
|
1147
|
-
}
|
|
1148
|
-
var lastEvents = [];
|
|
1149
|
-
|
|
1150
|
-
function render(events) {
|
|
1151
|
-
lastEvents = events;
|
|
1152
|
-
// Item 3 — drop the "up"/"running locally" pings; the live widget at the top
|
|
1153
|
-
// already conveys that. Only real FILE activity belongs in the table, and each
|
|
1154
|
-
// field is its own <td> so time / action / file never run together.
|
|
1155
|
-
var real = (events || []).filter(function (e) { return e && e.event !== "up"; });
|
|
1156
|
-
Array.prototype.forEach.call(bodyEl.querySelectorAll(".ha-row"), function (n) { n.remove(); });
|
|
1157
|
-
if (!real.length) {
|
|
1158
|
-
emptyEl.hidden = false;
|
|
1159
|
-
if (tableEl) tableEl.hidden = true;
|
|
1160
|
-
if (toggleEl) toggleEl.hidden = true;
|
|
1161
|
-
return;
|
|
1162
|
-
}
|
|
1163
|
-
emptyEl.hidden = true;
|
|
1164
|
-
if (tableEl) tableEl.hidden = false;
|
|
1165
|
-
var newestFirst = real.slice().reverse();
|
|
1166
|
-
var shown = expanded ? newestFirst : newestFirst.slice(0, VISIBLE);
|
|
1167
|
-
shown.forEach(function (e, i) {
|
|
1168
|
-
var tr = document.createElement("tr");
|
|
1169
|
-
tr.className = i === 0 ? "ha-row is-new" : "ha-row"; // accent the newest row
|
|
1170
|
-
var when = document.createElement("td");
|
|
1171
|
-
when.className = "ha-when";
|
|
1172
|
-
when.textContent = rel(e.at);
|
|
1173
|
-
try { when.title = new Date(e.at).toLocaleString(); } catch (x) {} // exact time on hover
|
|
1174
|
-
var desc = document.createElement("td");
|
|
1175
|
-
desc.className = "ha-desc";
|
|
1176
|
-
desc.textContent = e.description;
|
|
1177
|
-
var file = document.createElement("td");
|
|
1178
|
-
file.className = "ha-file";
|
|
1179
|
-
file.textContent = e.file || ""; // the concrete file name, e.g. content/home.html
|
|
1180
|
-
tr.appendChild(when); tr.appendChild(desc); tr.appendChild(file);
|
|
1181
|
-
bodyEl.appendChild(tr);
|
|
1182
|
-
});
|
|
1183
|
-
if (toggleEl) {
|
|
1184
|
-
if (real.length > VISIBLE) {
|
|
1185
|
-
toggleEl.hidden = false;
|
|
1186
|
-
toggleEl.textContent = expanded ? "Show fewer" : "Show all " + real.length;
|
|
1187
|
-
} else {
|
|
1188
|
-
toggleEl.hidden = true;
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
// Adaptive poll cadence + visibility pause (2026-07-15 KV-quota incident: a
|
|
1194
|
-
// single backgrounded tab, polling flat-out every 3s with no pause, burned
|
|
1195
|
-
// through the ENTIRE account-wide daily KV read quota by itself and took
|
|
1196
|
-
// down real customer traffic with 500s). Two rules:
|
|
1197
|
-
// 1. HIDDEN tab → don't poll at all. A backgrounded/minimized tab can't be
|
|
1198
|
-
// "watching" the loop; resume (with an immediate catch-up poll) the
|
|
1199
|
-
// instant it becomes visible again.
|
|
1200
|
-
// 2. VISIBLE but the loop is already fully celebrated (steps.run AND
|
|
1201
|
-
// steps.edit) → the high-stakes "is it working yet" moment has passed,
|
|
1202
|
-
// so back off to a slow cadence. Still visible AND still mid-loop →
|
|
1203
|
-
// keep the fast cadence (this is the one case where sub-second-feeling
|
|
1204
|
-
// responsiveness genuinely matters — a real developer staring at the
|
|
1205
|
-
// screen waiting to see their save land).
|
|
1206
|
-
var FAST_MS = 3000; // actively watching for the loop to prove itself
|
|
1207
|
-
var SLOW_MS = 20000; // loop already celebrated — just an occasional glance
|
|
1208
|
-
var pollTimer = null;
|
|
1209
|
-
|
|
1210
|
-
function nextDelay() {
|
|
1211
|
-
return (steps.run && steps.edit) ? SLOW_MS : FAST_MS;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
function scheduleNext() {
|
|
1215
|
-
if (pollTimer) { clearTimeout(pollTimer); pollTimer = null; }
|
|
1216
|
-
if (document.hidden) return; // stay fully paused; visibilitychange resumes us
|
|
1217
|
-
pollTimer = setTimeout(poll, nextDelay());
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
function poll() {
|
|
1221
|
-
fetch(basePath + "/api/dev/activity", { cache: "no-store" })
|
|
1222
|
-
.then(function (r) { return r.ok ? r.json() : Promise.reject(); })
|
|
1223
|
-
.then(function (d) {
|
|
1224
|
-
var events = (d && d.events) || [];
|
|
1225
|
-
var rt = d && d.runtime;
|
|
1226
|
-
var isLive = !!(rt && rt.live);
|
|
1227
|
-
liveNow = isLive; // drives Step 1 minimize + the Step-2 teaser visibility below
|
|
1228
|
-
updateBridge(rt, d && d.obstacle); // same poll drives the bridge strip (+ obstacle lane)
|
|
1229
|
-
render(events);
|
|
1230
|
-
// b1 fix — Step-2 completion must key off the SAME events the feed shows:
|
|
1231
|
-
// REAL file edits (event !== "up"), NOT the single newest buffer entry. The
|
|
1232
|
-
// local loop interleaves recurring "up" liveness pings into this one ring
|
|
1233
|
-
// buffer, so the newest event is frequently an "up" ping even right after a
|
|
1234
|
-
// save. The old gate read events[last]: when an "up" ping landed AFTER the
|
|
1235
|
-
// edit it (1) made lastIsEdit false so completeStep("edit") never fired, and
|
|
1236
|
-
// (2) still advanced the baseline watermark past the edit's timestamp —
|
|
1237
|
-
// permanently stranding Step 2 even though the save rendered in "Your
|
|
1238
|
-
// activity". Filter to real edits (mirroring render()'s filter) and advance
|
|
1239
|
-
// the watermark only from real edits, so a liveness ping can neither satisfy
|
|
1240
|
-
// nor poison the gate.
|
|
1241
|
-
var realEdits = events.filter(function (e) { return e && e.event !== "up"; });
|
|
1242
|
-
var lastEdit = realEdits.length ? realEdits[realEdits.length - 1] : null;
|
|
1243
|
-
var latestEdit = lastEdit ? lastEdit.at : 0;
|
|
1244
|
-
// Step 1 "Get running" completes once localhost reports LIVE (or any activity
|
|
1245
|
-
// arrives). Once complete, renderSteps() minimizes Step 1 and reveals the Step-2 nudge.
|
|
1246
|
-
if (isLive || events.length) completeStep("run");
|
|
1247
|
-
if (baseline === null) {
|
|
1248
|
-
setBaseline(latestEdit); // first poll primes the watermark from real edits — no edit inferred yet
|
|
1249
|
-
} else if (latestEdit > baseline) {
|
|
1250
|
-
setBaseline(latestEdit);
|
|
1251
|
-
// A real FILE edit landed after the watermark → Step 2 completes (once).
|
|
1252
|
-
// completeStep flips + persists once, returning true on that first poll only.
|
|
1253
|
-
if (completeStep("edit")) {
|
|
1254
|
-
celebrate(lastEdit && lastEdit.file);
|
|
1255
|
-
if (current === "edit") current = null; // minimize Step 2 → reveal Step-3 teaser
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
// ALWAYS re-render so a localhost drop hides the Step-2 nudge + reopens Step 1.
|
|
1259
|
-
renderSteps();
|
|
1260
|
-
})
|
|
1261
|
-
.catch(function () {})
|
|
1262
|
-
.then(scheduleNext); // reschedule (or not, if now hidden) regardless of outcome
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
document.addEventListener("visibilitychange", function () {
|
|
1266
|
-
if (document.hidden) {
|
|
1267
|
-
if (pollTimer) { clearTimeout(pollTimer); pollTimer = null; }
|
|
1268
|
-
} else {
|
|
1269
|
-
poll(); // catch up immediately; poll() reschedules itself at the end
|
|
1270
|
-
}
|
|
1271
|
-
});
|
|
1272
|
-
|
|
1273
|
-
// Item 7/10 — Step 3 "Submit for preview" opens the "coming soon" dialog (the
|
|
1274
|
-
// compliance-reviewed preview pipeline isn't wired into this panel yet).
|
|
1275
|
-
var submitBtn = document.getElementById("submitPreview");
|
|
1276
|
-
var comingSoon = document.getElementById("comingSoon");
|
|
1277
|
-
if (submitBtn && comingSoon) {
|
|
1278
|
-
submitBtn.addEventListener("click", function () {
|
|
1279
|
-
if (typeof comingSoon.showModal === "function") comingSoon.showModal();
|
|
1280
|
-
else comingSoon.setAttribute("open", "");
|
|
1281
|
-
});
|
|
1282
|
-
var csClose = document.getElementById("comingSoonClose");
|
|
1283
|
-
if (csClose) csClose.addEventListener("click", function () {
|
|
1284
|
-
if (typeof comingSoon.close === "function") comingSoon.close();
|
|
1285
|
-
comingSoon.removeAttribute("open");
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
renderSteps();
|
|
1290
|
-
if (!document.hidden) poll(); // if the tab starts hidden, visibilitychange kicks it off later
|
|
1291
|
-
})();
|
|
1292
|
-
`;
|
|
212
|
+
// Dev-only client widgets (Monitor, Stepper, Ask, Files, click-to-source) and
|
|
213
|
+
// the hosted activity rail live in typed apps/storefront/src/lib/dev/cockpit/*
|
|
214
|
+
// modules, imported from processed <script> tags below (ADR 0013) instead of
|
|
215
|
+
// being hand-mirrored here as raw strings. apiBase/isLocalDevLoop/appDomain/
|
|
216
|
+
// basePath cross into those modules via data-cockpit-* attrs on <body>.
|
|
1293
217
|
---
|
|
1294
218
|
|
|
1295
219
|
<html lang="en">
|
|
@@ -2040,7 +964,12 @@ const hostedActivityScript = `
|
|
|
2040
964
|
[hidden] { display: none !important; }
|
|
2041
965
|
</style>
|
|
2042
966
|
</head>
|
|
2043
|
-
<body
|
|
967
|
+
<body
|
|
968
|
+
data-cockpit-api-base={apiBase}
|
|
969
|
+
data-cockpit-is-dev={isLocalDevLoop ? "true" : "false"}
|
|
970
|
+
data-cockpit-app-domain={appDomain}
|
|
971
|
+
data-cockpit-base-path={basePath}
|
|
972
|
+
>
|
|
2044
973
|
<div class="wrap">
|
|
2045
974
|
<header class="topbar">
|
|
2046
975
|
<div class="brand">
|
|
@@ -2559,291 +1488,61 @@ const hostedActivityScript = `
|
|
|
2559
1488
|
</div>
|
|
2560
1489
|
)}
|
|
2561
1490
|
|
|
2562
|
-
<script
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
// head seed script above reads this same key before first paint). Clearing storage
|
|
2566
|
-
// falls back to prefers-color-scheme again, same as a first-ever visit.
|
|
2567
|
-
(function () {
|
|
2568
|
-
var root = document.documentElement;
|
|
2569
|
-
var btn = document.getElementById("themeBtn");
|
|
2570
|
-
if (!btn) return;
|
|
2571
|
-
var KEY = "tot.theme";
|
|
2572
|
-
function store(v) { try { localStorage.setItem(KEY, v); } catch (e) {} }
|
|
2573
|
-
btn.addEventListener("click", function () {
|
|
2574
|
-
var cur = root.getAttribute("data-theme");
|
|
2575
|
-
var next = cur === "dark" ? "light" : cur === "light" ? "dark"
|
|
2576
|
-
: (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "dark");
|
|
2577
|
-
root.setAttribute("data-theme", next);
|
|
2578
|
-
store(next);
|
|
2579
|
-
});
|
|
2580
|
-
})();
|
|
1491
|
+
<script>
|
|
1492
|
+
import { initThemeToggle } from "@/lib/dev/cockpit/themeToggle";
|
|
1493
|
+
initThemeToggle();
|
|
2581
1494
|
</script>
|
|
2582
1495
|
|
|
2583
|
-
<script
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
(function () {
|
|
2587
|
-
var copyIcon = document.getElementById("mcpCopyIcon");
|
|
2588
|
-
var cmdText = document.getElementById("mcpCmdText");
|
|
2589
|
-
var toast = document.getElementById("toast");
|
|
2590
|
-
var toastMsg = document.getElementById("toastMsg");
|
|
2591
|
-
if (!copyIcon || !cmdText) return;
|
|
2592
|
-
var tT;
|
|
2593
|
-
function ping(m) {
|
|
2594
|
-
if (!toast || !toastMsg) return;
|
|
2595
|
-
toastMsg.textContent = m; toast.classList.add("show");
|
|
2596
|
-
clearTimeout(tT); tT = setTimeout(function () { toast.classList.remove("show"); }, 1900);
|
|
2597
|
-
}
|
|
2598
|
-
copyIcon.addEventListener("click", function () {
|
|
2599
|
-
var text = cmdText.textContent;
|
|
2600
|
-
var done = navigator.clipboard
|
|
2601
|
-
? navigator.clipboard.writeText(text).then(function () { return true; }).catch(function () { return false; })
|
|
2602
|
-
: Promise.resolve(false);
|
|
2603
|
-
done.then(function (ok) {
|
|
2604
|
-
if (ok) {
|
|
2605
|
-
copyIcon.setAttribute("data-copied", "1");
|
|
2606
|
-
setTimeout(function () { copyIcon.removeAttribute("data-copied"); }, 1600);
|
|
2607
|
-
ping("Copied");
|
|
2608
|
-
} else {
|
|
2609
|
-
ping("Select the command above to copy");
|
|
2610
|
-
}
|
|
2611
|
-
});
|
|
2612
|
-
});
|
|
2613
|
-
})();
|
|
1496
|
+
<script>
|
|
1497
|
+
import { initMcpCopy } from "@/lib/dev/cockpit/mcpCopy";
|
|
1498
|
+
initMcpCopy();
|
|
2614
1499
|
</script>
|
|
2615
1500
|
|
|
2616
|
-
<script
|
|
2617
|
-
|
|
2618
|
-
(
|
|
2619
|
-
var info1 = document.getElementById("info1");
|
|
2620
|
-
if (!info1) return;
|
|
2621
|
-
var openBtn = info1.querySelector(".info-btn");
|
|
2622
|
-
openBtn.addEventListener("click", function (e) { e.stopPropagation(); info1.classList.toggle("open"); });
|
|
2623
|
-
document.addEventListener("click", function (e) { if (!info1.contains(e.target)) info1.classList.remove("open"); });
|
|
2624
|
-
})();
|
|
1501
|
+
<script>
|
|
1502
|
+
import { initInfoPopover } from "@/lib/dev/cockpit/infoPopover";
|
|
1503
|
+
initInfoPopover();
|
|
2625
1504
|
</script>
|
|
2626
1505
|
|
|
2627
|
-
<script
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
// /api/dev/cli-signin-code, reveal it (textContent, never innerHTML — it carries a
|
|
2631
|
-
// live code), and copy the whole thing.
|
|
2632
|
-
// · otherwise: copy the generic install-and-run command directly.
|
|
2633
|
-
(function () {
|
|
2634
|
-
var btn = document.getElementById("copyCmd");
|
|
2635
|
-
var textEl = document.getElementById("cli-cmd-text");
|
|
2636
|
-
var note = document.getElementById("cli-cmd-note");
|
|
2637
|
-
var toast = document.getElementById("toast");
|
|
2638
|
-
var toastMsg = document.getElementById("toastMsg");
|
|
2639
|
-
var copyIcon = document.getElementById("copyIcon");
|
|
2640
|
-
if (!btn || !textEl) return;
|
|
2641
|
-
var generic = btn.getAttribute("data-generic") || "";
|
|
2642
|
-
var fastpath = btn.getAttribute("data-fastpath") === "1";
|
|
2643
|
-
// The request's tenant prefix ("" host-routed, "/<tenant>" path-routed). Carried
|
|
2644
|
-
// as a data-attr because `basePath` is otherwise defined only inside the SEPARATE
|
|
2645
|
-
// hostedActivity IIFE — referencing it here (the cli-signin-code / -status / -approve
|
|
2646
|
-
// fetches below) was a ReferenceError that the mint's catch surfaced as the opaque
|
|
2647
|
-
// "Couldn't reach the sign-in service" with no request ever sent.
|
|
2648
|
-
var basePath = btn.getAttribute("data-base-path") || "";
|
|
2649
|
-
var tT;
|
|
2650
|
-
function ping(m) {
|
|
2651
|
-
if (!toast) return;
|
|
2652
|
-
toastMsg.textContent = m; toast.classList.add("show");
|
|
2653
|
-
clearTimeout(tT); tT = setTimeout(function () { toast.classList.remove("show"); }, 1900);
|
|
2654
|
-
}
|
|
2655
|
-
function copy(text) {
|
|
2656
|
-
if (navigator.clipboard) return navigator.clipboard.writeText(text).then(function () { return true; }).catch(function () { return false; });
|
|
2657
|
-
return Promise.resolve(false);
|
|
2658
|
-
}
|
|
2659
|
-
function label(s) { btn.childNodes[btn.childNodes.length - 1].textContent = s; }
|
|
2660
|
-
|
|
2661
|
-
// Standard copy-icon affordance on the terminal box. Once the command is
|
|
2662
|
-
// revealed, this is the obvious, always-there way to (re)copy it — the
|
|
2663
|
-
// icon→check swap is its own inline confirmation, so copying no longer
|
|
2664
|
-
// depends on the transient toast being noticed.
|
|
2665
|
-
var iconT;
|
|
2666
|
-
function flashCopied() {
|
|
2667
|
-
if (!copyIcon) return;
|
|
2668
|
-
copyIcon.setAttribute("data-copied", "1");
|
|
2669
|
-
copyIcon.setAttribute("aria-label", "Copied");
|
|
2670
|
-
copyIcon.setAttribute("title", "Copied");
|
|
2671
|
-
clearTimeout(iconT);
|
|
2672
|
-
iconT = setTimeout(function () {
|
|
2673
|
-
copyIcon.removeAttribute("data-copied");
|
|
2674
|
-
copyIcon.setAttribute("aria-label", "Copy the command");
|
|
2675
|
-
copyIcon.setAttribute("title", "Copy the command");
|
|
2676
|
-
}, 1600);
|
|
2677
|
-
}
|
|
2678
|
-
function revealIcon() {
|
|
2679
|
-
if (copyIcon) copyIcon.hidden = false;
|
|
2680
|
-
}
|
|
2681
|
-
if (copyIcon) {
|
|
2682
|
-
copyIcon.addEventListener("click", function () {
|
|
2683
|
-
copy(textEl.textContent).then(function (ok) {
|
|
2684
|
-
if (ok) { flashCopied(); ping("Copied — paste & press Enter"); }
|
|
2685
|
-
else ping("Select the command above to copy");
|
|
2686
|
-
});
|
|
2687
|
-
});
|
|
2688
|
-
}
|
|
2689
|
-
|
|
2690
|
-
// ── Rendezvous approval: after a command is generated, the terminal ATTACHES
|
|
2691
|
-
// its own PKCE key and prints a fingerprint; the developer confirms it HERE. This
|
|
2692
|
-
// is the load-bearing anti-interception step — approving the wrong fingerprint is
|
|
2693
|
-
// how a hijack is caught, so the confirm is an explicit, human decision.
|
|
2694
|
-
var rzvRoot = document.getElementById("rzvApprove");
|
|
2695
|
-
var rzvWait = document.getElementById("rzvWait");
|
|
2696
|
-
var rzvConfirm = document.getElementById("rzvConfirm");
|
|
2697
|
-
var rzvFp = document.getElementById("rzvFp");
|
|
2698
|
-
var rzvApproveBtn = document.getElementById("rzvApproveBtn");
|
|
2699
|
-
var rzvDenyBtn = document.getElementById("rzvDenyBtn");
|
|
2700
|
-
var rzvDone = document.getElementById("rzvDone");
|
|
2701
|
-
var rzvTimer = null, rzvCode = null, rzvDecided = false;
|
|
2702
|
-
function rzvShow(el) { if (el) el.hidden = false; }
|
|
2703
|
-
function rzvHide(el) { if (el) el.hidden = true; }
|
|
2704
|
-
// Once the terminal has ATTACHED, the command has plainly been pasted and
|
|
2705
|
-
// run — hide the copy affordances so the approve prompt is the page's
|
|
2706
|
-
// single focus (DZ 2026-08-18). An EXPIRED sign-in restores the generate
|
|
2707
|
-
// button (a fresh mint is needed); the stale command's copy icon stays gone.
|
|
2708
|
-
function rzvHideCopyCtas() {
|
|
2709
|
-
if (btn) btn.hidden = true;
|
|
2710
|
-
if (copyIcon) copyIcon.hidden = true;
|
|
2711
|
-
}
|
|
2712
|
-
function rzvRestoreGenerate() {
|
|
2713
|
-
if (btn) btn.hidden = false;
|
|
2714
|
-
}
|
|
2715
|
-
function rzvStopPoll() { if (rzvTimer) { clearTimeout(rzvTimer); rzvTimer = null; } }
|
|
2716
|
-
function rzvFinish(msg) {
|
|
2717
|
-
rzvStopPoll(); rzvDecided = true;
|
|
2718
|
-
rzvHide(rzvWait); rzvHide(rzvConfirm);
|
|
2719
|
-
if (rzvDone) { rzvDone.textContent = msg; rzvDone.hidden = false; }
|
|
2720
|
-
}
|
|
2721
|
-
async function rzvPoll() {
|
|
2722
|
-
if (rzvDecided) return;
|
|
2723
|
-
var r, b;
|
|
2724
|
-
try {
|
|
2725
|
-
r = await fetch(basePath + "/api/dev/cli-signin-status", {
|
|
2726
|
-
method: "POST", headers: { "content-type": "application/json" },
|
|
2727
|
-
body: JSON.stringify({ rendezvousCode: rzvCode }),
|
|
2728
|
-
});
|
|
2729
|
-
b = await r.json().catch(function () { return {}; });
|
|
2730
|
-
} catch (e) { rzvTimer = setTimeout(rzvPoll, 2800); return; }
|
|
2731
|
-
if (!r || !r.ok) { rzvTimer = setTimeout(rzvPoll, 3200); return; }
|
|
2732
|
-
if (b.state === "attached" && b.fingerprint) {
|
|
2733
|
-
rzvHideCopyCtas();
|
|
2734
|
-
rzvHide(rzvWait); rzvShow(rzvConfirm);
|
|
2735
|
-
if (rzvFp) { rzvFp.textContent = b.fingerprint; rzvFp.setAttribute("data-fp", b.fingerprint); }
|
|
2736
|
-
return; // stop polling — wait for the human decision
|
|
2737
|
-
}
|
|
2738
|
-
if (b.state === "approved") { rzvHideCopyCtas(); rzvFinish("Signed in ✓ — your terminal is good to go."); return; }
|
|
2739
|
-
if (b.state === "denied") { rzvHideCopyCtas(); rzvFinish("Rejected — that terminal wasn’t approved. You’re safe; generate a fresh command to try again."); return; }
|
|
2740
|
-
if (b.state === "expired") { rzvRestoreGenerate(); rzvFinish("This sign-in expired. Generate a fresh setup command above."); return; }
|
|
2741
|
-
rzvTimer = setTimeout(rzvPoll, 2500); // pending → keep watching
|
|
2742
|
-
}
|
|
2743
|
-
function rzvStart(code) {
|
|
2744
|
-
rzvCode = code; rzvDecided = false;
|
|
2745
|
-
if (rzvApproveBtn) rzvApproveBtn.disabled = false;
|
|
2746
|
-
if (rzvDenyBtn) rzvDenyBtn.disabled = false;
|
|
2747
|
-
rzvShow(rzvRoot); rzvShow(rzvWait); rzvHide(rzvConfirm); rzvHide(rzvDone);
|
|
2748
|
-
rzvStopPoll(); rzvPoll();
|
|
2749
|
-
}
|
|
2750
|
-
async function rzvDecide(decision) {
|
|
2751
|
-
if (rzvDecided) return;
|
|
2752
|
-
var fp = rzvFp ? (rzvFp.getAttribute("data-fp") || "") : "";
|
|
2753
|
-
if (rzvApproveBtn) rzvApproveBtn.disabled = true;
|
|
2754
|
-
if (rzvDenyBtn) rzvDenyBtn.disabled = true;
|
|
2755
|
-
var r;
|
|
2756
|
-
try {
|
|
2757
|
-
r = await fetch(basePath + "/api/dev/cli-signin-approve", {
|
|
2758
|
-
method: "POST", headers: { "content-type": "application/json" },
|
|
2759
|
-
body: JSON.stringify({ rendezvousCode: rzvCode, fingerprint: fp, decision: decision }),
|
|
2760
|
-
});
|
|
2761
|
-
} catch (e) {
|
|
2762
|
-
if (rzvApproveBtn) rzvApproveBtn.disabled = false;
|
|
2763
|
-
if (rzvDenyBtn) rzvDenyBtn.disabled = false;
|
|
2764
|
-
return;
|
|
2765
|
-
}
|
|
2766
|
-
if (decision === "deny") { rzvFinish("Rejected — that terminal wasn’t approved."); return; }
|
|
2767
|
-
if (r.ok) { rzvFinish("Signed in ✓ — your terminal is good to go."); return; }
|
|
2768
|
-
if (rzvApproveBtn) rzvApproveBtn.disabled = false;
|
|
2769
|
-
if (rzvDenyBtn) rzvDenyBtn.disabled = false;
|
|
2770
|
-
var m = r.status === 403 ? "That didn’t match your identity — generate a fresh command and try again."
|
|
2771
|
-
: r.status === 409 ? "That code didn’t match a waiting terminal — re-check it, or generate a fresh command."
|
|
2772
|
-
: "Couldn’t complete approval — try again.";
|
|
2773
|
-
if (rzvDone) { rzvDone.textContent = m; rzvDone.hidden = false; }
|
|
2774
|
-
}
|
|
2775
|
-
if (rzvApproveBtn) rzvApproveBtn.addEventListener("click", function () { rzvDecide("approve"); });
|
|
2776
|
-
if (rzvDenyBtn) rzvDenyBtn.addEventListener("click", function () { rzvDecide("deny"); });
|
|
2777
|
-
|
|
2778
|
-
if (!fastpath) {
|
|
2779
|
-
btn.addEventListener("click", function () {
|
|
2780
|
-
copy(generic).then(function (ok) {
|
|
2781
|
-
label(ok ? " Copied — paste & press Enter" : " Copy the command");
|
|
2782
|
-
ping(ok ? "Copied the command" : "Select the command to copy");
|
|
2783
|
-
setTimeout(function () { label(" Copy the command"); }, 2600);
|
|
2784
|
-
});
|
|
2785
|
-
});
|
|
2786
|
-
return;
|
|
2787
|
-
}
|
|
2788
|
-
|
|
2789
|
-
var busy = false;
|
|
2790
|
-
btn.addEventListener("click", async function () {
|
|
2791
|
-
if (busy) return;
|
|
2792
|
-
busy = true;
|
|
2793
|
-
label(" …"); btn.disabled = true;
|
|
2794
|
-
var res, body;
|
|
2795
|
-
try {
|
|
2796
|
-
res = await fetch(basePath + "/api/dev/cli-signin-code", {
|
|
2797
|
-
method: "POST",
|
|
2798
|
-
headers: { "content-type": "application/json" },
|
|
2799
|
-
body: "{}",
|
|
2800
|
-
});
|
|
2801
|
-
body = await res.json().catch(function () { return {}; });
|
|
2802
|
-
} catch (err) {
|
|
2803
|
-
if (note) note.textContent = "Couldn’t reach the sign-in service — try again in a moment.";
|
|
2804
|
-
label(" Copy the command"); btn.disabled = false; busy = false;
|
|
2805
|
-
return;
|
|
2806
|
-
}
|
|
2807
|
-
if (!res.ok || !body.command) {
|
|
2808
|
-
var byStatus = {
|
|
2809
|
-
401: "Your session expired — reload the page and sign in again.",
|
|
2810
|
-
403: "This account doesn’t have developer access to a store yet.",
|
|
2811
|
-
409: "Your store isn’t set up yet — reload this page for its status.",
|
|
2812
|
-
502: "The sign-in service is having a moment — try again.",
|
|
2813
|
-
503: "Terminal sign-in isn’t configured on this environment.",
|
|
2814
|
-
};
|
|
2815
|
-
if (note) note.textContent = byStatus[res.status] || "Couldn’t mint a sign-in command — try again.";
|
|
2816
|
-
label(" Copy the command"); btn.disabled = false; busy = false;
|
|
2817
|
-
return;
|
|
2818
|
-
}
|
|
2819
|
-
// Reveal the real command (carries the non-secret rendezvous handle) as a pure
|
|
2820
|
-
// text node, then copy it.
|
|
2821
|
-
textEl.textContent = body.command;
|
|
2822
|
-
textEl.classList.remove("is-placeholder");
|
|
2823
|
-
revealIcon();
|
|
2824
|
-
// Start watching for the terminal to attach so we can prompt for approval.
|
|
2825
|
-
if (body.rendezvousCode) rzvStart(body.rendezvousCode);
|
|
2826
|
-
var ok = await copy(body.command);
|
|
2827
|
-
if (ok) flashCopied();
|
|
2828
|
-
label(ok ? " Copied ✓" : " Select & copy");
|
|
2829
|
-
ping(ok ? "Copied — paste & press Enter" : "Select the command above to copy");
|
|
2830
|
-
if (note) {
|
|
2831
|
-
note.textContent = ok
|
|
2832
|
-
? "Copied. Paste into your terminal and press Enter — it signs you in, runs your store, and opens your browser. Single-use, short-lived code."
|
|
2833
|
-
: "Select the command above and copy it, then paste into your terminal and press Enter. Single-use, short-lived code.";
|
|
2834
|
-
}
|
|
2835
|
-
btn.disabled = false;
|
|
2836
|
-
setTimeout(function () { label(" Copy the command"); }, 4000);
|
|
2837
|
-
busy = false;
|
|
2838
|
-
});
|
|
2839
|
-
})();
|
|
1506
|
+
<script>
|
|
1507
|
+
import { initCopyCommand } from "@/lib/dev/cockpit/copyCommand";
|
|
1508
|
+
initCopyCommand();
|
|
2840
1509
|
</script>
|
|
2841
1510
|
|
|
2842
|
-
{hostedActivity &&
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
1511
|
+
{hostedActivity && (
|
|
1512
|
+
<script>
|
|
1513
|
+
import { initHostedActivity } from "@/lib/dev/cockpit/hostedActivity";
|
|
1514
|
+
initHostedActivity();
|
|
1515
|
+
</script>
|
|
1516
|
+
)}
|
|
1517
|
+
{isLocalDevLoop && (
|
|
1518
|
+
<script>
|
|
1519
|
+
import { initStepper } from "@/lib/dev/cockpit/stepper";
|
|
1520
|
+
initStepper();
|
|
1521
|
+
</script>
|
|
1522
|
+
)}
|
|
1523
|
+
{isLocalDevLoop && (
|
|
1524
|
+
<script>
|
|
1525
|
+
import { initAsk } from "@/lib/dev/cockpit/ask";
|
|
1526
|
+
initAsk();
|
|
1527
|
+
</script>
|
|
1528
|
+
)}
|
|
1529
|
+
{isLocalDevLoop && (
|
|
1530
|
+
<script>
|
|
1531
|
+
import { initMonitor } from "@/lib/dev/cockpit/monitor";
|
|
1532
|
+
initMonitor();
|
|
1533
|
+
</script>
|
|
1534
|
+
)}
|
|
1535
|
+
{isLocalDevLoop && (
|
|
1536
|
+
<script>
|
|
1537
|
+
import { initFiles } from "@/lib/dev/cockpit/files";
|
|
1538
|
+
initFiles();
|
|
1539
|
+
</script>
|
|
1540
|
+
)}
|
|
1541
|
+
{isLocalDevLoop && (
|
|
1542
|
+
<script>
|
|
1543
|
+
import { initClickToSource } from "@/lib/dev/cockpit/clickToSource";
|
|
1544
|
+
initClickToSource();
|
|
1545
|
+
</script>
|
|
1546
|
+
)}
|
|
2848
1547
|
</body>
|
|
2849
1548
|
</html>
|