diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index 1766877..e0e68f1 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -1330,6 +1330,38 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlSettingsOwnershipInspectSurfa Magic120CellViewContextSurface.PrimaryViewProfileContractId, Magic120CellViewProfileContract ) && Magic120CellViewProfileContract.IsStructurallyValid(); + Surface.Magic120CellProjectionSurfaceTagCount = + Magic120CellViewProfileContract.ProjectionSurfaceTags.Num(); + Surface.Magic120CellSymmetryPresetTagCount = + Magic120CellViewProfileContract.SymmetryPresetTags.Num(); + Surface.Magic120CellVisibilityPresetTagCount = + Magic120CellViewProfileContract.VisibilityPresetTags.Num(); + Surface.Magic120CellFocusSurfaceTagCount = + Magic120CellViewProfileContract.FocusSurfaceTags.Num(); + FHyperTwistTrainingHigherDimensionalRuntimeSessionSurface Magic120CellSessionSurface; + Surface.bMagic120CellPersistenceSettingsReady = + UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalRuntimeSessionSurfaceByActivationProfileId( + TEXT("magic120cell-cleanroom-runtime-activation"), + Magic120CellSessionSurface + ) && Magic120CellSessionSurface.IsStructurallyValid(); + if (Surface.bMagic120CellPersistenceSettingsReady) + { + Surface.Magic120CellSessionSurfaceId = Magic120CellSessionSurface.SessionSurfaceId; + Surface.Magic120CellPersistenceBoundaryId = + Magic120CellSessionSurface.PrimaryPersistenceBoundaryId; + } + FHyperTwistTrainingHigherDimensionalInteractiveSceneSurface Magic120CellSceneSurface; + Surface.bMagic120CellPersistenceSettingsReady = + Surface.bMagic120CellPersistenceSettingsReady + && UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalInteractiveSceneSurfaceByActivationProfileId( + TEXT("magic120cell-cleanroom-runtime-activation"), + Magic120CellSceneSurface + ) && Magic120CellSceneSurface.IsStructurallyValid(); + if (Surface.bMagic120CellPersistenceSettingsReady) + { + Surface.Magic120CellSceneSurfaceId = Magic120CellSceneSurface.SceneSurfaceId; + Surface.Magic120CellStateSemanticsId = Magic120CellSceneSurface.StateSemanticsId; + } FHyperTwistTrainingHigherDimensionalRuntimeViewContextSurface MagicCube5DViewContextSurface; const bool bMagicCube5DViewContextReady = @@ -1347,16 +1379,64 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlSettingsOwnershipInspectSurfa MagicCube5DViewContextSurface.PrimaryViewProfileContractId, MagicCube5DViewProfileContract ) && MagicCube5DViewProfileContract.IsStructurallyValid(); + Surface.MagicCube5DProjectionSurfaceTagCount = + MagicCube5DViewProfileContract.ProjectionSurfaceTags.Num(); + Surface.MagicCube5DStereoSurfaceTagCount = + MagicCube5DViewProfileContract.StereoSurfaceTags.Num(); + Surface.MagicCube5DVisibilitySurfaceTagCount = + MagicCube5DViewProfileContract.VisibilitySurfaceTags.Num(); + Surface.MagicCube5DFocusSurfaceTagCount = + MagicCube5DViewProfileContract.FocusSurfaceTags.Num(); + FHyperTwistTrainingHigherDimensionalRuntimeSessionSurface MagicCube5DSessionSurface; + Surface.bMagicCube5DPersistenceSettingsReady = + UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalRuntimeSessionSurfaceByActivationProfileId( + TEXT("magiccube5d-cleanroom-runtime-activation"), + MagicCube5DSessionSurface + ) && MagicCube5DSessionSurface.IsStructurallyValid(); + if (Surface.bMagicCube5DPersistenceSettingsReady) + { + Surface.MagicCube5DSessionSurfaceId = MagicCube5DSessionSurface.SessionSurfaceId; + Surface.MagicCube5DPersistenceBoundaryId = + MagicCube5DSessionSurface.PrimaryPersistenceBoundaryId; + } + FHyperTwistTrainingHigherDimensionalInteractiveSceneSurface MagicCube5DSceneSurface; + Surface.bMagicCube5DPersistenceSettingsReady = + Surface.bMagicCube5DPersistenceSettingsReady + && UHyperTwistTrainingRuntimeLibrary::TryGetBundledHigherDimensionalInteractiveSceneSurfaceByActivationProfileId( + TEXT("magiccube5d-cleanroom-runtime-activation"), + MagicCube5DSceneSurface + ) && MagicCube5DSceneSurface.IsStructurallyValid(); + if (Surface.bMagicCube5DPersistenceSettingsReady) + { + Surface.MagicCube5DSceneSurfaceId = MagicCube5DSceneSurface.SceneSurfaceId; + Surface.MagicCube5DStateSemanticsId = MagicCube5DSceneSurface.StateSemanticsId; + } const FHyperTwistTrainingHigherDimensionalRuntimeViewContextSurface ActiveViewContextSurface = HasDisplayedHigherDimensionalRuntimeViewContextSurface() ? GetDisplayedHigherDimensionalRuntimeViewContextSurface() : FHyperTwistTrainingHigherDimensionalRuntimeViewContextSurface(); + const FHyperTwistTrainingHigherDimensionalRuntimeSessionSurface ActiveSessionSurface = + HasDisplayedHigherDimensionalRuntimeSessionSurface() + ? GetDisplayedHigherDimensionalRuntimeSessionSurface() + : FHyperTwistTrainingHigherDimensionalRuntimeSessionSurface(); + const FHyperTwistTrainingHigherDimensionalInteractiveSceneSurface ActiveSceneSurface = + HasDisplayedHigherDimensionalInteractiveSceneSurface() + ? GetDisplayedHigherDimensionalInteractiveSceneSurface() + : FHyperTwistTrainingHigherDimensionalInteractiveSceneSurface(); Surface.bActiveHigherDimensionalSettingsVisible = ActiveViewContextSurface.IsStructurallyValid(); Surface.ActiveHigherDimensionalViewContextId = Surface.bActiveHigherDimensionalSettingsVisible ? ActiveViewContextSurface.ViewContextSurfaceId : TEXT("none"); + Surface.ActiveHigherDimensionalSessionSurfaceId = + ActiveSessionSurface.IsStructurallyValid() + ? ActiveSessionSurface.SessionSurfaceId + : TEXT("none"); + Surface.ActiveHigherDimensionalInteractiveSceneSurfaceId = + ActiveSceneSurface.IsStructurallyValid() + ? ActiveSceneSurface.SceneSurfaceId + : TEXT("none"); Surface.ActiveHigherDimensionalSelectorCount = Surface.bActiveHigherDimensionalSettingsVisible ? ActiveViewContextSurface.Selectors.Num() @@ -1397,17 +1477,63 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlSettingsOwnershipInspectSurfa !Surface.ActiveHigherDimensionalViewContextId.IsEmpty() ? Surface.ActiveHigherDimensionalViewContextId : TEXT("none"); + const FString ActiveSessionSurfaceId = + !Surface.ActiveHigherDimensionalSessionSurfaceId.IsEmpty() + ? Surface.ActiveHigherDimensionalSessionSurfaceId + : TEXT("none"); + const FString ActiveSceneSurfaceId = + !Surface.ActiveHigherDimensionalInteractiveSceneSurfaceId.IsEmpty() + ? Surface.ActiveHigherDimensionalInteractiveSceneSurfaceId + : TEXT("none"); const FString Magic120CellProfileId = !Surface.Magic120CellViewProfileContractId.IsEmpty() ? Surface.Magic120CellViewProfileContractId : TEXT("n/a"); + const FString Magic120CellSessionSurfaceId = + !Surface.Magic120CellSessionSurfaceId.IsEmpty() + ? Surface.Magic120CellSessionSurfaceId + : TEXT("n/a"); + const FString Magic120CellSceneSurfaceId = + !Surface.Magic120CellSceneSurfaceId.IsEmpty() + ? Surface.Magic120CellSceneSurfaceId + : TEXT("n/a"); + const FString Magic120CellPersistenceBoundaryId = + !Surface.Magic120CellPersistenceBoundaryId.IsEmpty() + ? Surface.Magic120CellPersistenceBoundaryId + : TEXT("n/a"); + const FString Magic120CellStateSemanticsId = + !Surface.Magic120CellStateSemanticsId.IsEmpty() + ? Surface.Magic120CellStateSemanticsId + : TEXT("n/a"); const FString MagicCube5DProfileId = !Surface.MagicCube5DViewProfileContractId.IsEmpty() ? Surface.MagicCube5DViewProfileContractId : TEXT("n/a"); + const FString MagicCube5DSessionSurfaceId = + !Surface.MagicCube5DSessionSurfaceId.IsEmpty() + ? Surface.MagicCube5DSessionSurfaceId + : TEXT("n/a"); + const FString MagicCube5DSceneSurfaceId = + !Surface.MagicCube5DSceneSurfaceId.IsEmpty() + ? Surface.MagicCube5DSceneSurfaceId + : TEXT("n/a"); + const FString MagicCube5DPersistenceBoundaryId = + !Surface.MagicCube5DPersistenceBoundaryId.IsEmpty() + ? Surface.MagicCube5DPersistenceBoundaryId + : TEXT("n/a"); + const FString MagicCube5DStateSemanticsId = + !Surface.MagicCube5DStateSemanticsId.IsEmpty() + ? Surface.MagicCube5DStateSemanticsId + : TEXT("n/a"); + const bool bMagic120CellOwnedSettingsReady = + Surface.bMagic120CellViewSettingsReady + && Surface.bMagic120CellPersistenceSettingsReady; + const bool bMagicCube5DOwnedSettingsReady = + Surface.bMagicCube5DViewSettingsReady + && Surface.bMagicCube5DPersistenceSettingsReady; Surface.SummaryLine = - TEXT("Camera, immersive, and dedicated-family view settings are already first-party owned, while XR/controller rebinding still remains unfinished."); + TEXT("Camera, immersive, and dedicated-family projection, selector, and persistence settings are already first-party owned, while XR/controller rebinding still remains unfinished."); Surface.CameraSettingsLine = FString::Printf( TEXT("Viewer camera settings: %s | tool %s | orbit/framing/export surface is owned."), *HyperTwistTrainingPanelWidgetInternal::DescribeReadyState( @@ -1422,24 +1548,42 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlSettingsOwnershipInspectSurfa ImmersivePresenceContract.ReducedDistractionPresetIds.Num() ); Surface.Magic120CellSettingsLine = FString::Printf( - TEXT("Magic120Cell view settings: %s | profile %s | selectors %d."), + TEXT("Magic120Cell owned settings: %s | profile %s | session %s | scene %s | persistence %s | semantics %s | selectors %d | projection tags %d | symmetry presets %d | visibility presets %d | focus surfaces %d."), *HyperTwistTrainingPanelWidgetInternal::DescribeReadyState( - Surface.bMagic120CellViewSettingsReady), + bMagic120CellOwnedSettingsReady), *Magic120CellProfileId, - Surface.Magic120CellSelectorCount + *Magic120CellSessionSurfaceId, + *Magic120CellSceneSurfaceId, + *Magic120CellPersistenceBoundaryId, + *Magic120CellStateSemanticsId, + Surface.Magic120CellSelectorCount, + Surface.Magic120CellProjectionSurfaceTagCount, + Surface.Magic120CellSymmetryPresetTagCount, + Surface.Magic120CellVisibilityPresetTagCount, + Surface.Magic120CellFocusSurfaceTagCount ); Surface.MagicCube5DSettingsLine = FString::Printf( - TEXT("MagicCube5D view settings: %s | profile %s | selectors %d."), + TEXT("MagicCube5D owned settings: %s | profile %s | session %s | scene %s | persistence %s | semantics %s | selectors %d | projection tags %d | stereo tags %d | visibility tags %d | focus surfaces %d."), *HyperTwistTrainingPanelWidgetInternal::DescribeReadyState( - Surface.bMagicCube5DViewSettingsReady), + bMagicCube5DOwnedSettingsReady), *MagicCube5DProfileId, - Surface.MagicCube5DSelectorCount + *MagicCube5DSessionSurfaceId, + *MagicCube5DSceneSurfaceId, + *MagicCube5DPersistenceBoundaryId, + *MagicCube5DStateSemanticsId, + Surface.MagicCube5DSelectorCount, + Surface.MagicCube5DProjectionSurfaceTagCount, + Surface.MagicCube5DStereoSurfaceTagCount, + Surface.MagicCube5DVisibilitySurfaceTagCount, + Surface.MagicCube5DFocusSurfaceTagCount ); Surface.SelectorSettingsLine = FString::Printf( - TEXT("Higher-dimensional selector ownership: %s | active context %s | active selectors %d."), + TEXT("Higher-dimensional selector ownership: %s | active context %s | active session %s | active scene %s | active selectors %d."), *HyperTwistTrainingPanelWidgetInternal::DescribeReadyState( Surface.bHigherDimensionalSelectorSettingsReady), *ActiveViewContextId, + *ActiveSessionSurfaceId, + *ActiveSceneSurfaceId, Surface.ActiveHigherDimensionalSelectorCount ); Surface.SelectorRecallLine = @@ -1451,11 +1595,11 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlSettingsOwnershipInspectSurfa Surface.bOpenXrProjectPluginEnabled ); Surface.StatusLine = FString::Printf( - TEXT("camera %s | immersive %s | 120-cell %s | 5D %s | selectors %s | selector recall %s | XR rebinding not yet shipped"), + TEXT("camera %s | immersive %s | 120-cell owned settings %s | 5D owned settings %s | selectors %s | selector recall %s | XR rebinding not yet shipped"), Surface.bCameraSettingsReady ? TEXT("ready") : TEXT("missing"), Surface.bImmersivePresenceSettingsReady ? TEXT("ready") : TEXT("missing"), - Surface.bMagic120CellViewSettingsReady ? TEXT("ready") : TEXT("missing"), - Surface.bMagicCube5DViewSettingsReady ? TEXT("ready") : TEXT("missing"), + bMagic120CellOwnedSettingsReady ? TEXT("ready") : TEXT("partial"), + bMagicCube5DOwnedSettingsReady ? TEXT("ready") : TEXT("partial"), Surface.bHigherDimensionalSelectorSettingsReady ? TEXT("ready") : TEXT("partial"), Surface.bRepositoryBackedSelectorRecallReady ? TEXT("ready") : TEXT("unavailable") ); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index b5d8eb2..871fccf 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -10140,18 +10140,72 @@ struct FHyperTwistTrainingControlSettingsOwnershipInspectSurface UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString ActiveHigherDimensionalViewContextId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveHigherDimensionalSessionSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveHigherDimensionalInteractiveSceneSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Magic120CellSessionSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Magic120CellSceneSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Magic120CellPersistenceBoundaryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Magic120CellStateSemanticsId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString Magic120CellViewProfileContractId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString MagicCube5DSessionSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString MagicCube5DSceneSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString MagicCube5DPersistenceBoundaryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString MagicCube5DStateSemanticsId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString MagicCube5DViewProfileContractId; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 Magic120CellSelectorCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 Magic120CellProjectionSurfaceTagCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 Magic120CellSymmetryPresetTagCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 Magic120CellVisibilityPresetTagCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 Magic120CellFocusSurfaceTagCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 MagicCube5DSelectorCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 MagicCube5DProjectionSurfaceTagCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 MagicCube5DStereoSurfaceTagCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 MagicCube5DVisibilitySurfaceTagCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 MagicCube5DFocusSurfaceTagCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 ActiveHigherDimensionalSelectorCount = 0; @@ -10167,9 +10221,15 @@ struct FHyperTwistTrainingControlSettingsOwnershipInspectSurface UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") bool bMagic120CellViewSettingsReady = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bMagic120CellPersistenceSettingsReady = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") bool bMagicCube5DViewSettingsReady = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bMagicCube5DPersistenceSettingsReady = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") bool bHigherDimensionalSelectorSettingsReady = false; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp index dbc707a..bf1e4b8 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp @@ -1320,16 +1320,95 @@ bool FHyperTwistTrainingPanelControlSettingsOwnershipInspectSurfaceTest::RunTest Surface.CameraSettingsToolId, TEXT("tool/camera-settings") ); + TestEqual( + TEXT("The control settings ownership surface must keep the default active higher-dimensional view context explicit when no live session is attached."), + Surface.ActiveHigherDimensionalViewContextId, + TEXT("none") + ); + TestEqual( + TEXT("The control settings ownership surface must keep the default active higher-dimensional session surface explicit when no live session is attached."), + Surface.ActiveHigherDimensionalSessionSurfaceId, + TEXT("none") + ); + TestEqual( + TEXT("The control settings ownership surface must keep the default active higher-dimensional interactive scene explicit when no live session is attached."), + Surface.ActiveHigherDimensionalInteractiveSceneSurfaceId, + TEXT("none") + ); TestEqual( TEXT("The control settings ownership surface must expose the shipped Magic120Cell view profile contract id."), Surface.Magic120CellViewProfileContractId, TEXT("magic120cell-focus-view-profile") ); + TestTrue( + TEXT("The control settings ownership surface must expose shipped Magic120Cell persistence ownership as real structured state."), + Surface.bMagic120CellPersistenceSettingsReady + && Surface.Magic120CellSessionSurfaceId + == TEXT("phase6c/magic120cell/dedicated-training-session-surface") + && Surface.Magic120CellSceneSurfaceId + == TEXT("phase6c/magic120cell/interactive-scene-surface") + && Surface.Magic120CellPersistenceBoundaryId + == TEXT("magic120cell-persistence-boundary") + && Surface.Magic120CellStateSemanticsId + == TEXT("phase6c/magic120cell/family-owned-scene-state") + ); + TestEqual( + TEXT("The control settings ownership surface must expose the current Magic120Cell projection-tag count."), + Surface.Magic120CellProjectionSurfaceTagCount, + 5 + ); + TestEqual( + TEXT("The control settings ownership surface must expose the current Magic120Cell symmetry-preset count."), + Surface.Magic120CellSymmetryPresetTagCount, + 5 + ); + TestEqual( + TEXT("The control settings ownership surface must expose the current Magic120Cell visibility-preset count."), + Surface.Magic120CellVisibilityPresetTagCount, + 5 + ); + TestEqual( + TEXT("The control settings ownership surface must expose the current Magic120Cell focus-surface count."), + Surface.Magic120CellFocusSurfaceTagCount, + 5 + ); TestEqual( TEXT("The control settings ownership surface must expose the shipped MagicCube5D view profile contract id."), Surface.MagicCube5DViewProfileContractId, TEXT("magiccube5d-projection-view-profile") ); + TestTrue( + TEXT("The control settings ownership surface must expose shipped MagicCube5D persistence ownership as real structured state."), + Surface.bMagicCube5DPersistenceSettingsReady + && Surface.MagicCube5DSessionSurfaceId + == TEXT("phase6c/magiccube5d/dedicated-training-session-surface") + && Surface.MagicCube5DSceneSurfaceId + == TEXT("phase6c/magiccube5d/interactive-scene-surface") + && Surface.MagicCube5DPersistenceBoundaryId + == TEXT("magiccube5d-persistence-boundary") + && Surface.MagicCube5DStateSemanticsId + == TEXT("phase6c/magiccube5d/family-owned-scene-state") + ); + TestEqual( + TEXT("The control settings ownership surface must expose the current MagicCube5D projection-tag count."), + Surface.MagicCube5DProjectionSurfaceTagCount, + 5 + ); + TestEqual( + TEXT("The control settings ownership surface must expose the current MagicCube5D stereo-tag count."), + Surface.MagicCube5DStereoSurfaceTagCount, + 5 + ); + TestEqual( + TEXT("The control settings ownership surface must expose the current MagicCube5D visibility-tag count."), + Surface.MagicCube5DVisibilitySurfaceTagCount, + 5 + ); + TestEqual( + TEXT("The control settings ownership surface must expose the current MagicCube5D focus-surface count."), + Surface.MagicCube5DFocusSurfaceTagCount, + 5 + ); TestEqual( TEXT("The control settings ownership surface must expose the current Magic120Cell selector count."), Surface.Magic120CellSelectorCount, @@ -1353,6 +1432,17 @@ bool FHyperTwistTrainingPanelControlSettingsOwnershipInspectSurfaceTest::RunTest TEXT("The control settings ownership surface must retain the camera settings tool reference in its detail line."), Surface.DetailLine.Contains(TEXT("tool/camera-settings")) ); + TestTrue( + TEXT("The control settings ownership surface detail line must now keep dedicated family session, scene, persistence, and state-semantics ids visible."), + Surface.DetailLine.Contains(TEXT("phase6c/magic120cell/dedicated-training-session-surface")) + && Surface.DetailLine.Contains(TEXT("phase6c/magic120cell/interactive-scene-surface")) + && Surface.DetailLine.Contains(TEXT("magic120cell-persistence-boundary")) + && Surface.DetailLine.Contains(TEXT("phase6c/magic120cell/family-owned-scene-state")) + && Surface.DetailLine.Contains(TEXT("phase6c/magiccube5d/dedicated-training-session-surface")) + && Surface.DetailLine.Contains(TEXT("phase6c/magiccube5d/interactive-scene-surface")) + && Surface.DetailLine.Contains(TEXT("magiccube5d-persistence-boundary")) + && Surface.DetailLine.Contains(TEXT("phase6c/magiccube5d/family-owned-scene-state")) + ); TestFalse( TEXT("The control settings ownership surface must not fabricate repository-backed selector recall before a launch request has been observed."), Surface.bRepositoryBackedSelectorRecallReady @@ -1438,6 +1528,13 @@ bool FHyperTwistCoachDashboardControlSettingsOwnershipInspectSurfaceTest::RunTes Surface.DetailLine.Contains(TEXT("magic120cell-focus-view-profile")) && Surface.DetailLine.Contains(TEXT("magiccube5d-projection-view-profile")) ); + TestTrue( + TEXT("The coach dashboard control settings ownership surface must retain dedicated family persistence and state-semantics ids in its detail line."), + Surface.DetailLine.Contains(TEXT("magic120cell-persistence-boundary")) + && Surface.DetailLine.Contains(TEXT("phase6c/magic120cell/family-owned-scene-state")) + && Surface.DetailLine.Contains(TEXT("magiccube5d-persistence-boundary")) + && Surface.DetailLine.Contains(TEXT("phase6c/magiccube5d/family-owned-scene-state")) + ); TestTrue( TEXT("The coach dashboard control settings ownership surface must keep the rendered disabled OpenXR project plugin truth explicit."), Surface.DetailLine.Contains(TEXT("project OpenXR plugin not enabled")) @@ -1447,6 +1544,26 @@ bool FHyperTwistCoachDashboardControlSettingsOwnershipInspectSurfaceTest::RunTes Surface.XrHostDecisionId, TEXT("desktop-hosted-openxr-controller-widening-no-go") ); + TestEqual( + TEXT("The coach dashboard control settings ownership surface must preserve the shipped Magic120Cell projection-tag count."), + Surface.Magic120CellProjectionSurfaceTagCount, + 5 + ); + TestEqual( + TEXT("The coach dashboard control settings ownership surface must preserve the shipped Magic120Cell symmetry-preset count."), + Surface.Magic120CellSymmetryPresetTagCount, + 5 + ); + TestEqual( + TEXT("The coach dashboard control settings ownership surface must preserve the shipped MagicCube5D stereo-tag count."), + Surface.MagicCube5DStereoSurfaceTagCount, + 5 + ); + TestEqual( + TEXT("The coach dashboard control settings ownership surface must preserve the shipped MagicCube5D focus-surface count."), + Surface.MagicCube5DFocusSurfaceTagCount, + 5 + ); TestFalse( TEXT("The coach dashboard control settings ownership surface must preserve the explicit disabled OpenXR project plugin posture."), Surface.bOpenXrProjectPluginEnabled @@ -1839,6 +1956,31 @@ bool FHyperTwistCoachDashboardControlSurfaceStructuredTextArtifactsTest::RunTest CoachDashboard, TEXT("CoachControlSettingsOwnershipCamera") ); + UTextBlock* ControlSettingsImmersiveTextBlock = + HyperTwistBrowserBridgeObjectTestInternal::FindDashboardTextBlock( + CoachDashboard, + TEXT("CoachControlSettingsOwnershipImmersive") + ); + UTextBlock* ControlSettingsMagic120CellTextBlock = + HyperTwistBrowserBridgeObjectTestInternal::FindDashboardTextBlock( + CoachDashboard, + TEXT("CoachControlSettingsOwnershipMagic120Cell") + ); + UTextBlock* ControlSettingsMagicCube5DTextBlock = + HyperTwistBrowserBridgeObjectTestInternal::FindDashboardTextBlock( + CoachDashboard, + TEXT("CoachControlSettingsOwnershipMagicCube5D") + ); + UTextBlock* ControlSettingsSelectorsTextBlock = + HyperTwistBrowserBridgeObjectTestInternal::FindDashboardTextBlock( + CoachDashboard, + TEXT("CoachControlSettingsOwnershipSelectors") + ); + UTextBlock* ControlSettingsSelectorRecallTextBlock = + HyperTwistBrowserBridgeObjectTestInternal::FindDashboardTextBlock( + CoachDashboard, + TEXT("CoachControlSettingsOwnershipSelectorRecall") + ); UTextBlock* ControlSettingsXrTextBlock = HyperTwistBrowserBridgeObjectTestInternal::FindDashboardTextBlock( CoachDashboard, @@ -1852,6 +1994,26 @@ bool FHyperTwistCoachDashboardControlSurfaceStructuredTextArtifactsTest::RunTest TEXT("The coach dashboard must build the structured camera-settings row."), ControlSettingsCameraTextBlock ); + TestNotNull( + TEXT("The coach dashboard must build the structured immersive-settings row."), + ControlSettingsImmersiveTextBlock + ); + TestNotNull( + TEXT("The coach dashboard must build the structured Magic120Cell settings row."), + ControlSettingsMagic120CellTextBlock + ); + TestNotNull( + TEXT("The coach dashboard must build the structured MagicCube5D settings row."), + ControlSettingsMagicCube5DTextBlock + ); + TestNotNull( + TEXT("The coach dashboard must build the structured selector-settings row."), + ControlSettingsSelectorsTextBlock + ); + TestNotNull( + TEXT("The coach dashboard must build the structured selector-recall row."), + ControlSettingsSelectorRecallTextBlock + ); TestNotNull( TEXT("The coach dashboard must build the structured XR settings-boundary row."), ControlSettingsXrTextBlock @@ -1872,6 +2034,60 @@ bool FHyperTwistCoachDashboardControlSurfaceStructuredTextArtifactsTest::RunTest ControlSettingsSurface.CameraSettingsLine ); } + if (ControlSettingsImmersiveTextBlock != nullptr) + { + TestEqual( + TEXT("The structured immersive-settings row must mirror the inspect surface."), + ControlSettingsImmersiveTextBlock->GetText().ToString(), + ControlSettingsSurface.ImmersiveSettingsLine + ); + } + if (ControlSettingsMagic120CellTextBlock != nullptr) + { + TestEqual( + TEXT("The structured Magic120Cell settings row must mirror the inspect surface."), + ControlSettingsMagic120CellTextBlock->GetText().ToString(), + ControlSettingsSurface.Magic120CellSettingsLine + ); + TestTrue( + TEXT("The structured Magic120Cell settings row must expose the shipped projection, symmetry, visibility, and focus counts."), + ControlSettingsMagic120CellTextBlock->GetText().ToString().Contains(TEXT("projection tags 5")) + && ControlSettingsMagic120CellTextBlock->GetText().ToString().Contains(TEXT("symmetry presets 5")) + && ControlSettingsMagic120CellTextBlock->GetText().ToString().Contains(TEXT("visibility presets 5")) + && ControlSettingsMagic120CellTextBlock->GetText().ToString().Contains(TEXT("focus surfaces 5")) + ); + } + if (ControlSettingsMagicCube5DTextBlock != nullptr) + { + TestEqual( + TEXT("The structured MagicCube5D settings row must mirror the inspect surface."), + ControlSettingsMagicCube5DTextBlock->GetText().ToString(), + ControlSettingsSurface.MagicCube5DSettingsLine + ); + TestTrue( + TEXT("The structured MagicCube5D settings row must expose the shipped projection, stereo, visibility, and focus counts."), + ControlSettingsMagicCube5DTextBlock->GetText().ToString().Contains(TEXT("projection tags 5")) + && ControlSettingsMagicCube5DTextBlock->GetText().ToString().Contains(TEXT("stereo tags 5")) + && ControlSettingsMagicCube5DTextBlock->GetText().ToString().Contains(TEXT("visibility tags 5")) + && ControlSettingsMagicCube5DTextBlock->GetText().ToString().Contains(TEXT("focus surfaces 5")) + ); + } + if (ControlSettingsSelectorsTextBlock != nullptr) + { + TestEqual( + TEXT("The structured selector-settings row must mirror the inspect surface."), + ControlSettingsSelectorsTextBlock->GetText().ToString(), + ControlSettingsSurface.SelectorSettingsLine + ); + } + if (ControlSettingsSelectorRecallTextBlock != nullptr) + { + TestEqual( + TEXT("The structured selector-recall row must mirror the inspect surface."), + ControlSettingsSelectorRecallTextBlock->GetText().ToString(), + ControlSettingsSurface.SelectorRecallLine + ); + } if (ControlSettingsXrTextBlock != nullptr) { TestEqual( diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md index f946cc9..2d9f30a 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md @@ -1189,6 +1189,98 @@ Current audit note: - `scripts/run-hypertwist-gitnexus-status.sh` - bounded mirror `Status: up-to-date` +## Latest dedicated-family control-settings ownership truth follow-up (`2026-06-28`) + +- the next same-family native/operator truth packet then tightened the shipped + control-settings ownership seam so higher-dimensional settings proof is no + longer inferred only from broad view-profile validity: + - `FHyperTwistTrainingControlSettingsOwnershipInspectSurface` now carries + active higher-dimensional session and interactive-scene ids plus explicit + `Magic120Cell` and `MagicCube5D` dedicated-family session-surface, + interactive-scene-surface, persistence-boundary, and state-semantics ids + - the same structured state now also carries concrete family tag counts for + projection, symmetry, stereo, visibility, and focus posture plus explicit + persistence-readiness booleans + - `UHyperTwistTrainingPanelWidget::GetDisplayedControlSettingsOwnershipInspectSurface()` + now renders those exact family-owned session, scene, persistence, and + state-semantics ids into the training-panel detail lines and the active + selector-context line instead of collapsing that ownership into softer + summary text + - `HyperTwistBrowserBridgeObjectTest.cpp` now proves those exact ids and + readiness flags on both the training-panel and coach-dashboard settings + inspect seams, while the structured dashboard artifact proof also checks + the new dedicated-family persistence and state-semantics text +- the maintained Windows validation lane then proved that exact-source state + on `C:\HyperTwist_worktrees\phase10validate`: + - synced the exact touched browser test source through + `scripts/run-hypertwist-remote-windows-file-sync.sh` into the already + current maintained validation root + - remote Unreal rebuild through + `scripts/run-hypertwist-remote-unreal-build.sh` + - `Result: Succeeded` + - UnrealBuildTool `Total execution time: 168.35 seconds` + - focused remote automation then passed under: + - `HyperTwist.Browser.TrainingPanel.ControlSettingsOwnershipInspectSurface` + - `HyperTwist.Browser.CoachDashboard.ControlSettingsOwnershipInspectSurface` + - `HyperTwist.Browser.CoachDashboard.ControlSurfaceStructuredTextArtifacts` + - the accepted unattended `Failed to create the web browser window.` dialog + still appeared on the Windows editor lane, but the focused automation + proofs completed successfully and exported their reports cleanly + +## Latest public-route responsive e2e hardening follow-up (`2026-06-28`) + +- the next same-family public-web hardening packet then cross-referenced the + existing `FamiliarOS` and `ScriptoriumAI` website structure only for the + bounded responsive public-route proof shape, while rewriting the actual + expectations to match real HyperTwist public headings and CTAs +- HyperTwist website now owns: + - `website/playwright.config.ts` + - `website/tests/e2e/helpers/navigation.ts` + - `website/tests/e2e/responsive-public-pages.spec.ts` + - `npm --prefix website run test:e2e:responsive` + - `npm --prefix website run test:e2e:responsive:list` +- that responsive packet covers the real current HyperTwist public routes for: + - homepage + - about + - resources + - pricing + - download + - support + - register +- each route now has a browser-level proof for: + - expected public `h1` visibility + - key CTA visibility where appropriate + - no horizontal overflow on mobile and tablet viewport classes +- the owned umbrella validator now also has an opt-in browser pass: + - `scripts/run-hypertwist-web-surface-validation.sh --with-responsive-e2e` + - default validation remains unchanged so routine source/type/test lanes do + not silently start depending on Playwright browser prerequisites + +## Latest protected-route responsive e2e hardening follow-up (`2026-06-28`) + +- the next same-family signed-in web hardening packet then mirrored that same + bounded viewport-proof posture across the current protected operator shell + instead of leaving responsive browser proof limited to anonymous public pages +- HyperTwist website now also owns: + - `website/tests/e2e/helpers/layout.ts` + - `website/tests/e2e/responsive-protected-app-routes.spec.ts` + - `npm --prefix website run test:e2e:protected-responsive` + - `npm --prefix website run test:e2e:protected-responsive:list` +- that protected packet covers the real current signed-in routes for: + - `/app` + - `/app/launch-status` + - `/app/downloads` + - `/app/browser-access` + - `/app/account` + - `/app/notices` +- each protected route now has a browser-level proof for: + - the shared signed-in shell `h1` + - route-specific signed-in panel markers + - no horizontal overflow on mobile and tablet viewport classes +- the same umbrella validator now runs both the public and protected + responsive browser proofs when `--with-responsive-e2e` is explicitly + requested + ## Latest native/public control-roster parity follow-up (`2026-06-25`) - the same-family native/operator continuity lane then aligned the shipped diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index 6852721..baee727 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -146,7 +146,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice. | Rewritten training analytics and report reference grounding | Implemented now | `apache/echarts` retained permissive lane + first-party current code | Current live `Training Analytics` reference side includes four rewritten first-party targets grounded in retained `apache/echarts`: session outcome and progress reporting, analytics data-view and export, timing-trend history and overview interaction, and the optional richer explainer or sidecar boundary. This does not displace the landed `Phase 3R-C` first-party analytics/reporting owner or elevate `ecomfe/echarts-gl` and `ecomfe/zrender` beyond support-only sidecars. | | Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, and the current Phase 1 browser-runtime landing now includes a first-party authoritative `Content/Browser/index.html` shell, bundled-runtime upgrade path, committed clean-checkout plain-JS fallback runtime, embedded `UHyperTwistBrowserWidget` bridge, the `2026-06-19` runtime-ready queue hardening that retains outbound Unreal shell traffic until the browser runtime is ready, a same-day first-party `Browser Runtime Status` surface with shared boot-state ownership across bundled and fallback shells, a follow-on typed Unreal-side `browser-runtime-status` capture seam that retains the last valid runtime snapshot across unrelated later envelope traffic until explicit reset, a second follow-on typed Unreal-side `hypertwist-runtime-ready` capture seam that raises the original handshake payload out of raw JSON-only handling without changing queue flush behavior, a third same-day native/operator-facing status surface that consumes those typed seams inside Unreal while clearing retained runtime ownership on shell-authority change, a fourth same-day native training/operator diagnostics-panel continuation that wires that typed status ownership into `UHyperTwistTrainingPanelWidget` and the coach dashboard without making the dashboard inspect seam depend on stale rendered strings, a fifth same-day diagnostics-fidelity continuation that now carries bootstrap/runtime-ready timestamps, last command/shell-state receipt timestamps, and fallback reason through the same native operator/training seams, and a later `2026-06-23` control/input readiness continuation that projects shipped keyboard/input truth and unfinished XR/preferences truth through the same native training/operator surfaces. This still is not proof of unlimited browser-shell parity. | | Native control/input readiness inspect surface | Implemented now | landed first-party `2026-06-23` browser/native operator continuation | `UHyperTwistTrainingPanelWidget` and `UHyperTwistCoachDashboardWidget` now expose `FHyperTwistTrainingControlInputReadinessInspectSurface`, including the shipped `classic-wca-keyboard/v1` mapping, exact classic-cube pointer, orbit, zoom, and action-shortcut truth, higher-dimensional dedicated-family runtime ownership truth, project-level `EnhancedInput` plus motion-controller groundwork facts, immersive-presence contract presence, and explicit unfinished XR/runtime plus preferences truth. The rendered detail line now also carries the fixed desktop-hosted `No-Go` decision on native OpenXR/controller widening instead of leaving that truth stranded in a non-rendered follow-up field. A later same-day structured-boundary hardening follow-up then promoted that same XR/controller boundary into stable inspect-surface fields as well, including decision id `desktop-hosted-openxr-controller-widening-no-go` plus explicit reopen requirements for dedicated runtime owners, user-facing settings or rebinding ownership, and Windows packaged controller validation, so native operator automation no longer depends only on rendered prose to verify the boundary. The recovered primary reverse-SSH `localhost:22022` lane rebuilt maintained validation worktree `C:\HyperTwist_worktrees\phase10validate` on `2026-06-23` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 1563.53 seconds`, and exported `Saved\AutomationReports\Browser-ControlInputReadiness-Verify\index.json` with all `16` `HyperTwist.Browser.*` tests passing, including `CoachDashboard.ControlInputReadinessInspectSurface` and `TrainingPanel.ControlInputReadinessInspectSurface`. A later same-day quality follow-up then moved the project-input-groundwork inspection behind that surface from raw text scanning into structured Unreal config reads, rebuilt the same maintained validation worktree again with `Result: Succeeded`, UnrealBuildTool `Total execution time: 158.13 seconds`, and re-exported focused browser proof to `Saved\AutomationReports\Browser-ControlInputReadiness-StructuredConfig-Verify` with all `16` `HyperTwist.Browser.*` tests green again. The latest same-family truth-render follow-up on `2026-06-24` then rebuilt that same maintained validation root with `Result: Succeeded`, UnrealBuildTool `Total execution time: 172.52 seconds`, and exported `Saved\AutomationReports\Browser-XrNoGoRendered-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green again, explicitly covering both training-panel and coach-dashboard control-input detail-line visibility for the `No-Go` host decision. A later same-day post-build structured-boundary follow-up then rebuilt that same maintained validation root once more with `Result: Succeeded`, UnrealBuildTool `Total execution time: 2323.18 seconds`, and exported `Saved\AutomationReports\Browser-XrBoundaryStructured-PostBuild-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green again, now explicitly covering the stable decision-id and reopen-requirement fields on both control-input inspect surfaces alongside the sibling control/settings and control/profile seams. A later same-day `2026-06-25` parity follow-up then widened the same surface from general readiness truth into literal roster truth by rendering dedicated classic-cube pointer and action-shortcut lines, rebuilding the maintained validation root with `Result: Succeeded` at `5278.57 seconds` and exact-source rerun `70.76 seconds`, and exporting `Saved\AutomationReports\Browser-ControlRosterParity-Verify\index.json` with all `21` `HyperTwist.Browser.*` tests green, including both training-panel and coach-dashboard control-input surfaces plus the structured dashboard artifact seam. The latest same-family `2026-06-28` ownership-truth follow-up then tightened higher-dimensional readiness again by resolving the shipped `Magic120Cell` and `MagicCube5D` dedicated-family host, view-context, session, and interactive-scene surfaces explicitly by activation profile id instead of treating broad catalog validity as equivalent proof. That exact-source-state follow-up rebuilt maintained validation root `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 109.14 seconds`, then passed all three focused browser filters `TrainingPanel.ControlInputReadinessInspectSurface`, `CoachDashboard.ControlInputReadinessInspectSurface`, and `CoachDashboard.ControlSurfaceStructuredTextArtifacts`, so both the inspect surfaces and the rendered structured dashboard row now keep concrete family ownership ids such as `phase6c/magic120cell/runtime-host-surface` and `phase6c/magiccube5d/interactive-scene-surface` visible to operators. | -| Native control/settings ownership inspect surface | Implemented now | landed first-party `2026-06-24` browser/native operator continuation | `UHyperTwistTrainingPanelWidget` and `UHyperTwistCoachDashboardWidget` now expose `FHyperTwistTrainingControlSettingsOwnershipInspectSurface`, including the shipped viewer camera-settings owner `tool/camera-settings`, the immersive-presence control contract, dedicated-family `magic120cell-focus-view-profile` and `magiccube5d-projection-view-profile` view ownership, higher-dimensional selector ownership, persisted generated-mode selector recall when a structurally valid launch request is present, and explicit unfinished XR/controller rebinding truth. A later same-day structured-boundary hardening follow-up then carried the same `desktop-hosted-openxr-controller-widening-no-go` decision id plus the explicit reopen requirements for dedicated runtime owners, polished user-facing settings or rebinding ownership, and Windows packaged controller validation into this surface’s structured state as well, so control-settings diagnostics no longer have to prove the boundary only by substring matching their rendered detail line. The same primary reverse-SSH `localhost:22022` lane rebuilt maintained validation worktree `C:\HyperTwist_worktrees\phase10validate` on `2026-06-24` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 4331.11 seconds`, and then exported `Saved\AutomationReports\Browser-ControlSettingsOwnership-Verify\index.json` with all `18` `HyperTwist.Browser.*` tests passing, including `CoachDashboard.ControlSettingsOwnershipInspectSurface`, `TrainingPanel.ControlSettingsOwnershipInspectSurface`, and the previously landed runtime plus control/input inspect seams. A later same-day selector-recall hardening follow-up then rebuilt that same maintained validation root again with `Result: Succeeded`, UnrealBuildTool `Total execution time: 4084.77 seconds`, and exported `Saved\AutomationReports\Browser-SelectorRecall-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests passing, keeping the current control/settings proof aligned with the latest repository-backed selector-recall behavior. A later same-day post-build structured-boundary follow-up then rebuilt that same maintained validation root once more with `Result: Succeeded`, UnrealBuildTool `Total execution time: 2323.18 seconds`, and exported `Saved\AutomationReports\Browser-XrBoundaryStructured-PostBuild-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green again, now explicitly covering the stable decision-id and reopen-requirement fields on both control/settings inspect surfaces alongside the sibling control/input and control/profile seams. | +| Native control/settings ownership inspect surface | Implemented now | landed first-party `2026-06-24` browser/native operator continuation | `UHyperTwistTrainingPanelWidget` and `UHyperTwistCoachDashboardWidget` now expose `FHyperTwistTrainingControlSettingsOwnershipInspectSurface`, including the shipped viewer camera-settings owner `tool/camera-settings`, the immersive-presence control contract, dedicated-family `magic120cell-focus-view-profile` and `magiccube5d-projection-view-profile` view ownership, higher-dimensional selector ownership, persisted generated-mode selector recall when a structurally valid launch request is present, and explicit unfinished XR/controller rebinding truth. A later same-day structured-boundary hardening follow-up then carried the same `desktop-hosted-openxr-controller-widening-no-go` decision id plus the explicit reopen requirements for dedicated runtime owners, polished user-facing settings or rebinding ownership, and Windows packaged controller validation into this surface’s structured state as well, so control-settings diagnostics no longer have to prove the boundary only by substring matching their rendered detail line. The same primary reverse-SSH `localhost:22022` lane rebuilt maintained validation worktree `C:\HyperTwist_worktrees\phase10validate` on `2026-06-24` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 4331.11 seconds`, and then exported `Saved\AutomationReports\Browser-ControlSettingsOwnership-Verify\index.json` with all `18` `HyperTwist.Browser.*` tests passing, including `CoachDashboard.ControlSettingsOwnershipInspectSurface`, `TrainingPanel.ControlSettingsOwnershipInspectSurface`, and the previously landed runtime plus control/input inspect seams. A later same-day selector-recall hardening follow-up then rebuilt that same maintained validation root again with `Result: Succeeded`, UnrealBuildTool `Total execution time: 4084.77 seconds`, and exported `Saved\AutomationReports\Browser-SelectorRecall-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests passing, keeping the current control/settings proof aligned with the latest repository-backed selector-recall behavior. A later same-day post-build structured-boundary follow-up then rebuilt that same maintained validation root once more with `Result: Succeeded`, UnrealBuildTool `Total execution time: 2323.18 seconds`, and exported `Saved\AutomationReports\Browser-XrBoundaryStructured-PostBuild-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green again, now explicitly covering the stable decision-id and reopen-requirement fields on both control/settings inspect surfaces alongside the sibling control/input and control/profile seams. The latest same-family `2026-06-28` ownership-proof follow-up then tightened this settings seam again by resolving the active higher-dimensional session surface, active interactive-scene surface, dedicated-family session surface, dedicated-family interactive-scene surface, persistence-boundary id, and state-semantics id explicitly for both `Magic120Cell` and `MagicCube5D`, while also carrying concrete projection, symmetry, stereo, visibility, and focus tag counts plus persistence-readiness booleans in structured state instead of leaving that ownership implied by broad view-profile validity. That exact-source proof-hardening follow-up synced the tightened browser test source into the already-current maintained validation root, rebuilt it with `Result: Succeeded` and UnrealBuildTool `Total execution time: 168.35 seconds`, then passed focused browser proof for `HyperTwist.Browser.TrainingPanel.ControlSettingsOwnershipInspectSurface`, `HyperTwist.Browser.CoachDashboard.ControlSettingsOwnershipInspectSurface`, and `HyperTwist.Browser.CoachDashboard.ControlSurfaceStructuredTextArtifacts`, so both the inspect surfaces and the rendered structured dashboard row now keep exact family-owned ids such as `phase6c/magic120cell/dedicated-training-session-surface`, `phase6c/magic120cell/interactive-scene-surface`, `magic120cell-persistence-boundary`, `phase6c/magiccube5d/dedicated-training-session-surface`, and `phase6c/magiccube5d/family-owned-scene-state` visible to operators. | | Native control/profile roster inspect surface | Implemented now | landed first-party `2026-06-24` browser/native operator continuation | `UHyperTwistTrainingPanelWidget` and `UHyperTwistCoachDashboardWidget` now expose `FHyperTwistTrainingControlProfileRosterInspectSurface`, keeping the actually shipped selectable native roster visible: classic keyboard profile `classic-wca-keyboard/v1` with `18` bindings plus the exact classic move roster, immersive-presence contract `immersive-training-presence-control-contract` with `3` intensity plus `3` reduced-distraction presets, dedicated-family `Magic120Cell` runtime/view ids `magic120cell-runtime-profile` and `magic120cell-focus-view-profile`, dedicated-family `MagicCube5D` runtime/view ids `magiccube5d-runtime-profile` and `magiccube5d-projection-view-profile`, the current `4` selectors in each dedicated-family roster, persisted generated-mode selector recall when a structurally valid launch request is present, and explicit unfinished controller rebinding truth. The rendered roster boundary now also states the fixed desktop-hosted `No-Go` decision on native OpenXR/controller widening instead of sounding like a merely pending generic settings packet. A later same-day structured-boundary hardening follow-up then carried the same `desktop-hosted-openxr-controller-widening-no-go` decision id plus its explicit reopen requirements into the roster surface’s structured state too, so native operator automation can prove the shipped roster boundary without depending only on rendered text. After hash-syncing the touched type/training/dashboard/test files into maintained validation worktree `C:\HyperTwist_worktrees\phase10validate`, the same primary reverse-SSH `localhost:22022` lane rebuilt the widened slice on `2026-06-24` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 3606.23 seconds`, then exported `Saved\AutomationReports\Browser-ControlProfileRoster-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests passing, including `CoachDashboard.ControlProfileRosterInspectSurface` and `TrainingPanel.ControlProfileRosterInspectSurface`. A later same-day selector-recall hardening follow-up then rebuilt that same maintained validation root again with `Result: Succeeded`, UnrealBuildTool `Total execution time: 4084.77 seconds`, and exported `Saved\AutomationReports\Browser-SelectorRecall-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests passing again, now explicitly covering imported-request fallback plus active-deck precedence for repository-backed selector recall inside the training-panel and coach-dashboard roster surface. The latest same-family truth-render follow-up on `2026-06-24` then rebuilt that same maintained validation root with `Result: Succeeded`, UnrealBuildTool `Total execution time: 172.52 seconds`, and exported `Saved\AutomationReports\Browser-XrNoGoRendered-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green again, explicitly covering the roster detail-line visibility of the same fixed `No-Go` boundary. A later same-day post-build structured-boundary follow-up then rebuilt that same maintained validation root once more with `Result: Succeeded`, UnrealBuildTool `Total execution time: 2323.18 seconds`, and exported `Saved\AutomationReports\Browser-XrBoundaryStructured-PostBuild-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green again, now explicitly covering the stable decision-id and reopen-requirement fields on both control/profile inspect surfaces alongside the sibling control/input and control/settings seams. A later same-day `2026-06-25` parity follow-up then added the literal shipped classic move-roster line to that surface, mirrored it into dedicated coach-dashboard structured rows, rebuilt the maintained validation root with `Result: Succeeded` at `5278.57 seconds` and exact-source rerun `70.76 seconds`, and exported `Saved\AutomationReports\Browser-ControlRosterParity-Verify\index.json` with all `21` `HyperTwist.Browser.*` tests green, including both training-panel and coach-dashboard control/profile surfaces plus the structured dashboard artifact seam. | | Native XR project-plugin posture inspect truth | Implemented now | landed first-party `2026-06-25` browser/native operator continuation | All three native control/input, control/settings, and control/profile inspect surfaces now expose the current project `OpenXR` plugin enabled/disabled fact in both structured state and rendered operator lines, so project-level motion-controller groundwork no longer masquerades as an enabled `OpenXR` shipping posture. The same primary reverse-SSH `localhost:22022` lane rebuilt the exact-source state on maintained validation root `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 99.38 seconds`, then exported `Saved\AutomationReports\Browser-XrPluginProjectPosture-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green across the training-panel and coach-dashboard input/settings/profile seams. | | Optional full-browser client path above Unreal backend authority | Deep-source grounded retained | landed first-party `Phase 8B` preparation packet + spec/evidence continuation + backend-contract continuation | This branch remains a spec-only first-party option: a browser-owned rendering and input shell may sit above explicit Unreal backend seams while Unreal remains authoritative for puzzle, training, timer, replay, and persistence state. The current continuation now fixes the allowed seam families to catalog, session, action, state, continuity push, replay, persistence status, and runtime health, then further fixes transport-neutral operation labels, minimum identity or freshness requirements, and the owned state/runtime payload anchors that any later browser-client backend implementation must preserve. The landed embedded browser/CEF shell remains the current shipping posture, and this branch stays separate from any native `MagicTile` behavior or renderer widening. | @@ -269,7 +269,7 @@ repo. | Feature | Status | Primary authority | Notes | |---|---|---|---| | Public `hypertwist.app` marketing shell | Implemented now | first-party `website/` app + feature registry/roadmap authority | HyperTwist now has a dedicated first-party public web surface for homepage, feature atlas, about, resources, pricing, download, support, legal routes, a dedicated `/getting-started` onboarding surface, and a dedicated `/launch-status` authority surface. This lane is separate from the embedded Unreal browser runtime under `Content/Browser/` and does not claim browser-simulator parity. The same public lane now also serves as a bounded operator/distribution manual through the docs/resources/download/support surfaces, the public feature-atlas route, the dedicated getting-started route, the dedicated launch-status route, and related launch/status surfaces, explaining browser-versus-desktop posture, rollout steps, package proof, simulator-use guidance, and current input/device truth without claiming browser ownership of the native runtime or overclaiming unfinished VR/controller posture. The same package now also carries a first-party external runtime-readiness verifier so deploy-time env and live health posture can be checked outside the dashboard, plus separated local-versus-production env templates whose placeholder values are intentionally rejected until real launch config is in place, bootstrap CI now validates both the frontend and auth-server website commands directly, and the auth server can now auto-serve the built `website/dist` bundle with bounded SPA fallback for same-origin public deployment. Request-level server coverage now also proves that public/app shell delivery does not shadow `/api/*`, `/auth*`, `/health`, or missing asset paths, while the pricing/download/notices routes now surface first-party preview-versus-launch posture from the same bounded launch checklist instead of relying on hidden operator-only status. The shared marketing shell now also carries a compact public-site-status banner across public pages, the homepage keeps a fuller status section, and the shared public launch-status component now consumes live auth-health webhook/runtime truth in addition to release-manifest download readiness so public marketing copy does not claim launch posture from static checkout/download config alone. The auth server now also emits an authoritative `launch` summary on `/api/auth/health`, including required blocker labels, billing product/price-map gates, and current checkout targets, so the public banner and protected dashboard no longer duplicate launch-blocker evaluation client-side. A later same-family continuation then widened that release-manifest authority again to carry public runtime commerce config for operator/studio checkout URLs and live plan-price strings, so pricing, notices, and dashboard launch-readiness surfaces no longer depend only on frontend build-time checkout config. The live website lane now also owns route-aware title/description/canonical/Open-Graph/Twitter metadata for the real `hypertwist.app` marketing surface so deployed public pages no longer remain on a single generic SPA title/description, plus first-party `robots.txt` and `sitemap.xml` assets for the public route set while keeping `/app`, `/login`, and `/register` out of crawler posture. The real `check-runtime-readiness` CLI is now also exercised against the checked-in production example env files, and a spawned `website/server` bootstrap proof now verifies the live same-origin process path from production-shaped env into `/health`, `/api/auth/health`, built-shell serving, and the public anonymous release-manifest posture for the shared desktop release lane. The same verifier now also probes the deployed root-shell marker and can explicitly fail when the public origin is still serving the older placeholder rollout page instead of the first-party website/auth-server lane, while the repo now also carries first-party `website/deploy/` `nginx` plus `systemd` handoff templates, a concrete same-origin public-host cutover guide, a deployment-file renderer that emits resolved operator outputs from real checkout paths, and a manifest-driven bundle renderer that lets one authoritative input own the public origin while emitting validated env plus install artifacts together, with the shared-VPS-safe default upstream moved to `3011` after live host inspection confirmed `3001` is already occupied by FamiliarOS. The same deployment lane now also distinguishes `launch` from `preview` posture so honest missing checkout/download/webhook/release values are accepted only for non-public rehearsal while placeholder strings still fail, `runtime.mode: mixed` plus `public_origin_ready: true` counts as valid preview-host proof, and the staging helper can archive either committed `HEAD` or the live worktree through `--archive-source worktree`. An isolated VPS-local staging proof then confirmed that both the committed HyperTwist website lane and the later preview-tier worktree packet can serve green health, release-manifest, and first-party shell responses on that real shared host behind `3011`, and a later root-owned cutover then replaced the public placeholder site with the live first-party same-origin preview deployment on `https://hypertwist.app`. The package now also ships a bounded root-owned live-deploy helper that stages the committed checkout, uploads the rendered bundle, installs env, rebuilds the site, replaces the live `systemd` plus `nginx` files, and validates the public origin; that helper has already been re-proved idempotently against the live host. The repo now also ships that host-proof flow as a first-party staging helper so future sessions can rerun the temp checkout/build/boot verification path directly before or after root-owned cutover. The website lane now also owns a sanitized generated packaged-validation summary for the Windows higher-dimensional desktop proof, rendered from the checked-in authoritative package report into `website/src/shared/generated/windows-package-validation-summary.json`, and the owned web-surface validation gate now checks that generated proof for freshness before approving the current public/auth/download/browser lane. The remaining public conversion seams now also stop dead-ending on generic sign-in/support detours when the next lane is already known: the explorer CTA preserves `/register?next=/app`, operator fallback opens the protected downloads lane, studio fallback opens the protected browser-access rollout lane, and launch/download fallback callouts now point directly at protected release, notices, or dashboard follow-through. The narrative/help surfaces then widened again so the public About and Support pages also surface the same live launch-readiness callout and packaged desktop-proof section already used by the stronger public manual routes, keeping maturity language attached to current native release evidence instead of leaving those pages purely abstract. The same public manual continuation now also adds a direct reusable surface-choice guide across the homepage, About, Pricing, Download, and Support pages so operators no longer have to infer from the larger surface matrix alone when they should stay on the public web surface, move into the protected dashboard, or move into the packaged desktop runtime. A later same-family continuation then propagated that same guide through the remaining public feature/docs/resources/changelog/legal pages as well, so the whole public site now preserves one consistent browser-versus-protected-versus-desktop handoff story instead of leaving those lower-traffic surfaces on older implicit wording. A further same-day manual-clarity continuation then added a reusable direct comparison section across the homepage, About, Docs, and Download routes explaining why the website remains necessary, where it is intentionally narrower than the simulator, why the native Unreal runtime stays primary, and how the current keyboard or mouse versus unfinished XR/controller truth should be read. The latest same-family parity follow-up then widened that exact browser-versus-desktop reality section into Pricing and Resources as well, so commercial and reference-heavy public routes no longer rely only on the lighter surface-choice guide when explaining what stays native and where the current XR/controller boundary still begins. The next adjacent parity follow-up then extended the same direct comparison into the public Feature atlas and Support routes as well, so the remaining major public operator-facing surfaces no longer fall back to the lighter guide alone when capability review or recovery work needs the sharper website-versus-native split. A later same-family continuity follow-up then widened the shared release-decision guide into the remaining public legal/notices routes too, so open-source notices, privacy, terms, and shipping/payment now also tell the operator whether the next honest move is protected desktop access, pricing/provisioning, protected browser/account continuity, or notices/source follow-through instead of leaving those pages on the older lighter handoff only. The same browser shell now also has a first-party top-level runtime-recovery boundary, so unexpected React route failures degrade into a HyperTwist-owned retry/return surface that keeps the browser-versus-desktop product truth explicit instead of collapsing into a blank shell. A later same-family quickstart/manual continuation then added a reusable first serious-session guide across homepage, docs, and download so operators can see the real path from release-target choice through desktop-link pairing, first native verification, higher-dimensional verification, and the current XR/controller `No-Go` boundary in one place instead of reconstructing that flow from scattered adjacent sections. That same onboarding/manual packet is now also anchored at a dedicated public `/getting-started` route so the shortest complete browser-to-desktop first-session path is canonical, crawlable, and easier to hand off than the broader site sections alone. The next adjacent launch-authority continuation then did the same for rollout truth through a dedicated public `/launch-status` route, giving preview-versus-launch posture, rollout blockers, packaged proof, and release-reference follow-through one canonical public authority surface instead of leaving that lane distributed across pricing and compact status banners alone. The next same-family shared-auth manual continuation then widened the real browser-account method lineup beyond the auth entry forms and deeper docs surfaces into the homepage, pricing, download, and support routes too, so the public pages where operators actually decide to sign in, buy access, or recover access posture now expose the real provider lineup directly. The latest same-family manual continuation now also adds a shared public route atlas across homepage, docs, and resources so each major public page explains its exact job in the broader operator/distribution manual instead of leaving page purpose mostly implicit in navigation alone. | -| Browser-based operator/account dashboard | Implemented now | first-party `website/` app + shared auth/dashboard packet | A protected browser dashboard is now live for operator access, account state, download posture, browser-access boundary explanation, notices review, and bounded billing/entitlement status. It reuses the shared SuperTokens auth posture proven in FamiliarOS and ScriptoriumAI while remaining HyperTwist-specific in product content and boundary claims, the current auth-health surface now truthfully distinguishes configured versus reachable or ready shared-core posture while exposing fallback-active reason instead of hardcoding readiness, and the same dashboard now also surfaces launch-readiness truth for download URLs, checkout links, source/notices URLs, billing-secret/map configuration, and local-versus-public runtime deployment posture. The same shared-auth lane now also carries end-to-end optional provider parity for GitHub, Google, and bounded ORCID sign-in: the auth server owns the ORCID custom-provider path, the same-origin bundle emits matching ORCID frontend/server env truth, auth-health reports ORCID readiness alongside the other providers, and the public docs route now exposes the current browser-account method lineup instead of leaving provider truth visible only on the form pages. The surrounding public decision surfaces now also mirror that same provider-lineup truth on homepage, pricing, download, and support, so shared-auth reality is not hidden until operators reach either the auth forms or the deeper manual. The same protected overview now also consumes the server-backed Windows packaged-validation summary that the release-manifest authority exposes, so operators can see current higher-dimensional desktop proof without drilling into the dedicated downloads screen. The adjacent protected `/app/browser-access` and `/app/notices` routes now also reuse the bounded escalation-map and release-follow-through guidance from the public support/manual lane, so sign-in does not degrade operator troubleshooting quality into a thinner shell than the public surface. The protected overview and protected download center now also mirror the same launch-readiness, operator-access, and studio-rollout help lanes as the public support/manual surface, but translate those actions into direct signed-in dashboard, downloads, browser-access, account, notices, pricing, and release-notes targets instead of sending the operator back through anonymous auth detours. A later same-family parity follow-up then added the same blunt browser-versus-desktop reality explanation to the protected dashboard, protected downloads, and browser-access routes as well, so sign-in no longer makes the product boundary or unfinished XR/controller truth less explicit than the public manual. The next adjacent parity follow-up then extended that same compact reality panel into the protected account and notices routes too, so entitlement review and distribution/compliance work no longer soften the browser-support versus native-simulator split after sign-in. The auth entry pages now also render the same browser-versus-desktop truth before sign-in is even complete, so login and register no longer rely only on next-step routing plus later dashboard surfaces to explain what stays native and where the current XR/controller boundary still begins. A later same-family protected-shell continuation then widened signed-in auth truth itself, so the dashboard and account routes now surface the auth method, current provider lineup, shared-auth readiness, runtime origins, cookie/public-origin posture, fallback state, and deployment diagnostics directly instead of collapsing that truth down to a thin stack label. The protected fallback path now also preserves signed-in viewer plan/access posture from local session truth when live manifest authority is temporarily unavailable, while still keeping download authority conservative until the auth server returns. The shared browser auth layer now also has a bounded release-authority reconciliation path that refreshes and persists the local browser session snapshot from fresher manifest viewer truth, while the dashboard and account surfaces expose a compact live-authority sync notice summarizing what changed so operators can see the session catch-up clearly. That same auth layer now also clears stale stored sessions when shared auth is configured and `/api/auth/me` returns `401`, while still preserving the intended offline/local-fallback posture when the account API is merely unreachable or shared auth is not configured. The next adjacent protected-shell launch-authority continuation then added a dedicated signed-in `/app/launch-status` route, so launch posture, rollout blockers, release references, packaged proof, and signed-in next actions no longer collapse back to a single overview panel once account context exists. The follow-through continuation after that then threaded the same signed-in launch-status route back through protected release-action cards, release-reference panels, and the browser-access, download, account, and notices surfaces, so rollout authority remains attached to the surrounding signed-in operator shell instead of becoming an isolated page. Focused frontend coverage now also protects deep-link login redirect preservation, safe `next`-path normalization across auth entry points, fallback/email auth-bootstrap normalization, login/register continuation behavior, public download-gating behavior, protected-route/shell behavior, real lazy-route tree behavior for key public and protected paths, top-level app-bootstrap and SuperTokens-wrapper posture, login/register unhappy-path and OAuth-button behavior, support-topic fallback routing when live checkout is not configured, desktop-link verify-url/dashboard readiness behavior, and explicit `noindex,nofollow` posture on protected/auth browser surfaces. The validation lane now also has a bounded signed test-session harness under `TEST_MODE=testing` that proves `/api/auth/me`, stale-session invalidation, release-authority reconciliation, and `/api/auth/desktop-link` behavior through the live spawned auth-server process without widening production auth posture. | +| Browser-based operator/account dashboard | Implemented now | first-party `website/` app + shared auth/dashboard packet | A protected browser dashboard is now live for operator access, account state, download posture, browser-access boundary explanation, notices review, and bounded billing/entitlement status. It reuses the shared SuperTokens auth posture proven in FamiliarOS and ScriptoriumAI while remaining HyperTwist-specific in product content and boundary claims, the current auth-health surface now truthfully distinguishes configured versus reachable or ready shared-core posture while exposing fallback-active reason instead of hardcoding readiness, and the same dashboard now also surfaces launch-readiness truth for download URLs, checkout links, source/notices URLs, billing-secret/map configuration, and local-versus-public runtime deployment posture. The same shared-auth lane now also carries end-to-end optional provider parity for GitHub, Google, and bounded ORCID sign-in: the auth server owns the ORCID custom-provider path, the same-origin bundle emits matching ORCID frontend/server env truth, auth-health reports ORCID readiness alongside the other providers, and the public docs route now exposes the current browser-account method lineup instead of leaving provider truth visible only on the form pages. The surrounding public decision surfaces now also mirror that same provider-lineup truth on homepage, pricing, download, and support, so shared-auth reality is not hidden until operators reach either the auth forms or the deeper manual. The same protected overview now also consumes the server-backed Windows packaged-validation summary that the release-manifest authority exposes, so operators can see current higher-dimensional desktop proof without drilling into the dedicated downloads screen. The adjacent protected `/app/browser-access` and `/app/notices` routes now also reuse the bounded escalation-map and release-follow-through guidance from the public support/manual lane, so sign-in does not degrade operator troubleshooting quality into a thinner shell than the public surface. The protected overview and protected download center now also mirror the same launch-readiness, operator-access, and studio-rollout help lanes as the public support/manual surface, but translate those actions into direct signed-in dashboard, downloads, browser-access, account, notices, pricing, and release-notes targets instead of sending the operator back through anonymous auth detours. A later same-family parity follow-up then added the same blunt browser-versus-desktop reality explanation to the protected dashboard, protected downloads, and browser-access routes as well, so sign-in no longer makes the product boundary or unfinished XR/controller truth less explicit than the public manual. The next adjacent parity follow-up then extended that same compact reality panel into the protected account and notices routes too, so entitlement review and distribution/compliance work no longer soften the browser-support versus native-simulator split after sign-in. The auth entry pages now also render the same browser-versus-desktop truth before sign-in is even complete, so login and register no longer rely only on next-step routing plus later dashboard surfaces to explain what stays native and where the current XR/controller boundary still begins. A later same-family protected-shell continuation then widened signed-in auth truth itself, so the dashboard and account routes now surface the auth method, current provider lineup, shared-auth readiness, runtime origins, cookie/public-origin posture, fallback state, and deployment diagnostics directly instead of collapsing that truth down to a thin stack label. The protected fallback path now also preserves signed-in viewer plan/access posture from local session truth when live manifest authority is temporarily unavailable, while still keeping download authority conservative until the auth server returns. The shared browser auth layer now also has a bounded release-authority reconciliation path that refreshes and persists the local browser session snapshot from fresher manifest viewer truth, while the dashboard and account surfaces expose a compact live-authority sync notice summarizing what changed so operators can see the session catch-up clearly. That same auth layer now also clears stale stored sessions when shared auth is configured and `/api/auth/me` returns `401`, while still preserving the intended offline/local-fallback posture when the account API is merely unreachable or shared auth is not configured. The next adjacent protected-shell launch-authority continuation then added a dedicated signed-in `/app/launch-status` route, so launch posture, rollout blockers, release references, packaged proof, and signed-in next actions no longer collapse back to a single overview panel once account context exists. The follow-through continuation after that then threaded the same signed-in launch-status route back through protected release-action cards, release-reference panels, and the browser-access, download, account, and notices surfaces, so rollout authority remains attached to the surrounding signed-in operator shell instead of becoming an isolated page. Focused frontend coverage now also protects deep-link login redirect preservation, safe `next`-path normalization across auth entry points, fallback/email auth-bootstrap normalization, login/register continuation behavior, public download-gating behavior, protected-route/shell behavior, real lazy-route tree behavior for key public and protected paths, top-level app-bootstrap and SuperTokens-wrapper posture, login/register unhappy-path and OAuth-button behavior, support-topic fallback routing when live checkout is not configured, desktop-link verify-url/dashboard readiness behavior, and explicit `noindex,nofollow` posture on protected/auth browser surfaces. The validation lane now also has a bounded signed test-session harness under `TEST_MODE=testing` that proves `/api/auth/me`, stale-session invalidation, release-authority reconciliation, and `/api/auth/desktop-link` behavior through the live spawned auth-server process without widening production auth posture. A later same-family responsive-browser hardening follow-up then added Playwright viewport proof for `/app`, `/app/launch-status`, `/app/downloads`, `/app/browser-access`, `/app/account`, and `/app/notices`, so the shipped signed-in operator shell now has the same bounded mobile/tablet no-horizontal-overflow proof posture as the current public-route family. | | Desktop download posture and browser-to-desktop pairing | Implemented now | first-party `website/` app + `website/server` desktop-link endpoints | Public download targets, dashboard-side release posture, and short-lived desktop-link token generation/verification are now first-party owned. The current server posture now enforces exact website-origin matching, bounded per-user issuance, one-time token consumption, and billing-backed plan/download entitlement resolution with focused `website/server` tests green on `2026-06-22`, and the verify handshake now returns the same resolved download-entitlement posture the dashboard sees instead of only identity plus plan/role. The same lane now also owns a shared `GET /api/releases/manifest` runtime authority for release version/channel/build/published/file-size/checksum/docs/source metadata, with anonymous callers intentionally denied raw download URLs while entitled session-backed callers receive the configured direct platform URL. That manifest now also carries first-party packaged-validation summary truth for the Windows higher-dimensional desktop lane, so the public `/download` page, the public `/resources` reference page, and the protected `/app/downloads` surface can project real package evidence for the dedicated-family `Magic120Cell` / `MagicCube5D` maps even while launch-tier release URLs remain unconfigured. That website-facing packaged proof is now sourced through a sanitized generated summary rendered from the checked-in authoritative `phase6c_dedicated_family_package_validation_report.json`, and the owned web-surface validation gate now checks that generated summary for freshness before approving the current public/auth/download/browser lane. The public `/download` page now keeps raw download URLs behind the protected dashboard instead of exposing them directly, preserves requested platform continuity through `/app/downloads?platform=...`, and surfaces that requested target again after auth handoff inside the protected release lane. The public download fallbacks now also open exact protected download and dashboard follow-through instead of stopping at a generic anonymous login hop. When live manifest authority is unavailable, the protected fallback path now still reflects the signed-in viewer posture from local session truth while continuing to withhold raw delivery authority. When live manifest authority resolves fresher entitled viewer truth than the local browser auth snapshot, the protected download-center lane now also follows that server-backed viewer posture for the actual download action instead of leaving the operator blocked behind stale local session state. Both the public and protected download surfaces now also carry first-party rollout steps, first-launch desktop setup guidance, browser-to-desktop pairing follow-through, and release/notices/source references so the desktop setup lane is more than a generic link bucket, while the support surface now carries an explicit escalation map separating account, package, runtime, and rollout/compliance problems. The protected dashboard overview and protected account surface now also mirror that same first-launch follow-through, and the protected browser-access/notices routes now mirror the same escalation separation, so post-sign-in operator guidance does not collapse back into a thinner release-only shell. The protected download center now also mirrors the same three help-topic lanes used by the public support/manual surface, but with direct signed-in actions for downloads, dashboard, account, browser access, notices, and release notes so entitled operators can stay inside the protected rollout lane once auth has already succeeded. The dashboard plus public launch-status callouts now consume the same manifest-backed Windows download truth instead of only static frontend config. Actual release URLs remain deployment configuration rather than hardcoded product truth. | | Paddle-ready pricing and billing webhook seam | Implemented now | first-party `website/` app + `website/server` billing endpoint | The public pricing surface now exists with plan structure, checkout-link configuration seams, and the same `/api/billing/paddle/webhook` endpoint family used by the broader product website lane. The current server now verifies `Paddle-Signature` against `PADDLE_WEBHOOK_SECRET` using the documented raw-body HMAC flow, persists a bounded first-party billing state file, and applies verified Paddle events into account/download entitlement state that the browser dashboard consumes, with focused `website/server` tests green on `2026-06-22`. The shared `GET /api/releases/manifest` authority now also carries public runtime commerce config for operator/studio checkout URLs and live plan-price strings, allowing the pricing page to switch from frontend build-time checkout assumptions to auth-server runtime truth when those values are configured. The fallback plan CTAs now also use exact protected release/browser-access targets when live checkout URLs are absent, instead of routing operators back into a vague public support detour. A spawned live-process proof now also verifies that a real signed webhook updates processed-event health and persisted billing state through the actual auth-server runtime, not only helper-level store tests, and transaction events no longer leak their id into stored `subscriptionId` state. Production checkout URLs, secret management, and broader operator/admin billing workflows remain deployment/application tasks, not shipped-code omissions. | | Public open-source notices and corresponding-source surface | Implemented now | first-party `website/` app + `HYPERTWIST_MPL_DISTRIBUTION_PLACEMENT_CHECKLIST_2026-05-25.md` | HyperTwist now has a stable public `Open Source Notices` route linked from pricing, download, and footer surfaces, satisfying the requirement that public distribution surfaces expose notice and corresponding-source guidance when shipped builds contain `MPL`-covered material. The exact public corresponding-source URL still must be configured before external launch. | diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md b/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md index 76fed76..b78fab0 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md @@ -126,6 +126,12 @@ proves shipped `Magic120Cell` and `MagicCube5D` host, view-context, session, and interactive-scene ownership explicitly by activation profile id instead of only implying readiness from broad runtime-catalog validity. +The adjacent native control-settings ownership seam was tightened the same day +too: operator-facing settings truth now proves dedicated-family session, +interactive-scene, persistence-boundary, and state-semantics ownership +explicitly for both `Magic120Cell` and `MagicCube5D` instead of leaving that +family-owned runtime state hidden behind generic view-settings wording. + Canonical audit note: - `C:\HyperTwist\docs\ops\HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md` diff --git a/scripts/run-hypertwist-web-surface-validation.sh b/scripts/run-hypertwist-web-surface-validation.sh index 0c70c98..88c9e5e 100644 --- a/scripts/run-hypertwist-web-surface-validation.sh +++ b/scripts/run-hypertwist-web-surface-validation.sh @@ -4,11 +4,12 @@ set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" strict_auth_server_audit=0 skip_audits=0 +with_responsive_e2e=0 usage() { cat <<'EOF' Usage: - scripts/run-hypertwist-web-surface-validation.sh [--strict-auth-server-audit] [--skip-audits] + scripts/run-hypertwist-web-surface-validation.sh [--strict-auth-server-audit] [--skip-audits] [--with-responsive-e2e] Runs the current first-party HyperTwist web-surface validation packet: - website type-check, focused tests, and production build @@ -20,6 +21,14 @@ Current auth-server nuance: the default mode accepts the known upstream `supertokens-node -> nodemailer` production advisory as a documented residual warning. Pass `--strict-auth-server-audit` to fail on that residual too. + +Optional responsive-browser hardening: + `--with-responsive-e2e` also runs the bounded Playwright public-route and + protected-route responsiveness packets for homepage, about, resources, + pricing, download, support, register, `/app`, `/app/launch-status`, + `/app/downloads`, `/app/browser-access`, `/app/account`, and + `/app/notices`. This expects Playwright browser prerequisites to be + available on the host. EOF } @@ -31,6 +40,9 @@ while [[ $# -gt 0 ]]; do --skip-audits) skip_audits=1 ;; + --with-responsive-e2e) + with_responsive_e2e=1 + ;; -h|--help) usage exit 0 @@ -150,6 +162,14 @@ run_step \ src/__tests__/public-marketing-pages.test.tsx \ src/__tests__/public-auth-pages.test.tsx run_step "website production build" npm --prefix website run build +if [[ "$with_responsive_e2e" -eq 1 ]]; then + run_step \ + "website responsive public-route Playwright proof" \ + npm --prefix website run test:e2e:responsive + run_step \ + "website responsive protected-route Playwright proof" \ + npm --prefix website run test:e2e:protected-responsive +fi run_step "website/server type-check" npm --prefix website/server run type-check run_step "website/server test suite" npm --prefix website/server test -- --run run_step "Content/Browser shell verification" npm --prefix Content/Browser run verify:shell diff --git a/website/README.md b/website/README.md index d74589b..655401b 100644 --- a/website/README.md +++ b/website/README.md @@ -131,6 +131,8 @@ scripts/run-hypertwist-web-surface-validation.sh npm run type-check npm run test npm run build +npm run test:e2e:responsive +npm run test:e2e:protected-responsive npm run check:runtime-readiness -- --frontend-env .env --server-env server/.env --health-url https://hypertwist.app ``` @@ -164,6 +166,9 @@ upstream Radix `use client` bundler warnings so real regressions remain visible; the one remaining accepted browser-build warning is the current upstream `@khronosgroup/gltf-viewer` `mikktspace_bg.wasm` Vite asset-resolution message, which does not currently break the owned shell verification or production build. +When responsive browser proof is needed too, pass +`scripts/run-hypertwist-web-surface-validation.sh --with-responsive-e2e` to +run the bounded Playwright public-route and protected-route viewport checks. Repo-owned structural-tool posture: diff --git a/website/package-lock.json b/website/package-lock.json index 355713f..34335dc 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -16,6 +16,7 @@ "supertokens-auth-react": "^0.51.2" }, "devDependencies": { + "@playwright/test": "^1.61.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", @@ -600,6 +601,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@remix-run/router": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", @@ -2105,6 +2122,53 @@ "dev": true, "license": "ISC" }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", diff --git a/website/package.json b/website/package.json index 9edafcd..2d74b63 100644 --- a/website/package.json +++ b/website/package.json @@ -10,6 +10,10 @@ "preview": "vite preview", "type-check": "tsc --noEmit", "test": "vitest run", + "test:e2e:responsive": "playwright test tests/e2e/responsive-public-pages.spec.ts --reporter=line", + "test:e2e:responsive:list": "playwright test tests/e2e/responsive-public-pages.spec.ts --list", + "test:e2e:protected-responsive": "playwright test tests/e2e/responsive-protected-app-routes.spec.ts --reporter=line", + "test:e2e:protected-responsive:list": "playwright test tests/e2e/responsive-protected-app-routes.spec.ts --list", "render:sitemap": "node scripts/render-public-sitemap.mjs", "check:runtime-readiness": "node scripts/check-runtime-readiness.mjs", "render:same-origin-deployment": "node scripts/render-same-origin-deployment.mjs", @@ -26,6 +30,7 @@ "supertokens-auth-react": "^0.51.2" }, "devDependencies": { + "@playwright/test": "^1.61.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", diff --git a/website/playwright.config.ts b/website/playwright.config.ts new file mode 100644 index 0000000..71156fc --- /dev/null +++ b/website/playwright.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from '@playwright/test' + +const configuredBaseUrl = process.env.PLAYWRIGHT_BASE_URL +const baseURL = configuredBaseUrl || 'http://127.0.0.1:4173' + +export default defineConfig({ + testDir: 'tests/e2e', + retries: 0, + use: { + baseURL, + headless: true, + actionTimeout: 10_000, + navigationTimeout: 20_000, + viewport: { width: 1440, height: 900 }, + locale: 'en-US', + timezoneId: 'UTC', + colorScheme: 'dark', + reducedMotion: 'reduce', + }, + webServer: configuredBaseUrl + ? undefined + : { + command: 'npm run build && npm run preview -- --host 127.0.0.1 --port 4173 --strictPort', + url: baseURL, + timeout: 180_000, + reuseExistingServer: !process.env.CI, + }, + reporter: [['list']], +}) diff --git a/website/src/site-data.ts b/website/src/site-data.ts index dcc8c57..a7d2799 100644 --- a/website/src/site-data.ts +++ b/website/src/site-data.ts @@ -114,8 +114,8 @@ export const browserDesktopRealityCards = [ posture: 'Shipped today versus still gated', description: 'HyperTwist already ships meaningful desktop control/settings ownership, but it does not market unfinished XR/controller widening as complete.', bullets: [ - 'Classic keyboard, mouse/touch classic-cube control, viewer camera settings, immersive presets, and dedicated-family selector/view ownership are real today.', - 'The public and protected manual already expose that shipped control roster instead of hiding it behind vague “customizable later” language.', + 'Classic keyboard, mouse/touch classic-cube control, viewer camera settings, immersive presets, and dedicated-family selector, session, scene, and view ownership are real today.', + 'The native operator surfaces now also expose concrete Magic120Cell and MagicCube5D session-surface, interactive-scene, persistence-boundary, and state-semantics ownership instead of hiding that proof behind softer settings summaries.', 'OpenXR/controller runtime widening and polished rebinding/preferences remain explicitly unfinished behind the current desktop-hosted No-Go boundary.', ], action: { @@ -190,7 +190,7 @@ export const productSurfaceMatrixRows = [ description: 'This remains the real product center for training quality, packaged proof, replay, recognition, and higher-dimensional runtime ownership.', owns: [ 'Recognition, correction, replay, coaching, analytics, and packaged training behavior.', - 'Dedicated-family Magic120Cell and MagicCube5D runtime-state, projection, and persistence ownership.', + 'Dedicated-family Magic120Cell and MagicCube5D runtime-state, session/scene, projection, and persistence ownership.', 'The device, package, and runtime lane that public and protected browser surfaces deliberately do not absorb.', ], doesNotClaim: [ @@ -466,6 +466,7 @@ export const higherDimensionalRuntimeGuideCards = [ bullets: [ 'Launch the dedicated Magic120Cell training map from the desktop runtime.', 'Use the current symmetry, focus, and logical-visibility posture as the authoritative first-party runtime owner.', + 'The native operator and training surfaces now also expose the dedicated session surface, interactive-scene surface, persistence boundary, and family-owned state semantics for this lane explicitly.', 'The native operator/training surfaces can now also recall the latest persisted generated-mode selector posture for this family instead of hiding it behind the browser shell.', 'Treat the public website as documentation and rollout support, not as the runtime that executes this family.', ], @@ -476,6 +477,7 @@ export const higherDimensionalRuntimeGuideCards = [ bullets: [ 'Launch the dedicated MagicCube5D training map from the desktop runtime.', 'Use the current projection-distance, stereo, and visibility defaults as the bounded first-party runtime posture.', + 'The native operator and training surfaces now also expose the dedicated session surface, interactive-scene surface, persistence boundary, and family-owned state semantics for this lane explicitly.', 'The native operator/training surfaces can now also recall the latest persisted generated-mode selector posture for this family instead of forcing a blind restart.', 'Keep this lane described honestly as packaged native behavior rather than browser simulator parity.', ], @@ -507,7 +509,7 @@ export const inputAndDevicePostureCards = [ bullets: [ 'Magic120Cell keeps symmetry, logical-visibility depth, and center-cell focus posture explicit.', 'MagicCube5D keeps projection-distance, stereo, face-visibility, and focus posture explicit.', - 'The desktop operator/training lane now also surfaces these dedicated-family view owners together with current camera-settings ownership.', + 'The desktop operator and training lane now also surfaces these dedicated-family view owners together with exact session-surface, interactive-scene, persistence-boundary, and state-semantics ownership.', 'The same native surfaces now expose selector recall from the latest persisted generated-mode launch request when that launch history exists.', 'These controls belong to the dedicated-family desktop runtime lanes, not to the public website.', ], @@ -575,6 +577,7 @@ export const controlProfileRosterCards = [ 'The current scenic immersive roster includes 3 immersive-intensity presets and 3 reduced-distraction presets.', 'Magic120Cell currently exposes 4 selector options alongside its symmetry, focus, and visibility owners.', 'MagicCube5D currently exposes 4 selector options alongside its projection-distance, stereo, focus, and visibility owners.', + 'Both higher-dimensional families now also surface their dedicated session, interactive-scene, persistence-boundary, and state-semantics ownership directly in the native diagnostics lane.', ], }, { @@ -582,6 +585,7 @@ export const controlProfileRosterCards = [ description: 'Current continuity is real, but it is still narrower than a finished global preferences and controller-rebinding suite.', bullets: [ 'The native operator and training surfaces can recall the latest persisted generated-mode selector posture when a structurally valid launch request exists.', + 'Those same native surfaces now also prove which family-specific persistence boundary and state-semantics contract currently owns the higher-dimensional settings lane.', 'Viewer camera settings and immersive-presence settings are already first-party owned in the desktop runtime.', 'Finished headset-specific controller rebinding and broad VR onboarding remain outside the current desktop-hosted No-Go lane unless a later reopen proves the need.', ], @@ -1025,6 +1029,11 @@ export const changelogEntries = [ title: 'Public pages now explain what each route is actually for', details: 'The homepage, docs, and resources routes now share a first-party public route atlas explaining what each major public page owns: product truth, onboarding, launch readiness, pricing, download, support, release notes, and the legal/distribution surfaces are now easier to navigate as one professional operator manual instead of a flatter marketing shell.', }, + { + date: 'June 28, 2026', + title: 'Native higher-dimensional settings proof now exposes family session and persistence seams', + details: 'The desktop operator and training diagnostics now render exact Magic120Cell and MagicCube5D session-surface, interactive-scene, persistence-boundary, and state-semantics ownership instead of only broad view-profile wording, and the maintained Windows validation lane re-exported focused browser proof green for the training-panel, coach-dashboard, and structured dashboard settings seams.', + }, { date: 'June 27, 2026', title: 'Protected launch status now has its own signed-in authority route', @@ -1168,7 +1177,7 @@ export const changelogEntries = [ { date: 'June 24, 2026', title: 'Signed-in operator shell now mirrors the native control roster', - details: 'The protected dashboard, browser-access, and account routes now carry the same shipped desktop control/settings roster exposed by the public manual, including the classic-wca-keyboard/v1 profile, scenic immersive presets, dedicated-family selector/view ownership, selector-recall continuity, and the explicit unfinished XR/controller boundary.', + details: 'The protected dashboard, browser-access, and account routes now carry the same shipped desktop control/settings roster exposed by the public manual, including the classic-wca-keyboard/v1 profile, scenic immersive presets, dedicated-family selector/view ownership, selector-recall continuity, later dedicated-family session/scene/persistence proof, and the explicit unfinished XR/controller boundary.', }, { date: 'June 24, 2026', @@ -1270,7 +1279,7 @@ export const supportFaqs = [ }, { question: 'Can I already customize controls and higher-dimensional view posture?', - answer: 'Partly. The current desktop runtime already owns the classic keyboard profile, viewer camera settings, immersive-presence settings, and higher-dimensional projection, focus, stereo, symmetry, and visibility defaults, and the native operator surfaces can now recall selector posture from the latest persisted generated-mode launch request. A broader polished controller rebinding and preferences layer remains outside the current desktop-hosted No-Go lane unless a later reopen proves the need.', + answer: 'Partly. The current desktop runtime already owns the classic keyboard profile, viewer camera settings, immersive-presence settings, and higher-dimensional projection, focus, stereo, symmetry, and visibility defaults. The native operator surfaces now also expose which Magic120Cell or MagicCube5D session surface, interactive scene, persistence boundary, and state-semantics contract currently owns that settings lane, and they can recall selector posture from the latest persisted generated-mode launch request. A broader polished controller rebinding and preferences layer remains outside the current desktop-hosted No-Go lane unless a later reopen proves the need.', }, { question: 'Do higher-dimensional selector choices persist between sessions?', diff --git a/website/src/styles/global.css b/website/src/styles/global.css index af803a4..69ecaac 100644 --- a/website/src/styles/global.css +++ b/website/src/styles/global.css @@ -152,6 +152,14 @@ img { justify-content: center; } +.button-row { + flex-wrap: wrap; +} + +.button-row > * { + min-width: 0; +} + .site-nav__link, .app-nav__link { padding: 0.7rem 0.95rem; @@ -246,6 +254,8 @@ img { .auth-card__subtitle { color: var(--ht-muted); line-height: 1.7; + overflow-wrap: anywhere; + word-break: break-word; } .hero-panel, @@ -350,6 +360,7 @@ img { .surface-matrix > *, .panel-grid > * { flex: 1 1 280px; + min-width: 0; } .card, @@ -428,6 +439,12 @@ img { color: var(--ht-muted); } +.list li, +.auth-card__footer { + overflow-wrap: anywhere; + word-break: break-word; +} + .feature-band { flex-wrap: wrap; } diff --git a/website/tests/e2e/helpers/layout.ts b/website/tests/e2e/helpers/layout.ts new file mode 100644 index 0000000..01e0143 --- /dev/null +++ b/website/tests/e2e/helpers/layout.ts @@ -0,0 +1,12 @@ +import { expect, type Page } from '@playwright/test' + +export async function expectNoHorizontalOverflow(page: Page) { + const metrics = await page.evaluate(() => ({ + documentWidth: document.documentElement.scrollWidth, + viewportWidth: document.documentElement.clientWidth, + bodyWidth: document.body.scrollWidth, + })) + + expect(metrics.documentWidth).toBeLessThanOrEqual(metrics.viewportWidth + 1) + expect(metrics.bodyWidth).toBeLessThanOrEqual(metrics.viewportWidth + 1) +} diff --git a/website/tests/e2e/helpers/navigation.ts b/website/tests/e2e/helpers/navigation.ts new file mode 100644 index 0000000..4401743 --- /dev/null +++ b/website/tests/e2e/helpers/navigation.ts @@ -0,0 +1,50 @@ +import type { Page } from '@playwright/test' + +type WaitUntilState = 'load' | 'domcontentloaded' | 'networkidle' | 'commit' + +type RouteNavigationOptions = { + waitUntil?: WaitUntilState + timeoutMs?: number +} + +const PLATFORM_AUTH_STORAGE_KEY = 'hypertwist.platform.user.v1' +const E2E_FALLBACK_USER = { + id: 'e2e_operator_user', + name: 'E2E Operator', + email: 'e2e.operator@hypertwist.local', + authMethod: 'email', + plan: 'free', +} + +async function seedFallbackAuthForProtectedRoutes(page: Page) { + await page.addInitScript( + ({ storageKey, userJson }) => { + try { + if (!window.localStorage.getItem(storageKey)) { + window.localStorage.setItem(storageKey, userJson) + } + } catch { + // Fail-open for storage-restricted contexts. + } + }, + { + storageKey: PLATFORM_AUTH_STORAGE_KEY, + userJson: JSON.stringify(E2E_FALLBACK_USER), + }, + ) +} + +export async function gotoAppRoute( + page: Page, + routePath: string, + options: RouteNavigationOptions = {}, +) { + const normalizedPath = routePath.startsWith('/') ? routePath : `/${routePath}` + if (normalizedPath.startsWith('/app')) { + await seedFallbackAuthForProtectedRoutes(page) + } + await page.goto(normalizedPath, { + waitUntil: options.waitUntil ?? 'domcontentloaded', + timeout: options.timeoutMs ?? 30_000, + }) +} diff --git a/website/tests/e2e/responsive-protected-app-routes.spec.ts b/website/tests/e2e/responsive-protected-app-routes.spec.ts new file mode 100644 index 0000000..b71a94d --- /dev/null +++ b/website/tests/e2e/responsive-protected-app-routes.spec.ts @@ -0,0 +1,63 @@ +import { expect, test, type Page } from '@playwright/test' +import { gotoAppRoute } from './helpers/navigation' +import { expectNoHorizontalOverflow } from './helpers/layout' + +type ProtectedRouteExpectation = { + path: string + marker: string +} + +const responsiveProtectedRoutes: readonly ProtectedRouteExpectation[] = [ + { + path: '/app', + marker: 'Desktop-link pairing', + }, + { + path: '/app/launch-status', + marker: 'Protected launch status', + }, + { + path: '/app/downloads', + marker: 'Release targets', + }, + { + path: '/app/browser-access', + marker: 'Current browser posture', + }, + { + path: '/app/account', + marker: 'Session profile', + }, + { + path: '/app/notices', + marker: 'Distribution notices', + }, +] as const + +async function assertResponsiveProtectedRoute(page: Page, route: ProtectedRouteExpectation) { + await gotoAppRoute(page, route.path, { waitUntil: 'domcontentloaded', timeoutMs: 45_000 }) + + await expect(page.getByRole('heading', { level: 1, name: 'HyperTwist dashboard' })).toBeVisible() + await expect(page.getByText(route.marker, { exact: true }).first()).toBeVisible() + await expectNoHorizontalOverflow(page) +} + +test.describe('responsive protected app routes (mobile)', () => { + test.use({ viewport: { width: 390, height: 844 } }) + + for (const route of responsiveProtectedRoutes) { + test(`${route.path} renders without horizontal overflow`, async ({ page }) => { + await assertResponsiveProtectedRoute(page, route) + }) + } +}) + +test.describe('responsive protected app routes (tablet)', () => { + test.use({ viewport: { width: 820, height: 1180 } }) + + for (const route of responsiveProtectedRoutes) { + test(`${route.path} renders without horizontal overflow`, async ({ page }) => { + await assertResponsiveProtectedRoute(page, route) + }) + } +}) diff --git a/website/tests/e2e/responsive-public-pages.spec.ts b/website/tests/e2e/responsive-public-pages.spec.ts new file mode 100644 index 0000000..2aa8d84 --- /dev/null +++ b/website/tests/e2e/responsive-public-pages.spec.ts @@ -0,0 +1,78 @@ +import { expect, test, type Page } from '@playwright/test' +import { gotoAppRoute } from './helpers/navigation' +import { expectNoHorizontalOverflow } from './helpers/layout' + +type PublicRouteExpectation = { + path: string + heading: string + cta?: string +} + +const responsivePublicRoutes: readonly PublicRouteExpectation[] = [ + { + path: '/', + heading: 'HyperTwist turns cube practice into a real operator-grade training stack.', + cta: 'Download desktop app', + }, + { + path: '/about', + heading: 'A training stack serious enough for higher-dimensional cubing.', + }, + { + path: '/resources', + heading: 'Resources that explain the product without leaking operator-only internals.', + }, + { + path: '/pricing', + heading: 'Pricing that matches the actual delivery model.', + }, + { + path: '/download', + heading: 'Download the desktop build and pair it with your browser account.', + }, + { + path: '/support', + heading: 'Support for rollout, downloads, pricing, and browser-to-desktop access.', + }, + { + path: '/register', + heading: 'Create a HyperTwist account', + cta: 'Create account', + }, +] as const + +async function assertResponsivePublicRoute(page: Page, route: PublicRouteExpectation) { + await gotoAppRoute(page, route.path, { waitUntil: 'domcontentloaded', timeoutMs: 45_000 }) + + await expect(page.getByRole('heading', { level: 1, name: route.heading })).toBeVisible() + if (route.cta) { + const button = page.getByRole('button', { name: route.cta }) + if ((await button.count()) > 0) { + await expect(button.first()).toBeVisible() + } else { + await expect(page.getByRole('link', { name: route.cta }).first()).toBeVisible() + } + } + + await expectNoHorizontalOverflow(page) +} + +test.describe('responsive public routes (mobile)', () => { + test.use({ viewport: { width: 390, height: 844 } }) + + for (const route of responsivePublicRoutes) { + test(`${route.path} renders without horizontal overflow`, async ({ page }) => { + await assertResponsivePublicRoute(page, route) + }) + } +}) + +test.describe('responsive public routes (tablet)', () => { + test.use({ viewport: { width: 820, height: 1180 } }) + + for (const route of responsivePublicRoutes) { + test(`${route.path} renders without horizontal overflow`, async ({ page }) => { + await assertResponsivePublicRoute(page, route) + }) + } +})