diff --git a/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_AUTH_BILLING_AND_DISTRIBUTION_PACKET_2026-06-22.md b/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_AUTH_BILLING_AND_DISTRIBUTION_PACKET_2026-06-22.md index 9c2e986..b853457 100644 --- a/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_AUTH_BILLING_AND_DISTRIBUTION_PACKET_2026-06-22.md +++ b/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_AUTH_BILLING_AND_DISTRIBUTION_PACKET_2026-06-22.md @@ -127,12 +127,14 @@ so external-facing copy no longer depends on operators opening the protected dashboard to see whether checkout, download, or notice configuration is still missing. -The homepage now also carries a first-party public-site-status section using -that same bounded launch-status lane, and the public launch-status component -now consumes live auth-health webhook/runtime truth in addition to the shared -release-manifest download readiness so public marketing copy does not imply -launch readiness when checkout/download config exists but the live billing -webhook or public-origin runtime posture is still incomplete. +The shared marketing shell now also carries a compact first-party +public-site-status banner across public pages, the homepage keeps a fuller +public-site-status section using that same bounded launch-status lane, and the +public launch-status component now consumes live auth-health webhook/runtime +truth in addition to the shared release-manifest download readiness so public +marketing copy does not imply launch readiness when checkout/download config +exists but the live billing webhook or public-origin runtime posture is still +incomplete. The login and register pages now also surface those shared-auth runtime warnings instead of silently behaving like production auth when the lane is still in diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index 230653d..73ebffb 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -263,7 +263,7 @@ repo. | Feature | Status | Primary authority | Notes | |---|---|---|---| -| Public `hypertwist.app` marketing shell | Implemented now | first-party `website/` app + feature registry/roadmap authority | HyperTwist now has a dedicated first-party public web surface for homepage, about, resources, pricing, download, support, and legal routes. This lane is separate from the embedded Unreal browser runtime under `Content/Browser/` and does not claim browser-simulator parity. The same package now also carries a first-party external runtime-readiness verifier so deploy-time env and live health posture can be checked outside the dashboard, plus separated local-versus-production env templates whose placeholder values are intentionally rejected until real launch config is in place, bootstrap CI now validates both the frontend and auth-server website commands directly, and the auth server can now auto-serve the built `website/dist` bundle with bounded SPA fallback for same-origin public deployment. Request-level server coverage now also proves that public/app shell delivery does not shadow `/api/*`, `/auth*`, `/health`, or missing asset paths, while the pricing/download/notices routes now surface first-party preview-versus-launch posture from the same bounded launch checklist instead of relying on hidden operator-only status. The homepage now also exposes that same public-site-status lane directly, and the shared public launch-status component now consumes live auth-health webhook/runtime truth in addition to release-manifest download readiness so public marketing copy does not claim launch posture from static checkout/download config alone. The live website lane now also owns route-aware title/description/canonical/Open-Graph/Twitter metadata for the real `hypertwist.app` marketing surface so deployed public pages no longer remain on a single generic SPA title/description, plus first-party `robots.txt` and `sitemap.xml` assets for the public route set while keeping `/app`, `/login`, and `/register` out of crawler posture. The real `check-runtime-readiness` CLI is now also exercised against the checked-in production example env files, and a spawned `website/server` bootstrap proof now verifies the live same-origin process path from production-shaped env into `/health`, `/api/auth/health`, built-shell serving, and the public anonymous release-manifest posture for the shared desktop release lane. The same verifier now also probes the deployed root-shell marker and can explicitly fail when the public origin is still serving the older placeholder rollout page instead of the first-party website/auth-server lane, while the repo now also carries first-party `website/deploy/` `nginx` plus `systemd` handoff templates, a concrete same-origin public-host cutover guide, a deployment-file renderer that emits resolved operator outputs from real checkout paths, and a manifest-driven bundle renderer that lets one authoritative input own the public origin while emitting validated env plus install artifacts together, with the shared-VPS-safe default upstream moved to `3011` after live host inspection confirmed `3001` is already occupied by FamiliarOS. The same deployment lane now also distinguishes `launch` from `preview` posture so honest missing checkout/download/webhook/release values are accepted only for non-public rehearsal while placeholder strings still fail, `runtime.mode: mixed` plus `public_origin_ready: true` counts as valid preview-host proof, and the staging helper can archive either committed `HEAD` or the live worktree through `--archive-source worktree`. An isolated VPS-local staging proof then confirmed that both the committed HyperTwist website lane and the later preview-tier worktree packet can serve green health, release-manifest, and first-party shell responses on that real shared host behind `3011`, and a later root-owned cutover then replaced the public placeholder site with the live first-party same-origin preview deployment on `https://hypertwist.app`. The package now also ships a bounded root-owned live-deploy helper that stages the committed checkout, uploads the rendered bundle, installs env, rebuilds the site, replaces the live `systemd` plus `nginx` files, and validates the public origin; that helper has already been re-proved idempotently against the live host. The repo now also ships that host-proof flow as a first-party staging helper so future sessions can rerun the temp checkout/build/boot verification path directly before or after root-owned cutover. | +| Public `hypertwist.app` marketing shell | Implemented now | first-party `website/` app + feature registry/roadmap authority | HyperTwist now has a dedicated first-party public web surface for homepage, about, resources, pricing, download, support, and legal routes. This lane is separate from the embedded Unreal browser runtime under `Content/Browser/` and does not claim browser-simulator parity. The same package now also carries a first-party external runtime-readiness verifier so deploy-time env and live health posture can be checked outside the dashboard, plus separated local-versus-production env templates whose placeholder values are intentionally rejected until real launch config is in place, bootstrap CI now validates both the frontend and auth-server website commands directly, and the auth server can now auto-serve the built `website/dist` bundle with bounded SPA fallback for same-origin public deployment. Request-level server coverage now also proves that public/app shell delivery does not shadow `/api/*`, `/auth*`, `/health`, or missing asset paths, while the pricing/download/notices routes now surface first-party preview-versus-launch posture from the same bounded launch checklist instead of relying on hidden operator-only status. The shared marketing shell now also carries a compact public-site-status banner across public pages, the homepage keeps a fuller status section, and the shared public launch-status component now consumes live auth-health webhook/runtime truth in addition to release-manifest download readiness so public marketing copy does not claim launch posture from static checkout/download config alone. The live website lane now also owns route-aware title/description/canonical/Open-Graph/Twitter metadata for the real `hypertwist.app` marketing surface so deployed public pages no longer remain on a single generic SPA title/description, plus first-party `robots.txt` and `sitemap.xml` assets for the public route set while keeping `/app`, `/login`, and `/register` out of crawler posture. The real `check-runtime-readiness` CLI is now also exercised against the checked-in production example env files, and a spawned `website/server` bootstrap proof now verifies the live same-origin process path from production-shaped env into `/health`, `/api/auth/health`, built-shell serving, and the public anonymous release-manifest posture for the shared desktop release lane. The same verifier now also probes the deployed root-shell marker and can explicitly fail when the public origin is still serving the older placeholder rollout page instead of the first-party website/auth-server lane, while the repo now also carries first-party `website/deploy/` `nginx` plus `systemd` handoff templates, a concrete same-origin public-host cutover guide, a deployment-file renderer that emits resolved operator outputs from real checkout paths, and a manifest-driven bundle renderer that lets one authoritative input own the public origin while emitting validated env plus install artifacts together, with the shared-VPS-safe default upstream moved to `3011` after live host inspection confirmed `3001` is already occupied by FamiliarOS. The same deployment lane now also distinguishes `launch` from `preview` posture so honest missing checkout/download/webhook/release values are accepted only for non-public rehearsal while placeholder strings still fail, `runtime.mode: mixed` plus `public_origin_ready: true` counts as valid preview-host proof, and the staging helper can archive either committed `HEAD` or the live worktree through `--archive-source worktree`. An isolated VPS-local staging proof then confirmed that both the committed HyperTwist website lane and the later preview-tier worktree packet can serve green health, release-manifest, and first-party shell responses on that real shared host behind `3011`, and a later root-owned cutover then replaced the public placeholder site with the live first-party same-origin preview deployment on `https://hypertwist.app`. The package now also ships a bounded root-owned live-deploy helper that stages the committed checkout, uploads the rendered bundle, installs env, rebuilds the site, replaces the live `systemd` plus `nginx` files, and validates the public origin; that helper has already been re-proved idempotently against the live host. The repo now also ships that host-proof flow as a first-party staging helper so future sessions can rerun the temp checkout/build/boot verification path directly before or after root-owned cutover. | | Browser-based operator/account dashboard | Implemented now | first-party `website/` app + shared auth/dashboard packet | A protected browser dashboard is now live for operator access, account state, download posture, browser-access boundary explanation, notices review, and bounded billing/entitlement status. It reuses the shared SuperTokens auth posture proven in FamiliarOS and ScriptoriumAI while remaining HyperTwist-specific in product content and boundary claims, the current auth-health surface now truthfully distinguishes configured versus reachable or ready shared-core posture while exposing fallback-active reason instead of hardcoding readiness, and the same dashboard now also surfaces launch-readiness truth for download URLs, checkout links, source/notices URLs, billing-secret/map configuration, and local-versus-public runtime deployment posture. The same protected overview now also consumes the server-backed Windows packaged-validation summary that the release-manifest authority exposes, so operators can see current higher-dimensional desktop proof without drilling into the dedicated downloads screen. Focused frontend coverage now also protects deep-link login redirect preservation, safe `next`-path normalization across auth entry points, fallback/email auth-bootstrap normalization, login/register continuation behavior, public download-gating behavior, protected-route/shell behavior, real lazy-route tree behavior for key public and protected paths, top-level app-bootstrap and SuperTokens-wrapper posture, login/register unhappy-path and OAuth-button behavior, support-topic fallback routing when live checkout is not configured, desktop-link verify-url/dashboard readiness behavior, and explicit `noindex,nofollow` posture on protected/auth browser surfaces. The validation lane now also has a bounded signed test-session harness under `TEST_MODE=testing` that proves `/api/auth/me` and `/api/auth/desktop-link` behavior through the live spawned auth-server process without widening production auth posture. | | Desktop download posture and browser-to-desktop pairing | Implemented now | first-party `website/` app + `website/server` desktop-link endpoints | Public download targets, dashboard-side release posture, and short-lived desktop-link token generation/verification are now first-party owned. The current server posture now enforces exact website-origin matching, bounded per-user issuance, one-time token consumption, and billing-backed plan/download entitlement resolution with focused `website/server` tests green on `2026-06-22`, and the verify handshake now returns the same resolved download-entitlement posture the dashboard sees instead of only identity plus plan/role. The same lane now also owns a shared `GET /api/releases/manifest` runtime authority for release version/channel/build/published/file-size/checksum/docs/source metadata, with anonymous callers intentionally denied raw download URLs while entitled session-backed callers receive the configured direct platform URL. That manifest now also carries first-party packaged-validation summary truth for the Windows higher-dimensional desktop lane, so the public `/download` page, the public `/resources` reference page, and the protected `/app/downloads` surface can project real package evidence for the dedicated-family `Magic120Cell` / `MagicCube5D` maps even while launch-tier release URLs remain unconfigured. The public `/download` page now keeps raw download URLs behind the protected dashboard instead of exposing them directly, preserves requested platform continuity through `/app/downloads?platform=...`, and surfaces that requested target again after auth handoff inside the protected release lane. Both the public and protected download surfaces now also carry first-party rollout steps plus release/notices/source references so the desktop setup lane is more than a generic link bucket, and the dashboard plus public launch-status callouts now consume the same manifest-backed Windows download truth instead of only static frontend config. Actual release URLs remain deployment configuration rather than hardcoded product truth. | | Paddle-ready pricing and billing webhook seam | Implemented now | first-party `website/` app + `website/server` billing endpoint | The public pricing surface now exists with plan structure, checkout-link configuration seams, and the same `/api/billing/paddle/webhook` endpoint family used by the broader product website lane. The current server now verifies `Paddle-Signature` against `PADDLE_WEBHOOK_SECRET` using the documented raw-body HMAC flow, persists a bounded first-party billing state file, and applies verified Paddle events into account/download entitlement state that the browser dashboard consumes, with focused `website/server` tests green on `2026-06-22`. A spawned live-process proof now also verifies that a real signed webhook updates processed-event health and persisted billing state through the actual auth-server runtime, not only helper-level store tests, and transaction events no longer leak their id into stored `subscriptionId` state. Production checkout URLs, secret management, and broader operator/admin billing workflows remain deployment/application tasks, not shipped-code omissions. | diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index 4e90680..f9748ed 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -296,7 +296,8 @@ Current consolidated milestone snapshot: `Magic120Cell` and `MagicCube5D` even before the launch-tier release URLs are populated, and a follow-on same-family marketing-shell continuation now also exposes a - first-party public-site-status section on the homepage while tightening the + compact first-party public-site-status banner across the shared marketing + shell, keeps a fuller status section on the homepage, and tightens the shared public launch-status component to consume live auth-health webhook and public-origin runtime posture instead of treating static checkout/download configuration alone as launch-ready, diff --git a/website/README.md b/website/README.md index f00b5f3..4145bcb 100644 --- a/website/README.md +++ b/website/README.md @@ -155,9 +155,9 @@ Use the runtime-readiness command before public launch or deployment approval: - same-origin preview proof may remain in runtime mode `mixed` when public origin readiness is true and the only remaining warning is the loopback SuperTokens-core notice -- the homepage now also carries a first-party public-site-status section so the - preview-versus-launch boundary is visible on the primary public route instead - of only on pricing, download, or notices surfaces +- the shared marketing shell now also carries a compact first-party + public-site-status banner across public pages, while the homepage keeps a + fuller public-site-status section for the same preview-versus-launch truth - the auth server can now also serve the built `website/dist` bundle directly for same-origin `hypertwist.app` deployment when that build output is present - the repo now also includes first-party same-origin `nginx` and `systemd` handoff templates under `website/deploy/` diff --git a/website/src/__tests__/route-shells.test.tsx b/website/src/__tests__/route-shells.test.tsx index cf51da8..f7936e4 100644 --- a/website/src/__tests__/route-shells.test.tsx +++ b/website/src/__tests__/route-shells.test.tsx @@ -1,4 +1,5 @@ import { cleanup, render, screen } from '@testing-library/react' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import userEvent from '@testing-library/user-event' import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -7,11 +8,18 @@ import { ROUTER_FUTURE_FLAGS } from '../router/router-future' const mockUsePlatformAuth = vi.fn() const mockLogout = vi.fn() const mockToggleColorMode = vi.fn() +const mockGetAuthHealth = vi.fn() +const mockGetReleaseManifest = vi.fn() vi.mock('../auth/platform-auth', () => ({ usePlatformAuth: () => mockUsePlatformAuth(), })) +vi.mock('../auth/auth-api', () => ({ + getAuthHealth: (...args: unknown[]) => mockGetAuthHealth(...args), + getReleaseManifest: (...args: unknown[]) => mockGetReleaseManifest(...args), +})) + import { MarketingShell } from '../components/layout/MarketingShell' import { AppShell } from '../components/layout/AppShell' import { ProtectedRoute } from '../components/routes/ProtectedRoute' @@ -34,12 +42,46 @@ function renderProtectedRoute(initialEntry: string) { ) } +function renderMarketingShell(isAuthenticated = false) { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + mockUsePlatformAuth.mockReturnValue({ + isAuthenticated, + isLoading: false, + logout: () => mockLogout(), + toggleColorMode: () => mockToggleColorMode(), + colorMode: 'dark', + user: { + name: 'Operator', + plan: 'operator', + }, + }) + + return render( + + + +
Body
+
+
+
, + ) +} + describe('website route and shell behavior', () => { beforeEach(() => { cleanup() mockUsePlatformAuth.mockReset() mockLogout.mockReset() mockToggleColorMode.mockReset() + mockGetAuthHealth.mockReset() + mockGetReleaseManifest.mockReset() mockUsePlatformAuth.mockReturnValue({ isAuthenticated: false, isLoading: false, @@ -51,6 +93,61 @@ describe('website route and shell behavior', () => { plan: 'operator', }, }) + mockGetAuthHealth.mockResolvedValue({ + ok: true, + service: 'hypertwist-auth-server', + supertokens: { + configured: true, + reachable: true, + ready: true, + apiVersion: '5.4', + error: null, + oauth: { + github: false, + google: false, + }, + }, + fallback: { + enabled: true, + active: false, + reason: null, + }, + billing: { + statePath: '/var/lib/hypertwist/auth/hypertwist-billing-state.json', + processedEventCount: 0, + pricePlanMapConfigured: false, + productPlanMapConfigured: false, + webhookSecretConfigured: false, + }, + runtime: { + mode: 'mixed', + public_origin_ready: true, + cookie_secure: true, + api_domain: 'https://hypertwist.app', + website_domain: 'https://hypertwist.app', + warnings: [], + errors: [], + }, + }) + mockGetReleaseManifest.mockResolvedValue({ + ok: true, + manifest: { + generated_at: '2026-06-22T12:00:00.000Z', + support_email: 'hello@hypertwist.app', + public_docs_url: 'https://docs.hypertwist.app', + release_notes_url: 'https://notes.hypertwist.app', + corresponding_source_url: 'https://hypertwist.app/open-source/source.zip', + open_source_repo_url: 'https://github.com/hypertwist/hypertwist', + viewer: { + authenticated: false, + canDownload: false, + plan: null, + role: null, + accessStatus: null, + }, + platforms: [], + }, + }) }) it('shows the protected-route loader while auth state is still resolving', () => { @@ -82,16 +179,18 @@ describe('website route and shell behavior', () => { }) it('switches the marketing-shell auth action between login and dashboard', () => { - const { rerender } = render( - - -
Body
-
-
, - ) + const { rerender } = renderMarketingShell(false) expect(screen.getByRole('link', { name: 'Log in' }).getAttribute('href')).toBe('/login') + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + mockUsePlatformAuth.mockReturnValue({ isAuthenticated: true, isLoading: false, @@ -105,11 +204,13 @@ describe('website route and shell behavior', () => { }) rerender( - - -
Body
-
-
, + + + +
Body
+
+
+
, ) expect(screen.getByRole('link', { name: 'Open dashboard' }).getAttribute('href')).toBe('/app') diff --git a/website/src/components/layout/MarketingShell.tsx b/website/src/components/layout/MarketingShell.tsx index d0bb48d..b5d2214 100644 --- a/website/src/components/layout/MarketingShell.tsx +++ b/website/src/components/layout/MarketingShell.tsx @@ -1,5 +1,6 @@ import { Link, NavLink } from 'react-router-dom' import { usePlatformAuth } from '../../auth/platform-auth' +import { MarketingLaunchStatusBanner } from '../ui/PublicLaunchStatus' import { brandConfig } from '../../site-config' import { footerLinks } from '../../site-data' @@ -65,6 +66,7 @@ export function MarketingShell({

{title}

{lede}

+ {children} diff --git a/website/src/components/ui/PublicLaunchStatus.tsx b/website/src/components/ui/PublicLaunchStatus.tsx index 4ef8e1c..ad9ade1 100644 --- a/website/src/components/ui/PublicLaunchStatus.tsx +++ b/website/src/components/ui/PublicLaunchStatus.tsx @@ -11,11 +11,7 @@ import { import { launchReadiness } from '../../site-config' import { buildSupportPath } from '../../site-routes' -export function PublicLaunchStatus({ - title = 'Current public launch status', -}: { - title?: string -}) { +function usePublicLaunchStatus() { const authHealthQuery = useQuery({ queryKey: ['auth-health', 'public-launch-status'], queryFn: getAuthHealth, @@ -62,6 +58,52 @@ export function PublicLaunchStatus({ return labels }, [authHealthQuery.isError, authHealthQuery.isLoading, missingItems, runtimeReady, webhookReady]) + return { + authHealthQuery, + checklist, + missingLabels, + ready, + releaseManifestQuery, + runtimeReady, + webhookReady, + } +} + +export function MarketingLaunchStatusBanner() { + const { missingLabels, ready } = usePublicLaunchStatus() + + return ( +
+
+

+ {ready ? 'Launch-ready posture' : 'Preview posture'} +

+ Public site status +
+

+ {ready + ? 'Checkout, release, notices, and live auth-runtime posture are aligned for the current public lane.' + : `Live preview lane: remaining launch blockers are ${missingLabels.join(', ')}.`} +

+
+ + View launch posture + + + Get launch help + +
+
+ ) +} + +export function PublicLaunchStatus({ + title = 'Current public launch status', +}: { + title?: string +}) { + const { authHealthQuery, checklist, missingLabels, ready, releaseManifestQuery, runtimeReady, webhookReady } = usePublicLaunchStatus() + return (

diff --git a/website/src/styles/global.css b/website/src/styles/global.css index 092dbe0..b5f483f 100644 --- a/website/src/styles/global.css +++ b/website/src/styles/global.css @@ -409,6 +409,14 @@ img { margin-bottom: 0; } +.site-status-banner { + margin-bottom: 1.4rem; +} + +.site-status-banner .inline-link { + margin-top: 0; +} + .list { margin: 0; padding-left: 1.15rem;