Harden protected website operator surfaces
This commit is contained in:
parent
00f25338d8
commit
9c93020c69
6 changed files with 615 additions and 44 deletions
|
|
@ -63,6 +63,24 @@ into a more practical public operator reference:
|
|||
operational language, and shipping/payment now carries the real digital
|
||||
delivery workflow instead of generic storefront filler
|
||||
|
||||
## Protected-surface continuation
|
||||
|
||||
The protected browser shell is no longer only one strong dashboard route plus
|
||||
thin adjacent placeholders.
|
||||
|
||||
The later continuation also hardened:
|
||||
|
||||
- `/app/browser-access` with live auth-health posture, browser-to-desktop
|
||||
handoff guidance, bounded branch truth, and release-reference links
|
||||
- `/app/account` with live release-manifest viewer posture, configured target
|
||||
visibility, and explicit operator follow-through links
|
||||
- `/app/notices` with protected release-target notice posture, operator duties,
|
||||
and linked protected/public notices plus corresponding-source references
|
||||
|
||||
This keeps the protected app shell aligned with the public manual packet so the
|
||||
operator surface remains honest and useful after sign-in rather than becoming a
|
||||
drop in information quality.
|
||||
|
||||
## Public pages widened by this packet
|
||||
|
||||
- homepage
|
||||
|
|
@ -107,6 +125,11 @@ The same-day public-manual continuation passed:
|
|||
- `npm run type-check` in `website/server`
|
||||
- `npm test -- --run` in `website/server`
|
||||
|
||||
The protected-surface continuation also passed:
|
||||
|
||||
- `npm test -- --run src/__tests__/protected-app-pages.test.tsx src/__tests__/app-route-tree.test.tsx`
|
||||
- `npm test -- --run` in `website/` after the protected-route widening
|
||||
|
||||
The same validation hardening also gives the live-spawned
|
||||
`website/server` bootstrap proof an explicit `15s` timeout so real child-process
|
||||
boot plus same-origin HTTP verification does not fail spuriously on a loaded
|
||||
|
|
|
|||
|
|
@ -209,6 +209,17 @@ Follow-up tool refresh on `2026-06-22`:
|
|||
is still isolated to the same Unreal validator seams rather than the browser
|
||||
or website lane
|
||||
|
||||
Additional follow-up after the protected website hardening continuation:
|
||||
|
||||
- `scripts/run-hypertwist-sentrux-source-only.sh` improved slightly to
|
||||
`Quality: 5902`
|
||||
- the remaining structural debt still stayed isolated to the same Unreal
|
||||
validator seams:
|
||||
- two `HyperTwistSkillTypes.h` `IsStructurallyValid()` functions
|
||||
- one `HyperTwistRecognitionTypes.h` `IsStructurallyValid()` function
|
||||
- the protected website/account/notices/browser-access widening did not create
|
||||
any new cycle debt or large-function debt in the browser or website lane
|
||||
|
||||
Important nuance from the `2026-06-22` follow-up:
|
||||
|
||||
- a helper-only readability pass on `HyperTwistSkillTypes.h` was tested and
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ First-party `hypertwist.app` surface for HyperTwist:
|
|||
|
||||
- public homepage, about, resources, pricing, support, and legal pages
|
||||
- browser-facing operator/account dashboard
|
||||
- protected browser-access, account, and notices routes backed by live auth-health and release-manifest authority
|
||||
- shared SuperTokens auth posture reused from the FamiliarOS and ScriptoriumAI website lane
|
||||
- desktop download posture and desktop-link handshake endpoints
|
||||
- server-backed release-manifest authority shared by public and protected download surfaces
|
||||
|
|
@ -54,6 +55,9 @@ manual:
|
|||
- the docs and resources pages now also carry a practical simulator-use manual
|
||||
for recognition, replay, higher-dimensional runtime ownership, and operator
|
||||
diagnostics without overclaiming browser or VR parity
|
||||
- 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
|
||||
|
||||
## Local development
|
||||
|
||||
|
|
|
|||
|
|
@ -320,4 +320,97 @@ describe('AppRouteTree', () => {
|
|||
})
|
||||
expect(screen.getByText('Windows')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the protected browser-access route through the real route tree when authenticated', async () => {
|
||||
mockUsePlatformAuth.mockReturnValue({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
login: vi.fn(),
|
||||
register: vi.fn(),
|
||||
logout: vi.fn(),
|
||||
toggleColorMode: vi.fn(),
|
||||
colorMode: 'dark',
|
||||
superTokensConfigured: true,
|
||||
user: {
|
||||
id: 'operator-1',
|
||||
name: 'Operator',
|
||||
email: 'operator@hypertwist.app',
|
||||
plan: 'operator',
|
||||
role: 'operator',
|
||||
canDownload: true,
|
||||
billing: {
|
||||
source: 'session',
|
||||
accessStatus: 'active',
|
||||
canDownload: true,
|
||||
lastEventType: 'transaction.completed',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
renderRoute('/app/browser-access')
|
||||
expect(await screen.findByText('Current browser posture')).toBeTruthy()
|
||||
expect(screen.getByText('Browser-to-desktop handoff')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the protected account route through the real route tree when authenticated', async () => {
|
||||
mockUsePlatformAuth.mockReturnValue({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
login: vi.fn(),
|
||||
register: vi.fn(),
|
||||
logout: vi.fn(),
|
||||
toggleColorMode: vi.fn(),
|
||||
colorMode: 'dark',
|
||||
superTokensConfigured: true,
|
||||
user: {
|
||||
id: 'operator-1',
|
||||
name: 'Operator',
|
||||
email: 'operator@hypertwist.app',
|
||||
plan: 'operator',
|
||||
role: 'operator',
|
||||
canDownload: true,
|
||||
billing: {
|
||||
source: 'session',
|
||||
accessStatus: 'active',
|
||||
canDownload: true,
|
||||
lastEventType: 'transaction.completed',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
renderRoute('/app/account')
|
||||
expect(await screen.findByText('Session profile')).toBeTruthy()
|
||||
expect(screen.getByText('Account follow-through')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the protected notices route through the real route tree when authenticated', async () => {
|
||||
mockUsePlatformAuth.mockReturnValue({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
login: vi.fn(),
|
||||
register: vi.fn(),
|
||||
logout: vi.fn(),
|
||||
toggleColorMode: vi.fn(),
|
||||
colorMode: 'dark',
|
||||
superTokensConfigured: true,
|
||||
user: {
|
||||
id: 'operator-1',
|
||||
name: 'Operator',
|
||||
email: 'operator@hypertwist.app',
|
||||
plan: 'operator',
|
||||
role: 'operator',
|
||||
canDownload: true,
|
||||
billing: {
|
||||
source: 'session',
|
||||
accessStatus: 'active',
|
||||
canDownload: true,
|
||||
lastEventType: 'transaction.completed',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
renderRoute('/app/notices')
|
||||
expect(await screen.findByText('Distribution notices')).toBeTruthy()
|
||||
expect(screen.getByText('Reference surfaces')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
215
website/src/__tests__/protected-app-pages.test.tsx
Normal file
215
website/src/__tests__/protected-app-pages.test.tsx
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
|
||||
const mockUsePlatformAuth = vi.fn()
|
||||
const mockGetAuthHealth = vi.fn()
|
||||
const mockGetReleaseManifest = vi.fn()
|
||||
|
||||
vi.mock('../auth/platform-auth', () => ({
|
||||
usePlatformAuth: () => mockUsePlatformAuth(),
|
||||
}))
|
||||
|
||||
vi.mock('../auth/auth-api', () => ({
|
||||
buildAuthApiBaseUrls: () => ['https://hypertwist.app'],
|
||||
createDesktopLinkToken: vi.fn(),
|
||||
getAuthHealth: (...args: unknown[]) => mockGetAuthHealth(...args),
|
||||
getReleaseManifest: (...args: unknown[]) => mockGetReleaseManifest(...args),
|
||||
}))
|
||||
|
||||
import { AccountPage, BrowserAccessPage, NoticesPage } from '../pages/app-pages'
|
||||
|
||||
function renderPage(page: React.ReactNode, initialEntry: string) {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter initialEntries={[initialEntry]}>
|
||||
{page}
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
}
|
||||
|
||||
function byExactTextContent(expected: string, tagName?: string) {
|
||||
return (_content: string, node: Element | null) => (
|
||||
node?.textContent === expected
|
||||
&& (tagName ? node.tagName === tagName.toUpperCase() : true)
|
||||
)
|
||||
}
|
||||
|
||||
describe('protected app pages', () => {
|
||||
beforeEach(() => {
|
||||
cleanup()
|
||||
mockUsePlatformAuth.mockReset()
|
||||
mockGetAuthHealth.mockReset()
|
||||
mockGetReleaseManifest.mockReset()
|
||||
|
||||
mockUsePlatformAuth.mockReturnValue({
|
||||
user: {
|
||||
id: 'operator-1',
|
||||
name: 'Operator',
|
||||
email: 'operator@hypertwist.app',
|
||||
authMethod: 'email',
|
||||
plan: 'operator',
|
||||
role: 'operator',
|
||||
canDownload: true,
|
||||
billing: {
|
||||
source: 'session',
|
||||
accessStatus: 'active',
|
||||
canDownload: true,
|
||||
lastEventType: 'transaction.completed',
|
||||
},
|
||||
},
|
||||
superTokensConfigured: true,
|
||||
})
|
||||
|
||||
mockGetAuthHealth.mockResolvedValue({
|
||||
ok: true,
|
||||
service: 'hypertwist-auth-server',
|
||||
supertokens: {
|
||||
configured: true,
|
||||
reachable: true,
|
||||
ready: true,
|
||||
apiVersion: '5.0',
|
||||
oauth: {
|
||||
github: false,
|
||||
google: false,
|
||||
},
|
||||
},
|
||||
fallback: {
|
||||
enabled: true,
|
||||
active: false,
|
||||
reason: null,
|
||||
},
|
||||
billing: {
|
||||
statePath: '/tmp/hypertwist-billing.json',
|
||||
processedEventCount: 1,
|
||||
pricePlanMapConfigured: true,
|
||||
productPlanMapConfigured: true,
|
||||
webhookSecretConfigured: true,
|
||||
},
|
||||
runtime: {
|
||||
mode: 'public',
|
||||
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: true,
|
||||
canDownload: true,
|
||||
plan: 'operator',
|
||||
role: 'operator',
|
||||
accessStatus: 'active',
|
||||
},
|
||||
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: 'https://downloads.hypertwist.app/windows.exe',
|
||||
download_available: true,
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
platform_key: 'macos',
|
||||
platform: 'macOS',
|
||||
subtitle: 'Planned distribution surface',
|
||||
details: 'List a signed desktop build here when the package lane is opened.',
|
||||
configured: false,
|
||||
channel: 'preview',
|
||||
version: null,
|
||||
build_id: null,
|
||||
published_at: null,
|
||||
file_name: null,
|
||||
file_size_bytes: null,
|
||||
checksum_sha256: null,
|
||||
download_url: null,
|
||||
download_available: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('surfaces live browser-shell posture and browser-to-desktop guidance', async () => {
|
||||
renderPage(<BrowserAccessPage />, '/app/browser-access')
|
||||
|
||||
expect(await screen.findByText('Current browser posture')).toBeTruthy()
|
||||
expect(await screen.findByText(byExactTextContent('Auth runtime mode: public', 'LI'))).toBeTruthy()
|
||||
expect(await screen.findByText(byExactTextContent('Configured release targets: 1/2', 'LI'))).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Open dashboard' }).getAttribute('href')).toBe('/app')
|
||||
expect(screen.getByRole('link', { name: 'Protected notices' }).getAttribute('href')).toBe('/app/notices')
|
||||
})
|
||||
|
||||
it('surfaces account release access and protected follow-through links', async () => {
|
||||
renderPage(<AccountPage />, '/app/account')
|
||||
|
||||
expect(await screen.findByText('Session profile')).toBeTruthy()
|
||||
expect(screen.getByText(byExactTextContent('Billing source: session', 'LI'))).toBeTruthy()
|
||||
expect(await screen.findByText(byExactTextContent('Manifest viewer access: active', 'LI'))).toBeTruthy()
|
||||
expect(await screen.findByText(byExactTextContent('Windows: Primary shipping lane (Version 1.0.0, Channel candidate)', 'LI'))).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Open downloads' }).getAttribute('href')).toBe('/app/downloads')
|
||||
expect(screen.getByRole('link', { name: 'Get help' }).getAttribute('href')).toBe('/support?topic=operator-access')
|
||||
})
|
||||
|
||||
it('surfaces protected notices references and release-target notice posture', async () => {
|
||||
renderPage(<NoticesPage />, '/app/notices')
|
||||
|
||||
expect(await screen.findByText('Distribution notices')).toBeTruthy()
|
||||
expect(await screen.findAllByText(byExactTextContent('Configured release targets: 1/2', 'LI'))).toHaveLength(1)
|
||||
expect(screen.getByText(byExactTextContent('Public repository/notices URL: https://github.com/hypertwist/hypertwist', 'LI'))).toBeTruthy()
|
||||
expect(screen.getByText(byExactTextContent('Corresponding-source URL: https://hypertwist.app/open-source/source.zip', 'LI'))).toBeTruthy()
|
||||
expect(screen.getByText(byExactTextContent('Windows: configured, validation passed', 'LI'))).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Public notices' }).getAttribute('href')).toBe('/open-source-notices')
|
||||
})
|
||||
})
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import { useMemo } from 'react'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { Link, useSearchParams } from 'react-router-dom'
|
||||
import { buildAuthApiBaseUrls, createDesktopLinkToken, getAuthHealth, getReleaseManifest } from '../auth/auth-api'
|
||||
import { usePlatformAuth } from '../auth/platform-auth'
|
||||
import { SiteMetadata } from '../components/seo/SiteMetadata'
|
||||
import { ReleaseValidationSummary } from '../components/ui/ReleaseValidationSummary'
|
||||
import { resolvePublicLaunchReadiness } from '../public-launch'
|
||||
import { downloadTargets, launchReadiness, mplSourceUrl, openSourceRepoUrl, planCatalog, publicDocsUrl, releaseNotesUrl } from '../site-config'
|
||||
import { buildReleaseMetadataItems, resolveReleaseCommerceView, resolveReleaseManifestView } from '../release-manifest'
|
||||
import { getDownloadPlatformLabel, normalizeDownloadPlatform } from '../site-routes'
|
||||
import { buildReleaseMetadataItems, resolveReleaseCommerceView, resolveReleaseManifestView, type ReleaseManifestView } from '../release-manifest'
|
||||
import { buildSupportPath, getDownloadPlatformLabel, normalizeDownloadPlatform } from '../site-routes'
|
||||
import { desktopDownloadSteps, roadmapHonestyCards } from '../site-data'
|
||||
|
||||
function Panel({
|
||||
|
|
@ -49,17 +49,84 @@ function buildProtectedReleaseManifestFallback(supportEmail: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export function DashboardOverviewPage() {
|
||||
const { user, superTokensConfigured } = usePlatformAuth()
|
||||
const healthQuery = useQuery({
|
||||
queryKey: ['auth-health'],
|
||||
function useProtectedAuthHealthQuery(scope: string) {
|
||||
return useQuery({
|
||||
queryKey: ['auth-health', scope],
|
||||
queryFn: getAuthHealth,
|
||||
retry: false,
|
||||
})
|
||||
const releaseManifestQuery = useQuery({
|
||||
queryKey: ['release-manifest', 'dashboard', user?.id || 'anonymous', user?.canDownload === true ? 'download' : 'nodownload'],
|
||||
}
|
||||
|
||||
function useProtectedReleaseManifestQuery(scope: string, userId: string | undefined, canDownload: boolean) {
|
||||
return useQuery({
|
||||
queryKey: ['release-manifest', scope, userId || 'anonymous', canDownload ? 'download' : 'nodownload'],
|
||||
queryFn: getReleaseManifest,
|
||||
retry: false,
|
||||
})
|
||||
}
|
||||
|
||||
function ReleaseAuthorityLinks({
|
||||
releaseManifest,
|
||||
includePublicNoticesLink = false,
|
||||
includeProtectedNoticesLink = false,
|
||||
}: {
|
||||
releaseManifest: ReleaseManifestView
|
||||
includePublicNoticesLink?: boolean
|
||||
includeProtectedNoticesLink?: boolean
|
||||
}) {
|
||||
const hasLinks = Boolean(
|
||||
releaseManifest.public_docs_url
|
||||
|| releaseManifest.release_notes_url
|
||||
|| releaseManifest.corresponding_source_url
|
||||
|| releaseManifest.open_source_repo_url
|
||||
|| includePublicNoticesLink
|
||||
|| includeProtectedNoticesLink,
|
||||
)
|
||||
|
||||
if (!hasLinks) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="button-row top-gap">
|
||||
{releaseManifest.public_docs_url ? (
|
||||
<a className="button button--ghost" href={releaseManifest.public_docs_url} target="_blank" rel="noreferrer">
|
||||
Public docs
|
||||
</a>
|
||||
) : null}
|
||||
{releaseManifest.release_notes_url ? (
|
||||
<a className="button button--ghost" href={releaseManifest.release_notes_url} target="_blank" rel="noreferrer">
|
||||
Release notes
|
||||
</a>
|
||||
) : null}
|
||||
{releaseManifest.corresponding_source_url ? (
|
||||
<a className="button button--ghost" href={releaseManifest.corresponding_source_url} target="_blank" rel="noreferrer">
|
||||
Corresponding source
|
||||
</a>
|
||||
) : null}
|
||||
{releaseManifest.open_source_repo_url ? (
|
||||
<a className="button button--ghost" href={releaseManifest.open_source_repo_url} target="_blank" rel="noreferrer">
|
||||
Repository/notices
|
||||
</a>
|
||||
) : null}
|
||||
{includeProtectedNoticesLink ? (
|
||||
<Link className="button button--ghost" to="/app/notices">
|
||||
Protected notices
|
||||
</Link>
|
||||
) : null}
|
||||
{includePublicNoticesLink ? (
|
||||
<Link className="button button--ghost" to="/open-source-notices">
|
||||
Public notices
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function DashboardOverviewPage() {
|
||||
const { user, superTokensConfigured } = usePlatformAuth()
|
||||
const healthQuery = useProtectedAuthHealthQuery('dashboard')
|
||||
const releaseManifestQuery = useProtectedReleaseManifestQuery('dashboard', user?.id, user?.canDownload === true)
|
||||
|
||||
const desktopLinkMutation = useMutation({
|
||||
mutationFn: createDesktopLinkToken,
|
||||
|
|
@ -281,11 +348,7 @@ export function DownloadCenterPage() {
|
|||
const [searchParams] = useSearchParams()
|
||||
const canDownload = user?.canDownload === true
|
||||
const requestedPlatform = normalizeDownloadPlatform(searchParams.get('platform'))
|
||||
const releaseManifestQuery = useQuery({
|
||||
queryKey: ['release-manifest', 'protected', user?.id || 'anonymous', canDownload ? 'download' : 'nodownload'],
|
||||
queryFn: getReleaseManifest,
|
||||
retry: false,
|
||||
})
|
||||
const releaseManifestQuery = useProtectedReleaseManifestQuery('protected', user?.id, canDownload)
|
||||
const releaseManifest = useMemo(
|
||||
() => resolveReleaseManifestView(
|
||||
releaseManifestQuery.data?.manifest,
|
||||
|
|
@ -425,6 +488,21 @@ export function DownloadCenterPage() {
|
|||
}
|
||||
|
||||
export function BrowserAccessPage() {
|
||||
const { user } = usePlatformAuth()
|
||||
const healthQuery = useProtectedAuthHealthQuery('browser-access')
|
||||
const releaseManifestQuery = useProtectedReleaseManifestQuery('browser-access', user?.id, user?.canDownload === true)
|
||||
const releaseManifest = useMemo(
|
||||
() => resolveReleaseManifestView(
|
||||
releaseManifestQuery.data?.manifest,
|
||||
buildProtectedReleaseManifestFallback(user?.email || 'hello@hypertwist.app'),
|
||||
),
|
||||
[releaseManifestQuery.data?.manifest, user?.email],
|
||||
)
|
||||
const configuredPlatformCount = useMemo(
|
||||
() => releaseManifest.platforms.filter((platform) => platform.configured).length,
|
||||
[releaseManifest.platforms],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
|
|
@ -435,18 +513,69 @@ export function BrowserAccessPage() {
|
|||
/>
|
||||
<div className="panel-grid">
|
||||
<Panel title="Current browser posture" kicker="Shipping lane">
|
||||
<ul className="list">
|
||||
<li>The embedded browser/CEF shell is live and owned as part of the current shipping posture.</li>
|
||||
<li>Browser runtime status, runtime-ready typing, and native operator diagnostics are already surfaced in the desktop runtime.</li>
|
||||
<li>The public browser dashboard is for operator/account/release access, not a claim of full simulator parity.</li>
|
||||
</ul>
|
||||
</Panel>
|
||||
<ul className="list">
|
||||
<li>The embedded browser/CEF shell is live and owned as part of the current shipping posture.</li>
|
||||
<li>Browser runtime status, runtime-ready typing, and native operator diagnostics are already surfaced in the desktop runtime.</li>
|
||||
<li>The public browser dashboard is for operator/account/release access, not a claim of full simulator parity.</li>
|
||||
</ul>
|
||||
{healthQuery.isLoading ? <p>Checking current browser-shell auth posture...</p> : null}
|
||||
{healthQuery.isError ? (
|
||||
<p className="form-error">
|
||||
Runtime auth-health could not be loaded. The page is still using the bounded dashboard posture rather than claiming browser-simulator parity.
|
||||
</p>
|
||||
) : null}
|
||||
{healthQuery.data ? (
|
||||
<ul className="list top-gap">
|
||||
<li>Auth runtime mode: {healthQuery.data.runtime.mode}</li>
|
||||
<li>Public auth origin ready: {healthQuery.data.runtime.public_origin_ready ? 'yes' : 'no'}</li>
|
||||
<li>Fallback currently active: {healthQuery.data.fallback.active ? 'yes' : 'no'}</li>
|
||||
<li>Session plan: {user?.plan || 'not resolved yet'}</li>
|
||||
<li>Desktop download entitlement: {user?.canDownload ? 'enabled' : 'not yet entitled'}</li>
|
||||
</ul>
|
||||
) : null}
|
||||
</Panel>
|
||||
<Panel title="Browser-to-desktop handoff" kicker="Operator workflow">
|
||||
<p>
|
||||
The browser shell exists to resolve account, release, and pairing state before the desktop runtime takes over the real simulator job.
|
||||
</p>
|
||||
<ul className="list top-gap">
|
||||
{desktopDownloadSteps.map((step) => (
|
||||
<li key={step}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="button-row top-gap">
|
||||
<Link className="button button--ghost" to="/app">
|
||||
Open dashboard
|
||||
</Link>
|
||||
<Link className="button button--ghost" to="/app/downloads">
|
||||
Open downloads
|
||||
</Link>
|
||||
</div>
|
||||
</Panel>
|
||||
<Panel title="Release authority around the browser shell" kicker="Distribution context">
|
||||
{releaseManifestQuery.isError ? (
|
||||
<p className="form-error">
|
||||
Live release-manifest lookup failed. Browser-shell release context is currently using fallback metadata instead of current auth-server authority.
|
||||
</p>
|
||||
) : null}
|
||||
<ul className="list">
|
||||
<li>Configured release targets: {configuredPlatformCount}/{releaseManifest.platforms.length}</li>
|
||||
<li>Manifest viewer access: {releaseManifest.viewer.accessStatus || 'not resolved yet'}</li>
|
||||
<li>Support contact: {releaseManifest.support_email}</li>
|
||||
</ul>
|
||||
<ReleaseAuthorityLinks releaseManifest={releaseManifest} includeProtectedNoticesLink />
|
||||
</Panel>
|
||||
<Panel title="Optional branches remain bounded" kicker="Not widened here">
|
||||
<ul className="list">
|
||||
<li>The optional full-browser client remains spec-only.</li>
|
||||
<li>MagicTile native renderer widening remains explicit No-Go until a real browser-host gap is proven.</li>
|
||||
<li>This dashboard intentionally stays above those boundaries instead of silently crossing them.</li>
|
||||
</ul>
|
||||
<ul className="list">
|
||||
<li>The optional full-browser client remains spec-only.</li>
|
||||
<li>MagicTile native renderer widening remains explicit No-Go until a real browser-host gap is proven.</li>
|
||||
<li>This dashboard intentionally stays above those boundaries instead of silently crossing them.</li>
|
||||
</ul>
|
||||
<ul className="list top-gap">
|
||||
{roadmapHonestyCards.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</Panel>
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -455,6 +584,18 @@ export function BrowserAccessPage() {
|
|||
|
||||
export function AccountPage() {
|
||||
const { user, superTokensConfigured } = usePlatformAuth()
|
||||
const releaseManifestQuery = useProtectedReleaseManifestQuery('account', user?.id, user?.canDownload === true)
|
||||
const releaseManifest = useMemo(
|
||||
() => resolveReleaseManifestView(
|
||||
releaseManifestQuery.data?.manifest,
|
||||
buildProtectedReleaseManifestFallback(user?.email || 'hello@hypertwist.app'),
|
||||
),
|
||||
[releaseManifestQuery.data?.manifest, user?.email],
|
||||
)
|
||||
const configuredPlatforms = useMemo(
|
||||
() => releaseManifest.platforms.filter((platform) => platform.configured),
|
||||
[releaseManifest.platforms],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -466,14 +607,63 @@ export function AccountPage() {
|
|||
/>
|
||||
<div className="panel-grid">
|
||||
<Panel title="Session profile" kicker="Account details">
|
||||
<ul className="list">
|
||||
<li>Email: {user?.email}</li>
|
||||
<li>Name: {user?.name}</li>
|
||||
<li>Plan: {user?.plan}</li>
|
||||
<li>Desktop downloads: {user?.canDownload ? 'enabled' : 'not yet entitled'}</li>
|
||||
<li>Billing status: {user?.billing?.accessStatus || 'session-default'}</li>
|
||||
<li>Auth stack: {superTokensConfigured ? 'SuperTokens-backed' : 'Local fallback mode'}</li>
|
||||
</ul>
|
||||
<ul className="list">
|
||||
<li>Email: {user?.email}</li>
|
||||
<li>Name: {user?.name}</li>
|
||||
<li>Plan: {user?.plan}</li>
|
||||
<li>Role: {user?.role || 'operator'}</li>
|
||||
<li>Desktop downloads: {user?.canDownload ? 'enabled' : 'not yet entitled'}</li>
|
||||
<li>Billing status: {user?.billing?.accessStatus || 'session-default'}</li>
|
||||
<li>Billing source: {user?.billing?.source || 'session'}</li>
|
||||
<li>Auth stack: {superTokensConfigured ? 'SuperTokens-backed' : 'Local fallback mode'}</li>
|
||||
</ul>
|
||||
</Panel>
|
||||
<Panel title="Entitlement and release access" kicker="Protected release lane">
|
||||
{releaseManifestQuery.isLoading ? <p>Refreshing release-access posture from the live manifest...</p> : null}
|
||||
{releaseManifestQuery.isError ? (
|
||||
<p className="form-error">
|
||||
Live release-manifest lookup failed. This account view is using bounded fallback metadata until auth-server release authority returns.
|
||||
</p>
|
||||
) : null}
|
||||
<ul className="list">
|
||||
<li>Manifest viewer authenticated: {releaseManifest.viewer.authenticated ? 'yes' : 'no'}</li>
|
||||
<li>Manifest viewer plan: {releaseManifest.viewer.plan || user?.plan || 'not resolved yet'}</li>
|
||||
<li>Manifest viewer access: {releaseManifest.viewer.accessStatus || 'not resolved yet'}</li>
|
||||
<li>Configured release targets: {configuredPlatforms.length}/{releaseManifest.platforms.length}</li>
|
||||
</ul>
|
||||
{configuredPlatforms.length > 0 ? (
|
||||
<ul className="list top-gap">
|
||||
{configuredPlatforms.map((platform) => {
|
||||
const metadataItems = buildReleaseMetadataItems(platform)
|
||||
const compactMetadata = metadataItems.slice(0, 2).map((item) => `${item.label} ${item.value}`).join(', ')
|
||||
return (
|
||||
<li key={platform.platform_key}>
|
||||
{platform.platform}: {platform.subtitle}{compactMetadata ? ` (${compactMetadata})` : ''}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
) : null}
|
||||
</Panel>
|
||||
<Panel title="Account follow-through" kicker="Next actions">
|
||||
<p>
|
||||
Use the dashboard when you need a desktop-link token, the download center when you need an entitled package, and the notices lane when you need distribution/legal references.
|
||||
</p>
|
||||
<div className="button-row top-gap">
|
||||
<Link className="button button--ghost" to="/app">
|
||||
Open dashboard
|
||||
</Link>
|
||||
<Link className="button button--ghost" to="/app/downloads">
|
||||
Open downloads
|
||||
</Link>
|
||||
<Link className="button button--ghost" to="/app/notices">
|
||||
Review notices
|
||||
</Link>
|
||||
<Link className="button button--ghost" to={buildSupportPath('operator-access')}>
|
||||
Get help
|
||||
</Link>
|
||||
</div>
|
||||
<ReleaseAuthorityLinks releaseManifest={releaseManifest} includeProtectedNoticesLink />
|
||||
</Panel>
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -482,11 +672,7 @@ export function AccountPage() {
|
|||
|
||||
export function NoticesPage() {
|
||||
const { user } = usePlatformAuth()
|
||||
const releaseManifestQuery = useQuery({
|
||||
queryKey: ['release-manifest', 'protected', user?.id || 'anonymous', user?.canDownload === true ? 'download' : 'nodownload'],
|
||||
queryFn: getReleaseManifest,
|
||||
retry: false,
|
||||
})
|
||||
const releaseManifestQuery = useProtectedReleaseManifestQuery('notices', user?.id, user?.canDownload === true)
|
||||
const releaseManifest = useMemo(
|
||||
() => resolveReleaseManifestView(
|
||||
releaseManifestQuery.data?.manifest,
|
||||
|
|
@ -494,6 +680,10 @@ export function NoticesPage() {
|
|||
),
|
||||
[releaseManifestQuery.data?.manifest, user?.email],
|
||||
)
|
||||
const configuredPlatforms = useMemo(
|
||||
() => releaseManifest.platforms.filter((platform) => platform.configured),
|
||||
[releaseManifest.platforms],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -505,12 +695,47 @@ export function NoticesPage() {
|
|||
/>
|
||||
<div className="panel-grid">
|
||||
<Panel title="Distribution notices" kicker="Legal posture">
|
||||
<p>
|
||||
Pricing, checkout, and download surfaces must expose open-source notices and corresponding-source guidance whenever shipped builds contain MPL-covered material.
|
||||
</p>
|
||||
<p>
|
||||
Current corresponding-source URL: {releaseManifest.corresponding_source_url || 'configure the corresponding-source URL before public launch'}
|
||||
</p>
|
||||
<p>
|
||||
Pricing, checkout, and download surfaces must expose open-source notices and corresponding-source guidance whenever shipped builds contain MPL-covered material.
|
||||
</p>
|
||||
{releaseManifestQuery.isLoading ? <p>Refreshing notices posture from the live release manifest...</p> : null}
|
||||
{releaseManifestQuery.isError ? (
|
||||
<p className="form-error">
|
||||
Live release-manifest lookup failed. This page is using bounded fallback notice metadata until auth-server release authority returns.
|
||||
</p>
|
||||
) : null}
|
||||
<ul className="list top-gap">
|
||||
<li>Configured release targets: {configuredPlatforms.length}/{releaseManifest.platforms.length}</li>
|
||||
<li>Viewer download access: {releaseManifest.viewer.canDownload ? 'enabled' : 'not yet enabled'}</li>
|
||||
<li>Public repository/notices URL: {releaseManifest.open_source_repo_url || 'configure the public repository/notices URL before launch'}</li>
|
||||
<li>Corresponding-source URL: {releaseManifest.corresponding_source_url || 'configure the corresponding-source URL before public launch'}</li>
|
||||
</ul>
|
||||
</Panel>
|
||||
<Panel title="Operator obligations" kicker="Keep aligned">
|
||||
<ul className="list">
|
||||
<li>Keep pricing, download, and notices surfaces aligned whenever a public downloadable build is offered.</li>
|
||||
<li>Keep release notes and package proof reachable so public audiences can see what build posture is actually being distributed.</li>
|
||||
<li>Do not present browser access as simulator parity just because distribution/legal pages are live.</li>
|
||||
</ul>
|
||||
{releaseManifest.platforms.length > 0 ? (
|
||||
<ul className="list top-gap">
|
||||
{releaseManifest.platforms.map((platform) => (
|
||||
<li key={platform.platform_key}>
|
||||
{platform.platform}: {platform.configured ? 'configured' : 'not configured'}{platform.validation_summary ? `, validation ${platform.validation_summary.result}` : ''}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
</Panel>
|
||||
<Panel title="Reference surfaces" kicker="Release references">
|
||||
<p>
|
||||
Operators should keep public notices, protected notices, release notes, and corresponding source reachable from the same release story.
|
||||
</p>
|
||||
<ReleaseAuthorityLinks
|
||||
releaseManifest={releaseManifest}
|
||||
includeProtectedNoticesLink
|
||||
includePublicNoticesLink
|
||||
/>
|
||||
</Panel>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue