feat: harden browser surfaces and settings ownership
This commit is contained in:
parent
42fcd54ecc
commit
e2d3438741
17 changed files with 890 additions and 20 deletions
|
|
@ -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")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
64
website/package-lock.json
generated
64
website/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
29
website/playwright.config.ts
Normal file
29
website/playwright.config.ts
Normal file
|
|
@ -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']],
|
||||
})
|
||||
|
|
@ -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?',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
12
website/tests/e2e/helpers/layout.ts
Normal file
12
website/tests/e2e/helpers/layout.ts
Normal file
|
|
@ -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)
|
||||
}
|
||||
50
website/tests/e2e/helpers/navigation.ts
Normal file
50
website/tests/e2e/helpers/navigation.ts
Normal file
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
63
website/tests/e2e/responsive-protected-app-routes.spec.ts
Normal file
63
website/tests/e2e/responsive-protected-app-routes.spec.ts
Normal file
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
})
|
||||
78
website/tests/e2e/responsive-public-pages.spec.ts
Normal file
78
website/tests/e2e/responsive-public-pages.spec.ts
Normal file
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue