diff --git a/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md b/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md index b70ad1f..ccd074f 100644 --- a/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md +++ b/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md @@ -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 diff --git a/docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md b/docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md index b643fda..cc71db0 100644 --- a/docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md +++ b/docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md @@ -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 diff --git a/website/README.md b/website/README.md index 2abe535..cb97ac7 100644 --- a/website/README.md +++ b/website/README.md @@ -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 diff --git a/website/src/__tests__/app-route-tree.test.tsx b/website/src/__tests__/app-route-tree.test.tsx index f322db7..12efaac 100644 --- a/website/src/__tests__/app-route-tree.test.tsx +++ b/website/src/__tests__/app-route-tree.test.tsx @@ -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() + }) }) diff --git a/website/src/__tests__/protected-app-pages.test.tsx b/website/src/__tests__/protected-app-pages.test.tsx new file mode 100644 index 0000000..aeee875 --- /dev/null +++ b/website/src/__tests__/protected-app-pages.test.tsx @@ -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( + + + {page} + + , + ) +} + +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(, '/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(, '/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(, '/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') + }) +}) diff --git a/website/src/pages/app-pages.tsx b/website/src/pages/app-pages.tsx index f0cdee2..5078aee 100644 --- a/website/src/pages/app-pages.tsx +++ b/website/src/pages/app-pages.tsx @@ -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 ( +
+ {releaseManifest.public_docs_url ? ( + + Public docs + + ) : null} + {releaseManifest.release_notes_url ? ( + + Release notes + + ) : null} + {releaseManifest.corresponding_source_url ? ( + + Corresponding source + + ) : null} + {releaseManifest.open_source_repo_url ? ( + + Repository/notices + + ) : null} + {includeProtectedNoticesLink ? ( + + Protected notices + + ) : null} + {includePublicNoticesLink ? ( + + Public notices + + ) : null} +
+ ) +} + +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 ( <>
-
    -
  • The embedded browser/CEF shell is live and owned as part of the current shipping posture.
  • -
  • Browser runtime status, runtime-ready typing, and native operator diagnostics are already surfaced in the desktop runtime.
  • -
  • The public browser dashboard is for operator/account/release access, not a claim of full simulator parity.
  • -
-
+
    +
  • The embedded browser/CEF shell is live and owned as part of the current shipping posture.
  • +
  • Browser runtime status, runtime-ready typing, and native operator diagnostics are already surfaced in the desktop runtime.
  • +
  • The public browser dashboard is for operator/account/release access, not a claim of full simulator parity.
  • +
+ {healthQuery.isLoading ?

Checking current browser-shell auth posture...

: null} + {healthQuery.isError ? ( +

+ Runtime auth-health could not be loaded. The page is still using the bounded dashboard posture rather than claiming browser-simulator parity. +

+ ) : null} + {healthQuery.data ? ( +
    +
  • Auth runtime mode: {healthQuery.data.runtime.mode}
  • +
  • Public auth origin ready: {healthQuery.data.runtime.public_origin_ready ? 'yes' : 'no'}
  • +
  • Fallback currently active: {healthQuery.data.fallback.active ? 'yes' : 'no'}
  • +
  • Session plan: {user?.plan || 'not resolved yet'}
  • +
  • Desktop download entitlement: {user?.canDownload ? 'enabled' : 'not yet entitled'}
  • +
+ ) : null} + + +

+ The browser shell exists to resolve account, release, and pairing state before the desktop runtime takes over the real simulator job. +

+
    + {desktopDownloadSteps.map((step) => ( +
  • {step}
  • + ))} +
+
+ + Open dashboard + + + Open downloads + +
+
+ + {releaseManifestQuery.isError ? ( +

+ Live release-manifest lookup failed. Browser-shell release context is currently using fallback metadata instead of current auth-server authority. +

+ ) : null} +
    +
  • Configured release targets: {configuredPlatformCount}/{releaseManifest.platforms.length}
  • +
  • Manifest viewer access: {releaseManifest.viewer.accessStatus || 'not resolved yet'}
  • +
  • Support contact: {releaseManifest.support_email}
  • +
+ +
-
    -
  • The optional full-browser client remains spec-only.
  • -
  • MagicTile native renderer widening remains explicit No-Go until a real browser-host gap is proven.
  • -
  • This dashboard intentionally stays above those boundaries instead of silently crossing them.
  • -
+
    +
  • The optional full-browser client remains spec-only.
  • +
  • MagicTile native renderer widening remains explicit No-Go until a real browser-host gap is proven.
  • +
  • This dashboard intentionally stays above those boundaries instead of silently crossing them.
  • +
+
    + {roadmapHonestyCards.map((item) => ( +
  • {item}
  • + ))} +
@@ -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() { />
-
    -
  • Email: {user?.email}
  • -
  • Name: {user?.name}
  • -
  • Plan: {user?.plan}
  • -
  • Desktop downloads: {user?.canDownload ? 'enabled' : 'not yet entitled'}
  • -
  • Billing status: {user?.billing?.accessStatus || 'session-default'}
  • -
  • Auth stack: {superTokensConfigured ? 'SuperTokens-backed' : 'Local fallback mode'}
  • -
+
    +
  • Email: {user?.email}
  • +
  • Name: {user?.name}
  • +
  • Plan: {user?.plan}
  • +
  • Role: {user?.role || 'operator'}
  • +
  • Desktop downloads: {user?.canDownload ? 'enabled' : 'not yet entitled'}
  • +
  • Billing status: {user?.billing?.accessStatus || 'session-default'}
  • +
  • Billing source: {user?.billing?.source || 'session'}
  • +
  • Auth stack: {superTokensConfigured ? 'SuperTokens-backed' : 'Local fallback mode'}
  • +
+
+ + {releaseManifestQuery.isLoading ?

Refreshing release-access posture from the live manifest...

: null} + {releaseManifestQuery.isError ? ( +

+ Live release-manifest lookup failed. This account view is using bounded fallback metadata until auth-server release authority returns. +

+ ) : null} +
    +
  • Manifest viewer authenticated: {releaseManifest.viewer.authenticated ? 'yes' : 'no'}
  • +
  • Manifest viewer plan: {releaseManifest.viewer.plan || user?.plan || 'not resolved yet'}
  • +
  • Manifest viewer access: {releaseManifest.viewer.accessStatus || 'not resolved yet'}
  • +
  • Configured release targets: {configuredPlatforms.length}/{releaseManifest.platforms.length}
  • +
+ {configuredPlatforms.length > 0 ? ( +
    + {configuredPlatforms.map((platform) => { + const metadataItems = buildReleaseMetadataItems(platform) + const compactMetadata = metadataItems.slice(0, 2).map((item) => `${item.label} ${item.value}`).join(', ') + return ( +
  • + {platform.platform}: {platform.subtitle}{compactMetadata ? ` (${compactMetadata})` : ''} +
  • + ) + })} +
+ ) : null} +
+ +

+ 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. +

+
+ + Open dashboard + + + Open downloads + + + Review notices + + + Get help + +
+
@@ -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() { />
-

- Pricing, checkout, and download surfaces must expose open-source notices and corresponding-source guidance whenever shipped builds contain MPL-covered material. -

-

- Current corresponding-source URL: {releaseManifest.corresponding_source_url || 'configure the corresponding-source URL before public launch'} -

+

+ Pricing, checkout, and download surfaces must expose open-source notices and corresponding-source guidance whenever shipped builds contain MPL-covered material. +

+ {releaseManifestQuery.isLoading ?

Refreshing notices posture from the live release manifest...

: null} + {releaseManifestQuery.isError ? ( +

+ Live release-manifest lookup failed. This page is using bounded fallback notice metadata until auth-server release authority returns. +

+ ) : null} +
    +
  • Configured release targets: {configuredPlatforms.length}/{releaseManifest.platforms.length}
  • +
  • Viewer download access: {releaseManifest.viewer.canDownload ? 'enabled' : 'not yet enabled'}
  • +
  • Public repository/notices URL: {releaseManifest.open_source_repo_url || 'configure the public repository/notices URL before launch'}
  • +
  • Corresponding-source URL: {releaseManifest.corresponding_source_url || 'configure the corresponding-source URL before public launch'}
  • +
+
+ +
    +
  • Keep pricing, download, and notices surfaces aligned whenever a public downloadable build is offered.
  • +
  • Keep release notes and package proof reachable so public audiences can see what build posture is actually being distributed.
  • +
  • Do not present browser access as simulator parity just because distribution/legal pages are live.
  • +
+ {releaseManifest.platforms.length > 0 ? ( +
    + {releaseManifest.platforms.map((platform) => ( +
  • + {platform.platform}: {platform.configured ? 'configured' : 'not configured'}{platform.validation_summary ? `, validation ${platform.validation_summary.result}` : ''} +
  • + ))} +
+ ) : null} +
+ +

+ Operators should keep public notices, protected notices, release notes, and corresponding source reachable from the same release story. +

+