Harden HyperTwist route loading surfaces

This commit is contained in:
axiomlogicnexus 2026-06-25 09:34:50 +00:00
parent ae4e517e5d
commit 18f4a32990
10 changed files with 208 additions and 14 deletions

View file

@ -156,6 +156,8 @@ describe('AppRouteTree', () => {
renderRoute('/')
expect(screen.getByText('Loading HyperTwist...')).toBeTruthy()
expect(screen.getByText('Public release shell')).toBeTruthy()
expect(screen.getByText(/public product story, current launch posture, and browser-versus-desktop authority map/i)).toBeTruthy()
expect(await screen.findByText('HyperTwist turns cube practice into a real operator-grade training stack.')).toBeTruthy()
})
@ -180,6 +182,8 @@ describe('AppRouteTree', () => {
it('renders the login page through the real public route tree', async () => {
renderRoute('/login?next=%2Fapp%2Fdownloads')
expect(screen.getByText('Browser account access')).toBeTruthy()
expect(screen.getByText(/safe next-step routing, and protected release continuity/i)).toBeTruthy()
expect(await screen.findByText('Log in to HyperTwist')).toBeTruthy()
expect(screen.getByRole('link', { name: /create an account/i }).getAttribute('href')).toBe('/register?next=%2Fapp%2Fdownloads')
})

View file

@ -35,6 +35,14 @@ describe('public route registry', () => {
expect(crawlablePublicRoutes.map((route) => route.path)).not.toContain('/register')
})
it('carries non-empty loader metadata for every shared public-shell route', () => {
for (const route of publicRouteRegistry) {
expect(route.loaderTitle.trim().length).toBeGreaterThan(0)
expect((route.loaderDescription || '').trim().length).toBeGreaterThan(0)
expect((route.loaderEyebrow || '').trim().length).toBeGreaterThan(0)
}
})
it('renders sitemap XML from the shared crawlable route registry', () => {
const xml = buildPublicSitemapXml()

View file

@ -156,10 +156,13 @@ describe('website route and shell behavior', () => {
isLoading: true,
})
renderProtectedRoute('/app/account')
const { container } = renderProtectedRoute('/app/account')
expect(screen.getByText('Loading operator access...')).toBeTruthy()
expect(screen.getByText('Browser account access')).toBeTruthy()
expect(screen.getByText(/Checking your saved browser session, protected release posture, and browser-to-desktop continuity/i)).toBeTruthy()
expect(container.querySelector('.general-page-loader-shell--fullscreen')).not.toBeNull()
expect(screen.getByAltText('HyperTwist')).toBeTruthy()
expect(screen.getByRole('link', { name: 'Open sign-in' }).getAttribute('href')).toBe('/login?next=%2Fapp%2Faccount')
expect(screen.getByRole('link', { name: 'Open support' }).getAttribute('href')).toBe('/support?topic=operator-access')
})

View file

@ -13,7 +13,10 @@ export function ProtectedRoute() {
return (
<GeneralPageLoader
title="Loading operator access..."
eyebrow="Browser account access"
description="Checking your saved browser session, protected release posture, and browser-to-desktop continuity before the operator shell opens."
fullscreen
showBrand
>
<div className="button-row general-page-loader-actions">
<Link className="button button--ghost" to={loginRedirectTarget}>

View file

@ -5,10 +5,16 @@ export function GeneralPageLoader({
title = 'Loading',
description,
children,
eyebrow,
fullscreen = false,
showBrand = false,
}: {
title?: string
description?: string
children?: ReactNode
eyebrow?: string
fullscreen?: boolean
showBrand?: boolean
}) {
const [isCompleting, setIsCompleting] = useState(false)
@ -29,8 +35,23 @@ export function GeneralPageLoader({
const spinnerColor = isCompleting ? 'var(--ht-amber)' : 'var(--ht-cyan)'
return (
<div className="general-page-loader-shell" role="status" aria-live="polite" aria-label={title}>
<div
className={`general-page-loader-shell${fullscreen ? ' general-page-loader-shell--fullscreen' : ''}`}
role="status"
aria-live="polite"
aria-label={title}
>
<div className="general-page-loader-frame">
{showBrand ? (
<div className="general-page-loader-brand">
<img
src="/branding/hypertwist-3d-symbol.png"
alt="HyperTwist"
className="general-page-loader-brand__image"
/>
</div>
) : null}
{eyebrow ? <p className="general-page-loader-eyebrow">{eyebrow}</p> : null}
<NamedAgentSpinner
name="cascade"
size={32}

View file

@ -3,6 +3,8 @@
"path": "/",
"label": "Home",
"loaderTitle": "Loading HyperTwist...",
"loaderEyebrow": "Public release shell",
"loaderDescription": "Loading the public product story, current launch posture, and browser-versus-desktop authority map.",
"nav": true,
"footer": false,
"crawlable": true
@ -11,6 +13,8 @@
"path": "/features",
"label": "Features",
"loaderTitle": "Feature atlas",
"loaderEyebrow": "Capability atlas",
"loaderDescription": "Loading the shipped feature map, higher-dimensional runtime posture, and explicit desktop-first boundaries.",
"nav": true,
"footer": true,
"crawlable": true
@ -19,6 +23,8 @@
"path": "/about",
"label": "About",
"loaderTitle": "About HyperTwist",
"loaderEyebrow": "Product narrative",
"loaderDescription": "Loading the mission, higher-dimensional seriousness, and current control-boundary truth behind the product.",
"nav": true,
"footer": true,
"crawlable": true
@ -27,6 +33,8 @@
"path": "/resources",
"label": "Resources",
"loaderTitle": "Resources",
"loaderEyebrow": "Public reference portal",
"loaderDescription": "Loading rollout-safe resources, simulator guidance, and current higher-dimensional operator references.",
"nav": true,
"footer": true,
"crawlable": true
@ -35,6 +43,8 @@
"path": "/docs",
"label": "Docs",
"loaderTitle": "Documentation",
"loaderEyebrow": "Public manual",
"loaderDescription": "Loading the feature-registry-backed manual, rollout doctrine, and browser-versus-desktop usage guide.",
"nav": false,
"footer": false,
"crawlable": true
@ -43,6 +53,8 @@
"path": "/support",
"label": "Support",
"loaderTitle": "Support",
"loaderEyebrow": "Operator help lane",
"loaderDescription": "Loading rollout guidance, account and entitlement help, and browser-to-desktop escalation routes.",
"nav": false,
"footer": false,
"crawlable": true
@ -51,6 +63,8 @@
"path": "/changelog",
"label": "Release notes",
"loaderTitle": "Release notes",
"loaderEyebrow": "Public release history",
"loaderDescription": "Loading recent browser, package, control-boundary, and rollout changes from the active shipping lane.",
"nav": false,
"footer": false,
"crawlable": true
@ -59,6 +73,8 @@
"path": "/pricing",
"label": "Pricing",
"loaderTitle": "Pricing",
"loaderEyebrow": "Paddle-ready plans",
"loaderDescription": "Loading plan posture, public launch readiness, and the current desktop-first distribution model.",
"nav": true,
"footer": true,
"crawlable": true
@ -67,6 +83,8 @@
"path": "/download",
"label": "Download",
"loaderTitle": "Download center",
"loaderEyebrow": "Desktop distribution",
"loaderDescription": "Loading the current release manifest posture, package proof, and browser-to-desktop pairing guidance.",
"nav": true,
"footer": true,
"crawlable": true
@ -75,6 +93,8 @@
"path": "/open-source-notices",
"label": "Open Source Notices",
"loaderTitle": "Open source notices",
"loaderEyebrow": "Distribution references",
"loaderDescription": "Loading notices, corresponding-source posture, and public legal follow-through for downloadable builds.",
"nav": false,
"footer": true,
"crawlable": true
@ -83,6 +103,8 @@
"path": "/privacy",
"label": "Privacy",
"loaderTitle": "Privacy",
"loaderEyebrow": "Public policy surface",
"loaderDescription": "Loading the browser-account privacy posture, desktop-link boundary, and distribution-safe data handling notes.",
"nav": false,
"footer": true,
"crawlable": true
@ -91,6 +113,8 @@
"path": "/terms",
"label": "Terms",
"loaderTitle": "Terms",
"loaderEyebrow": "Public policy surface",
"loaderDescription": "Loading access terms, protected download boundaries, and the current desktop-first simulator posture.",
"nav": false,
"footer": true,
"crawlable": true
@ -99,6 +123,8 @@
"path": "/shipping-payment",
"label": "Shipping & Payment",
"loaderTitle": "Shipping & payment",
"loaderEyebrow": "Distribution policy",
"loaderDescription": "Loading the digital-delivery model, Paddle-ready billing posture, and protected release follow-through.",
"nav": false,
"footer": true,
"crawlable": true
@ -107,6 +133,8 @@
"path": "/login",
"label": "Log in",
"loaderTitle": "Log in",
"loaderEyebrow": "Browser account access",
"loaderDescription": "Preparing sign-in, safe next-step routing, and protected release continuity before the operator shell opens.",
"nav": false,
"footer": false,
"crawlable": false
@ -115,6 +143,8 @@
"path": "/register",
"label": "Create account",
"loaderTitle": "Create account",
"loaderEyebrow": "Browser account access",
"loaderDescription": "Preparing account creation, protected release follow-through, and browser-to-desktop continuity for first launch.",
"nav": false,
"footer": false,
"crawlable": false

View file

@ -4,6 +4,8 @@ export type PublicRouteDefinition = {
path: string
label: string
loaderTitle: string
loaderEyebrow?: string
loaderDescription?: string
nav: boolean
footer: boolean
crawlable: boolean

View file

@ -11,8 +11,60 @@ const BrowserAccessPage = lazy(() => import('../pages/app-pages').then((m) => ({
const AccountPage = lazy(() => import('../pages/app-pages').then((m) => ({ default: m.AccountPage })))
const NoticesPage = lazy(() => import('../pages/app-pages').then((m) => ({ default: m.NoticesPage })))
function withAppLoader(title: string, element: React.ReactNode) {
return <Suspense fallback={<GeneralPageLoader title={title} />}>{element}</Suspense>
const protectedAppLoaders = {
dashboard: {
title: 'Loading dashboard...',
eyebrow: 'Protected operator shell',
description:
'Restoring account posture, release authority, package proof, and browser-to-desktop pairing surfaces inside the signed-in operator lane.',
},
downloads: {
title: 'Loading downloads...',
eyebrow: 'Protected release lane',
description:
'Resolving entitled desktop targets, current package proof, and rollout references before the signed-in download surface opens.',
},
browserAccess: {
title: 'Loading browser access...',
eyebrow: 'Protected browser shell',
description:
'Restoring the signed-in browser posture, release guidance, and explicit browser-versus-desktop authority boundaries.',
},
account: {
title: 'Loading account...',
eyebrow: 'Protected account lane',
description:
'Refreshing the signed-in account view, current plan posture, and release-access continuity before desktop follow-through resumes.',
},
notices: {
title: 'Loading notices...',
eyebrow: 'Protected distribution references',
description:
'Restoring signed-in notices, corresponding-source posture, and download/distribution references inside the protected release shell.',
},
} as const
function withAppLoader(
loader: {
title: string
eyebrow: string
description: string
},
element: React.ReactNode,
) {
return (
<Suspense
fallback={
<GeneralPageLoader
title={loader.title}
eyebrow={loader.eyebrow}
description={loader.description}
/>
}
>
{element}
</Suspense>
)
}
export function AppRouteTree() {
@ -22,11 +74,11 @@ export function AppRouteTree() {
<Route element={<ProtectedRoute />}>
<Route path="/app" element={<AppShell />}>
<Route index element={withAppLoader('Loading dashboard...', <DashboardOverviewPage />)} />
<Route path="downloads" element={withAppLoader('Loading downloads...', <DownloadCenterPage />)} />
<Route path="browser-access" element={withAppLoader('Loading browser access...', <BrowserAccessPage />)} />
<Route path="account" element={withAppLoader('Loading account...', <AccountPage />)} />
<Route path="notices" element={withAppLoader('Loading notices...', <NoticesPage />)} />
<Route index element={withAppLoader(protectedAppLoaders.dashboard, <DashboardOverviewPage />)} />
<Route path="downloads" element={withAppLoader(protectedAppLoaders.downloads, <DownloadCenterPage />)} />
<Route path="browser-access" element={withAppLoader(protectedAppLoaders.browserAccess, <BrowserAccessPage />)} />
<Route path="account" element={withAppLoader(protectedAppLoaders.account, <AccountPage />)} />
<Route path="notices" element={withAppLoader(protectedAppLoaders.notices, <NoticesPage />)} />
</Route>
</Route>

View file

@ -1,7 +1,7 @@
import { Suspense, lazy, type ReactNode } from 'react'
import { Route } from 'react-router-dom'
import { GeneralPageLoader } from '../components/ui/Skeletons'
import { publicRouteRegistry } from '../public-route-registry'
import { publicRouteRegistry, type PublicRouteDefinition } from '../public-route-registry'
const HomeLanding = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.HomeLanding })))
const FeaturesPage = lazy(() => import('../pages/public-pages-features').then((m) => ({ default: m.FeaturesPage })))
@ -37,8 +37,24 @@ const publicRouteElements: Record<string, ReactNode> = {
'/register': <RegisterPage />,
}
function withGeneralLoader(title: string, element: ReactNode) {
return <Suspense fallback={<GeneralPageLoader title={title} />}>{element}</Suspense>
function withGeneralLoader(route: PublicRouteDefinition, element: ReactNode) {
const isAuthRoute = route.path === '/login' || route.path === '/register'
return (
<Suspense
fallback={
<GeneralPageLoader
title={route.loaderTitle}
eyebrow={route.loaderEyebrow}
description={route.loaderDescription}
fullscreen={isAuthRoute}
showBrand
/>
}
>
{element}
</Suspense>
)
}
export function renderPublicRoutes() {
@ -48,7 +64,7 @@ export function renderPublicRoutes() {
<Route
key={route.path}
path={route.path}
element={withGeneralLoader(route.loaderTitle, publicRouteElements[route.path])}
element={withGeneralLoader(route, publicRouteElements[route.path])}
/>
))}
</>

View file

@ -664,7 +664,14 @@ code {
place-items: center;
overflow: hidden;
padding: 1.5rem;
background: transparent;
background:
radial-gradient(circle at top, rgba(84, 203, 255, 0.12), transparent 40%),
radial-gradient(circle at bottom, rgba(247, 178, 103, 0.1), transparent 48%),
linear-gradient(180deg, rgba(6, 10, 20, 0.9), rgba(7, 11, 19, 0.96));
}
.general-page-loader-shell--fullscreen {
min-height: 100vh;
}
.general-page-loader-frame {
@ -684,7 +691,51 @@ code {
backdrop-filter: blur(14px);
}
.general-page-loader-frame::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
background: radial-gradient(circle at top, rgba(255, 255, 255, 0.08), transparent 58%);
}
.general-page-loader-brand {
position: relative;
z-index: 1;
display: grid;
place-items: center;
width: 4.5rem;
height: 4.5rem;
border-radius: 1.4rem;
border: 1px solid rgba(84, 203, 255, 0.24);
background:
radial-gradient(circle at top, rgba(84, 203, 255, 0.12), transparent 58%),
rgba(8, 13, 26, 0.9);
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.04),
0 0 22px rgba(84, 203, 255, 0.16);
}
.general-page-loader-brand__image {
width: 2.4rem;
height: 2.4rem;
object-fit: contain;
}
.general-page-loader-eyebrow {
position: relative;
z-index: 1;
margin: 0;
font-size: 0.73rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.62);
}
.general-page-loader-text {
position: relative;
z-index: 1;
display: inline-flex;
font-size: 0.81rem;
font-weight: 600;
@ -694,6 +745,8 @@ code {
}
.general-page-loader-body {
position: relative;
z-index: 1;
margin: 0;
max-width: 32rem;
text-align: center;
@ -702,6 +755,8 @@ code {
}
.general-page-loader-extra {
position: relative;
z-index: 1;
width: 100%;
}