diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md index a0e2bea..ad6ad7b 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md @@ -268,6 +268,12 @@ Latest same-day browser/distribution continuity follow-up still on `2026-06-24`: - operator access - studio rollout - each topic now carries explicit next steps, manual/route focus, and quick actions so the public web surface reflects the same browser/package/runtime/compliance separation the hardened routes already enforce +- those topic actions now also preserve exact protected destinations through auth: + - protected downloads + - protected notices + - protected browser-access + - protected dashboard +- the public topic cards therefore no longer dead-end on broad public guidance when the real next step is signed-in protected follow-through - the new shared callout posture explicitly separates: - what still works - what stays intentionally withheld diff --git a/website/src/__tests__/public-marketing-pages.test.tsx b/website/src/__tests__/public-marketing-pages.test.tsx index e332355..be5822c 100644 --- a/website/src/__tests__/public-marketing-pages.test.tsx +++ b/website/src/__tests__/public-marketing-pages.test.tsx @@ -884,8 +884,9 @@ describe('public marketing pages', () => { const selectedLaneSection = selectedLaneHeading.closest('section') expect(selectedLaneSection).toBeTruthy() expect(within(selectedLaneSection as HTMLElement).getByText('Confirm public launch status, operator/studio checkout posture, and support contact first.')).toBeTruthy() + expect(within(selectedLaneSection as HTMLElement).getByRole('link', { name: 'Sign in for protected release lane' }).getAttribute('href')).toBe('/login?next=%2Fapp%2Fdownloads%3Fplatform%3Dwindows') + expect(within(selectedLaneSection as HTMLElement).getByRole('link', { name: 'Sign in for protected notices' }).getAttribute('href')).toBe('/login?next=%2Fapp%2Fnotices') expect(within(selectedLaneSection as HTMLElement).getByRole('link', { name: 'Open pricing' }).getAttribute('href')).toBe('/pricing') - expect(within(selectedLaneSection as HTMLElement).getByRole('link', { name: 'Open download center' }).getAttribute('href')).toBe('/download') expect(screen.getByText('If the desktop app is primary, why keep the web version?')).toBeTruthy() expect(screen.getByText('Is VR/controller support already fully finished?')).toBeTruthy() expect(screen.getByText('Do higher-dimensional selector choices persist between sessions?')).toBeTruthy() diff --git a/website/src/__tests__/site-routes.test.ts b/website/src/__tests__/site-routes.test.ts index b69cb00..35590f2 100644 --- a/website/src/__tests__/site-routes.test.ts +++ b/website/src/__tests__/site-routes.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from 'vitest' import { + buildLoginPath, buildProtectedDownloadPath, + buildRegisterPath, buildSupportPath, isExternalHref, normalizeDownloadPlatform, @@ -25,4 +27,10 @@ describe('site route helpers', () => { expect(isExternalHref('https://checkout.paddle.com')).toBe(true) expect(isExternalHref('/support?topic=operator-access')).toBe(false) }) + + it('builds auth-preserving login and register links for protected destinations', () => { + expect(buildLoginPath('/app/downloads?platform=windows')).toBe('/login?next=%2Fapp%2Fdownloads%3Fplatform%3Dwindows') + expect(buildRegisterPath('/app/notices')).toBe('/register?next=%2Fapp%2Fnotices') + expect(buildLoginPath('//evil.example')).toBe('/login?next=%2Fapp') + }) }) diff --git a/website/src/pages/public-page-helpers.tsx b/website/src/pages/public-page-helpers.tsx index 04e12d9..ccd2873 100644 --- a/website/src/pages/public-page-helpers.tsx +++ b/website/src/pages/public-page-helpers.tsx @@ -16,7 +16,12 @@ import { releaseNotesUrl, } from '../site-config' import { deliverySurfaceCards } from '../site-data' -import { isExternalHref } from '../site-routes' +import { + buildLoginPath, + buildProtectedDownloadPath, + buildRegisterPath, + isExternalHref, +} from '../site-routes' export type SupportTopicGuide = { title: string @@ -45,9 +50,9 @@ export const supportTopicGuidance: Record = { 'The public manual explains the browser-versus-desktop product split that launch copy must preserve.', ], actions: [ + { 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' }, - { label: 'Open download center', to: '/download' }, - { label: 'Review notices', to: '/open-source-notices' }, ], }, 'operator-access': { @@ -65,9 +70,9 @@ export const supportTopicGuidance: Record = { 'The public manual explains when the browser dashboard owns the next step and when the native runtime takes over.', ], actions: [ - { label: 'Open sign-in', to: '/login' }, - { label: 'Open download posture', to: '/download' }, - { label: 'Open public manual', to: '/docs' }, + { label: 'Sign in for protected downloads', to: buildLoginPath(buildProtectedDownloadPath('windows')) }, + { label: 'Create account for desktop access', to: buildRegisterPath(buildProtectedDownloadPath('windows')) }, + { label: 'Open protected dashboard', to: buildLoginPath('/app') }, ], }, 'studio-rollout': { @@ -85,9 +90,10 @@ export const supportTopicGuidance: Record = { 'Resources summarize higher-dimensional runtime lanes and operator playbooks for wider team review.', ], actions: [ + { label: 'Sign in for protected notices', to: buildLoginPath('/app/notices') }, + { label: 'Sign in for protected browser access', to: buildLoginPath('/app/browser-access') }, { label: 'Open release notes', to: '/changelog' }, { label: 'Open resources', to: '/resources' }, - { label: 'Review notices', to: '/open-source-notices' }, ], }, } diff --git a/website/src/site-data.ts b/website/src/site-data.ts index 76d4671..0c34028 100644 --- a/website/src/site-data.ts +++ b/website/src/site-data.ts @@ -762,6 +762,11 @@ export const resourceCollections = [ ] as const export const changelogEntries = [ + { + date: 'June 24, 2026', + title: 'Public support topics now preserve exact protected destinations through auth', + details: 'Support-topic quick routes no longer stop at generic public guidance. Launch-readiness, operator-access, and studio-rollout actions now deep-link into the correct protected dashboard, download, browser-access, or notices destination through auth-preserving next-target links.', + }, { date: 'June 24, 2026', title: 'Support-topic routing now spans support, docs, and resources with the same authority split', diff --git a/website/src/site-routes.ts b/website/src/site-routes.ts index ecce67b..fb21553 100644 --- a/website/src/site-routes.ts +++ b/website/src/site-routes.ts @@ -1,3 +1,5 @@ +import { normalizeNextPath } from './auth/next-path' + export type DownloadPlatformKey = 'windows' | 'macos' | 'linux' const DOWNLOAD_PLATFORM_LABELS: Record = { @@ -43,6 +45,19 @@ export function buildSupportPath(topic?: string): string { return `/support?${params.toString()}` } +function buildAuthPath(mode: 'login' | 'register', nextPath: string | null | undefined, fallback = '/app') { + const normalizedNextPath = normalizeNextPath(nextPath, fallback) + return `/${mode}?next=${encodeURIComponent(normalizedNextPath)}` +} + +export function buildLoginPath(nextPath?: string | null, fallback?: string): string { + return buildAuthPath('login', nextPath, fallback) +} + +export function buildRegisterPath(nextPath?: string | null, fallback?: string): string { + return buildAuthPath('register', nextPath, fallback) +} + export function isExternalHref(value: string): boolean { return /^[a-z][a-z0-9+.-]*:/i.test(value) }