Add canonical public launch status route

This commit is contained in:
axiomlogicnexus 2026-06-27 23:31:28 +00:00
parent 0505a49a42
commit d5f800de65
15 changed files with 339 additions and 83 deletions

View file

@ -243,6 +243,7 @@ The website now includes:
- public download center
- dedicated public `/getting-started` route for the shortest browser-to-desktop onboarding path
- dedicated public `/launch-status` route for the canonical preview-versus-launch rollout checklist
- plan-gated release posture
- browser-to-desktop token handoff
- protected-dashboard download gating above the public target-description page

View file

@ -73,6 +73,16 @@ route now acts as the canonical public onboarding/manual surface for:
- first launch and higher-dimensional verification order
- explicit XR/controller boundary truth
The next same-family launch-authority continuation then added a dedicated
public `/launch-status` route so the current preview-versus-launch posture no
longer has to be reconstructed from pricing plus compact public banners alone.
That route now acts as the canonical public authority surface for:
- the current launch-readiness checklist
- rollout hardening tracks
- packaged desktop proof in launch context
- release-reference and support follow-through
## Protected-surface continuation
The protected browser shell is no longer only one strong dashboard route plus
@ -109,6 +119,7 @@ drop in information quality.
- pricing page
- download page
- getting-started page
- launch-status page
- support page
- changelog page
- open-source notices page

View file

@ -1049,6 +1049,10 @@ Current audit note:
browser-to-desktop onboarding/manual path is available as one canonical
crawlable page rather than only as repeated fragments across homepage, docs,
and download
- the next same-family launch-authority continuation then did the same for
preview-versus-launch posture through a dedicated public `/launch-status`
route, so rollout blockers, release references, support lanes, and packaged
proof no longer have to be reconstructed from pricing plus scattered callouts
- this keeps the public manual professional without inventing browser
simulator parity or widening the native XR branch
- focused validation for that continuation stayed green under:

File diff suppressed because one or more lines are too long

View file

@ -4,6 +4,7 @@ First-party `hypertwist.app` surface for HyperTwist:
- public homepage, about, resources, pricing, support, and legal pages
- dedicated public `/getting-started` onboarding route for the first real browser-to-desktop operator journey
- dedicated public `/launch-status` route for the canonical preview-versus-launch rollout checklist
- public feature-atlas route for current capability and boundary truth
- browser-facing operator/account dashboard
- protected browser-access, account, and notices routes backed by live auth-health and release-manifest authority
@ -80,6 +81,9 @@ manual:
- that same first-session story is now also anchored at a dedicated public
`/getting-started` route so the shortest complete onboarding/manual lane is
crawlable, canonical, and easier to hand off than the broader page set alone
- the same launch-authority story is now also anchored at a dedicated public
`/launch-status` route so preview-versus-launch truth, rollout blockers,
packaged proof, and release references have one canonical authority surface
- the protected app shell now also carries richer operator-facing browser
boundary, account, entitlement, and notices guidance instead of treating
those routes as thin placeholders beside the main dashboard

View file

@ -15,6 +15,9 @@
<url>
<loc>https://hypertwist.app/getting-started</loc>
</url>
<url>
<loc>https://hypertwist.app/launch-status</loc>
</url>
<url>
<loc>https://hypertwist.app/docs</loc>
</url>

View file

@ -187,6 +187,14 @@ describe('AppRouteTree', () => {
expect(await screen.findByText('Start in the browser. Train in the desktop runtime.')).toBeTruthy()
})
it('renders the launch-status page through the real public route tree', async () => {
renderRoute('/launch-status')
expect(screen.getByText('Launch status')).toBeTruthy()
expect(screen.getByText('Launch authority')).toBeTruthy()
expect(screen.getByText(/canonical public launch-readiness checklist, release posture, and preview-versus-launch authority map/i)).toBeTruthy()
expect(await screen.findByText('See exactly what still separates preview from public launch.')).toBeTruthy()
})
it('renders the login page through the real public route tree', async () => {
renderRoute('/login?next=%2Fapp%2Fdownloads')

View file

@ -120,6 +120,7 @@ import {
FeaturesPage,
GettingStartedPage,
HomeLanding,
LaunchStatusPage,
OpenSourceNoticesPage,
PricingPage,
PrivacyPage,
@ -1422,6 +1423,117 @@ describe('public marketing pages', () => {
expect(document.head.querySelector('meta[property="og:url"]')?.getAttribute('content')).toBe('https://hypertwist.app/getting-started')
})
it('renders the dedicated launch-status page as the canonical public release-readiness route', async () => {
mockGetAuthHealth.mockResolvedValue({
ok: true,
service: 'hypertwist-auth-server',
supertokens: {
configured: true,
reachable: true,
ready: true,
apiVersion: '5.4',
error: null,
oauth: {
github: true,
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: [
{
platform_key: 'windows',
platform: 'Windows',
subtitle: 'Primary shipping lane',
details: 'Current packaged validation is strongest on the Windows Unreal lane.',
configured: true,
channel: 'candidate',
version: '1.0.0',
build_id: 'win64-1000',
published_at: '2026-06-22T00:00:00.000Z',
file_name: 'HyperTwist-Windows.zip',
file_size_bytes: 1048576,
checksum_sha256: 'abc123',
download_url: null,
download_available: false,
validation_summary: {
lane: 'Windows Unreal packaged validation',
result: 'passed',
generated_at: '2026-06-22T01:43:08.7625247Z',
configuration: 'Development',
skip_build: true,
smoke_map_count: 2,
smoke_maps: [
{
map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining',
label: 'Magic120Cell dedicated-family training map',
result: 'passed',
},
{
map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining',
label: 'MagicCube5D dedicated-family training map',
result: 'passed',
},
],
},
},
],
},
})
renderWithProviders(<LaunchStatusPage />, ['/launch-status'])
expect(screen.getByText('Current launch-readiness checklist')).toBeTruthy()
expect(screen.getByText('What must be true before public launch')).toBeTruthy()
expect(screen.getByText('Launch hardening tracks')).toBeTruthy()
expect(screen.getByText('Launch support and escalation lanes')).toBeTruthy()
expect(screen.getByText('Choose the next launch move')).toBeTruthy()
expect(screen.getByText('Release, notice, and support bundle')).toBeTruthy()
expect(screen.getByText('Why launch authority stays in the browser shell')).toBeTruthy()
expect(screen.getAllByText('Launch readiness').length).toBeGreaterThan(0)
expect(
screen.getAllByRole('link', { name: 'Open launch status' }).some((link) => link.getAttribute('href') === '/launch-status'),
).toBe(true)
expect(document.title).toBe('HyperTwist launch status | HyperTwist')
expect(document.head.querySelector('meta[property="og:url"]')?.getAttribute('content')).toBe('https://hypertwist.app/launch-status')
})
it('renders the release-notes page with the latest native selector-recall packet and rollout checklist', async () => {
mockGetAuthHealth.mockResolvedValue({
ok: true,

View file

@ -34,6 +34,7 @@ describe('public route registry', () => {
expect(crawlablePublicRoutes.map((route) => route.path)).not.toContain('/login')
expect(crawlablePublicRoutes.map((route) => route.path)).not.toContain('/register')
expect(crawlablePublicRoutes.map((route) => route.path)).toContain('/getting-started')
expect(crawlablePublicRoutes.map((route) => route.path)).toContain('/launch-status')
})
it('carries non-empty loader metadata for every shared public-shell route', () => {
@ -50,6 +51,7 @@ describe('public route registry', () => {
expect(xml).toContain('<loc>https://hypertwist.app/</loc>')
expect(xml).toContain('<loc>https://hypertwist.app/features</loc>')
expect(xml).toContain('<loc>https://hypertwist.app/getting-started</loc>')
expect(xml).toContain('<loc>https://hypertwist.app/launch-status</loc>')
expect(xml).not.toContain('/login')
expect(xml).not.toContain('/register')
})

View file

@ -55,7 +55,7 @@ export function MarketingLaunchStatusBanner() {
: `Live preview lane: remaining launch blockers are ${missingLabels.join(', ')}.`}
</p>
<div className="status-row">
<Link className="inline-link" to="/pricing">
<Link className="inline-link" to="/launch-status">
View launch posture
</Link>
<Link className="inline-link" to={buildProtectedDownloadPath('windows')}>

View file

@ -50,11 +50,12 @@ export const supportTopicGuidance: Record<string, SupportTopicGuide> = {
'Keep notices and corresponding-source posture live before widening rollout.',
],
routeFocus: [
'Pricing and download pages explain public launch posture and target selection.',
'Launch-status, pricing, and download pages explain public launch posture and target selection.',
'Public notices and terms pages carry the distribution and legal follow-through.',
'The public manual explains the browser-versus-desktop product split that launch copy must preserve.',
],
actions: [
{ label: 'Open launch status', to: '/launch-status' },
{ label: 'Sign in for protected release lane', to: buildLoginPath(buildProtectedDownloadPath('windows')) },
{ label: 'Sign in for protected notices', to: buildLoginPath('/app/notices') },
{ label: 'Open pricing', to: '/pricing' },

View file

@ -68,7 +68,8 @@ type StepCard = {
type StepOnlyCard = {
title: string
steps: readonly string[]
steps?: readonly string[]
bullets?: readonly string[]
}
type FaqCard = {
@ -173,7 +174,7 @@ function StepOnlyCardSection({
<article key={card.title} className="card">
<h3>{card.title}</h3>
<ul className="list top-gap">
{card.steps.map((step) => (
{(card.steps ?? card.bullets ?? []).map((step) => (
<li key={step}>{step}</li>
))}
</ul>
@ -674,6 +675,11 @@ export function ResourcesPage() {
<h3>Getting started</h3>
<p>The canonical first-session path from browser access into the packaged desktop runtime.</p>
</Link>
<Link to="/launch-status" className="feature-band__card feature-band__card--link">
<Landmark size={22} />
<h3>Launch status</h3>
<p>The canonical public checklist for preview-versus-launch posture, release authority, and rollout blockers.</p>
</Link>
<Link to="/docs" className="feature-band__card feature-band__card--link">
<BookOpenText size={22} />
<h3>Docs landing</h3>
@ -855,6 +861,111 @@ export function ResourcesPage() {
)
}
function GettingStartedPageContent({
releaseManifest,
windowsValidationPlatform,
}: Pick<ReturnType<typeof usePublicReleaseManifestView>, 'releaseManifest' | 'windowsValidationPlatform'>) {
return (
<>
<OperatorDesktopQuickstartSection
title="The first serious HyperTwist session"
description="This quickstart stays faithful to the current product topology: browser first for identity and release posture, desktop first for the actual simulator."
/>
<StepCardSection
title="Operator path"
description="These are the current first-party working stages from public discovery through protected release access and into the native simulator."
cards={operatorManualTracks}
/>
<Section
title="Browser account access methods"
description="The onboarding path is easier to trust when the shared-auth provider lineup is visible before operators cross into the protected shell."
>
<BrowserAuthMethodsGuide />
</Section>
<Section
title="First launch and desktop setup"
description="These are the concrete checks that should happen immediately after the package is in hand."
>
<div className="card-grid">
{desktopFirstLaunchCards.map((card) => (
<article key={card.title} className="card">
<h3>{card.title}</h3>
<p>{card.description}</p>
<ul className="list top-gap">
{card.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
<BrowserDesktopRealitySection />
<Section
title="Simulator use today"
description="This keeps onboarding anchored to the lanes that actually execute in the current native runtime."
>
<div className="card-grid">
{simulatorManualCards.map((card) => (
<article key={card.title} className="card">
<h3>{card.title}</h3>
<p>{card.description}</p>
<ul className="list top-gap">
{card.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
<BulletCardSection
title="Current control and device truth"
description="This is the practical runtime boundary that first-session onboarding should teach directly instead of leaving as a later surprise."
cards={inputAndDevicePostureCards}
/>
<BulletCardSection
title="Selectable control and settings roster"
description="The current desktop runtime already has real profile and settings ownership, but it stays narrower than a finished XR/controller/preferences suite."
cards={controlProfileRosterCards}
/>
<SurfaceChoiceGuideSection description="Onboarding is smoother when the right next surface is explicit too: public for orientation, protected for entitled access, and desktop for simulator execution." />
<PublicReleaseDecisionGuideSection
releaseManifest={releaseManifest}
description="This onboarding lane should also tell operators whether the next honest move is protected desktop access, pricing/provisioning, browser/account continuity, or notices/source follow-through."
/>
<PublicPackagedDesktopProofSection
platform={windowsValidationPlatform}
actions={[
{ to: '/download', label: 'Open download center' },
{ to: '/app', label: 'Open operator dashboard' },
]}
/>
<SupportTopicDirectorySection
title="Need help on the way in?"
description="Use these quick routes when the next onboarding step turns into a launch, access, or rollout issue."
topics={supportTopicDirectory}
/>
<ReleaseAuthorityBundleSection
releaseManifest={releaseManifest}
description="A professional onboarding page should keep docs, release notes, corresponding source, notices, and operator support visible as part of the same release story."
/>
</>
)
}
export function GettingStartedPage() {
const { releaseManifest, windowsValidationPlatform } = usePublicReleaseManifestView('public-getting-started')
@ -870,100 +981,81 @@ export function GettingStartedPage() {
title="Start in the browser. Train in the desktop runtime."
lede="This page is the shortest complete operator path through the current product: resolve access, pair the installed runtime safely, verify the first session, and keep the real XR/controller boundary visible."
>
<OperatorDesktopQuickstartSection
title="The first serious HyperTwist session"
description="This quickstart stays faithful to the current product topology: browser first for identity and release posture, desktop first for the actual simulator."
/>
<StepCardSection
title="Operator path"
description="These are the current first-party working stages from public discovery through protected release access and into the native simulator."
cards={operatorManualTracks}
/>
<Section
title="Browser account access methods"
description="The onboarding path is easier to trust when the shared-auth provider lineup is visible before operators cross into the protected shell."
>
<BrowserAuthMethodsGuide />
</Section>
<Section
title="First launch and desktop setup"
description="These are the concrete checks that should happen immediately after the package is in hand."
>
<div className="card-grid">
{desktopFirstLaunchCards.map((card) => (
<article key={card.title} className="card">
<h3>{card.title}</h3>
<p>{card.description}</p>
<ul className="list top-gap">
{card.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
<BrowserDesktopRealitySection />
<Section
title="Simulator use today"
description="This keeps onboarding anchored to the lanes that actually execute in the current native runtime."
>
<div className="card-grid">
{simulatorManualCards.map((card) => (
<article key={card.title} className="card">
<h3>{card.title}</h3>
<p>{card.description}</p>
<ul className="list top-gap">
{card.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
<BulletCardSection
title="Current control and device truth"
description="This is the practical runtime boundary that first-session onboarding should teach directly instead of leaving as a later surprise."
cards={inputAndDevicePostureCards}
/>
<BulletCardSection
title="Selectable control and settings roster"
description="The current desktop runtime already has real profile and settings ownership, but it stays narrower than a finished XR/controller/preferences suite."
cards={controlProfileRosterCards}
/>
<SurfaceChoiceGuideSection description="Onboarding is smoother when the right next surface is explicit too: public for orientation, protected for entitled access, and desktop for simulator execution." />
<PublicReleaseDecisionGuideSection
<GettingStartedPageContent
releaseManifest={releaseManifest}
description="This onboarding lane should also tell operators whether the next honest move is protected desktop access, pricing/provisioning, browser/account continuity, or notices/source follow-through."
windowsValidationPlatform={windowsValidationPlatform}
/>
</MarketingShell>
</>
)
}
export function LaunchStatusPage() {
const { releaseManifest, windowsValidationPlatform } = usePublicReleaseManifestView('public-launch-status')
return (
<>
<SiteMetadata
title="HyperTwist launch status"
description="Check the canonical HyperTwist public launch-readiness posture: checkout, download, notices, corresponding source, public auth runtime, and release authority."
canonicalPath="/launch-status"
/>
<MarketingShell
eyebrow="Launch authority"
title="See exactly what still separates preview from public launch."
lede="This is the canonical public authority surface for rollout truth: release targets, checkout posture, notices, corresponding source, and public auth-runtime readiness stay visible here in one place instead of being reconstructed from scattered callouts."
>
<Section
title="Current public launch status"
description="Use this page when rollout language, pricing, download, and legal/distribution follow-through need one authoritative browser-owned checklist."
>
<PublicLaunchStatus title="Current launch-readiness checklist" />
</Section>
<StepOnlyCardSection
title="What must be true before public launch"
description="These are the bounded rollout checks that keep external launch language tied to real release authority instead of aspiration."
cards={releaseRolloutChecklist}
/>
<StepOnlyCardSection
title="Launch hardening tracks"
description="These are the practical work tracks for turning a truthful preview lane into a production-grade public release lane."
cards={deploymentReadinessTracks}
/>
<PublicPackagedDesktopProofSection
platform={windowsValidationPlatform}
actions={[
{ to: '/download', label: 'Open download center' },
{ to: '/app', label: 'Open operator dashboard' },
{ to: '/getting-started', label: 'Open getting started' },
]}
/>
<SupportTopicDirectorySection
title="Need help on the way in?"
description="Use these quick routes when the next onboarding step turns into a launch, access, or rollout issue."
title="Launch support and escalation lanes"
description="Launch posture only helps when the next human lane is explicit too, so these support routes stay attached to the same authority surface."
topics={supportTopicDirectory}
selectedTopicKey="launch-readiness"
/>
<PublicReleaseDecisionGuideSection
releaseManifest={releaseManifest}
title="Choose the next launch move"
description="This page should not stop at checklist language. It should also say whether the next honest move is protected desktop access, pricing/provisioning, browser/account follow-through, or notices/source review."
/>
<ReleaseAuthorityBundleSection
releaseManifest={releaseManifest}
description="A professional onboarding page should keep docs, release notes, corresponding source, notices, and operator support visible as part of the same release story."
title="Release, notice, and support bundle"
description="Launch readiness is only trustworthy when docs, release notes, corresponding source, public notices, and operator contact remain visible as one release bundle."
/>
<SurfaceChoiceGuideSection description="This keeps launch posture grounded in the real product topology too: public for launch truth, protected for account-aware release work, and native for the simulator itself." />
<BrowserDesktopRealitySection
title="Why launch authority stays in the browser shell"
description="Launch-readiness posture belongs to the distribution shell because checkout, notices, release references, and account access are browser-owned even while the actual training runtime remains native."
/>
</MarketingShell>
</>

View file

@ -49,6 +49,16 @@
"footer": true,
"crawlable": true
},
{
"path": "/launch-status",
"label": "Launch Status",
"loaderTitle": "Launch status",
"loaderEyebrow": "Launch authority",
"loaderDescription": "Loading the canonical public launch-readiness checklist, release posture, and preview-versus-launch authority map.",
"nav": false,
"footer": true,
"crawlable": true
},
{
"path": "/docs",
"label": "Docs",

View file

@ -8,6 +8,7 @@ const FeaturesPage = lazy(() => import('../pages/public-pages-features').then((m
const AboutPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.AboutPage })))
const ResourcesPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.ResourcesPage })))
const GettingStartedPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.GettingStartedPage })))
const LaunchStatusPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.LaunchStatusPage })))
const DocsPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.DocsPage })))
const SupportPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.SupportPage })))
const ChangelogPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.ChangelogPage })))
@ -26,6 +27,7 @@ const publicRouteElements: Record<string, ReactNode> = {
'/about': <AboutPage />,
'/resources': <ResourcesPage />,
'/getting-started': <GettingStartedPage />,
'/launch-status': <LaunchStatusPage />,
'/docs': <DocsPage />,
'/support': <SupportPage />,
'/changelog': <ChangelogPage />,

View file

@ -819,6 +819,7 @@ export const resourceCollections = [
items: [
'Feature registry-backed descriptions only',
'Dedicated getting-started route for the first real browser-to-desktop operator journey',
'Dedicated launch-status route for the canonical preview-versus-launch authority surface',
'Public feature atlas for the real browser-versus-desktop product split',
'Roadmap-honest separation between shipped and retained capability',
'Release notes that surface what actually landed',
@ -864,6 +865,11 @@ export const resourceCollections = [
] as const
export const changelogEntries = [
{
date: 'June 27, 2026',
title: 'Launch status now has its own canonical public authority route',
details: 'HyperTwist now exposes a dedicated public `/launch-status` surface for the live launch-readiness checklist, rollout tracks, packaged desktop proof, release references, and the exact preview-versus-launch blockers, so launch truth no longer has to be reconstructed from pricing plus scattered callouts.',
},
{
date: 'June 27, 2026',
title: 'A dedicated getting-started route now anchors the public onboarding story',