Harden validation, tooling, and public product surfaces
This commit is contained in:
parent
9c93020c69
commit
a1d40d1232
29 changed files with 7020 additions and 5015 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -13,6 +13,8 @@ website/dist/
|
|||
website/node_modules/
|
||||
website/server/node_modules/
|
||||
website/server/data/
|
||||
tools/sentrux/bin/*
|
||||
!tools/sentrux/bin/.gitkeep
|
||||
|
||||
# Sensitive or reference-only material.
|
||||
docs/refs/
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,968 @@
|
|||
#include "HyperTwistRecognition/HyperTwistRecognitionTypes.h"
|
||||
|
||||
namespace HyperTwistRecognitionTypesInternal
|
||||
{
|
||||
bool AreAllStringsPopulated(const TArray<FString>& Values)
|
||||
{
|
||||
for (const FString& Value : Values)
|
||||
{
|
||||
if (Value.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename ItemType>
|
||||
bool AreAllItemsStructurallyValid(const TArray<ItemType>& Items)
|
||||
{
|
||||
for (const ItemType& Item : Items)
|
||||
{
|
||||
if (!Item.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechNativeCaptureRouteShellProfile::IsStructurallyValid() const
|
||||
{
|
||||
return !NativeCaptureRouteShellProfileId.IsEmpty()
|
||||
&& !ShellKind.IsEmpty()
|
||||
&& !OwnershipSummarySurfaceMode.IsEmpty()
|
||||
&& !PreparationSurfaceMode.IsEmpty()
|
||||
&& !SessionReopenSurfaceMode.IsEmpty()
|
||||
&& !RouteInspectActionId.IsEmpty()
|
||||
&& !PreparationRetryActionId.IsEmpty()
|
||||
&& !SessionReopenActionId.IsEmpty()
|
||||
&& !PermissionDependencyActionId.IsEmpty()
|
||||
&& Panels.Num() > 0
|
||||
&& ActionBindings.Num() > 0
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Panels)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechNativeCaptureRouteShellState::IsStructurallyValid() const
|
||||
{
|
||||
if (NativeCaptureRouteShellProfileId.IsEmpty()
|
||||
|| NativeCaptureRouteWorkflowProfileId.IsEmpty()
|
||||
|| ActiveProviderProfileId.IsEmpty()
|
||||
|| ActiveServiceLaneId.IsEmpty()
|
||||
|| WorkflowStateId.IsEmpty()
|
||||
|| CaptureRouteStateId.IsEmpty()
|
||||
|| PermissionStateId.IsEmpty()
|
||||
|| LatestSourceKind.IsEmpty()
|
||||
|| StatusLine.IsEmpty()
|
||||
|| DetailLine.IsEmpty()
|
||||
|| ActiveIssueCount < 0
|
||||
|| AvailableActionIds.Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ActiveIssueCount != Issues.Num())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Entries)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Issues)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(AvailableActionIds);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechUsageCostDashboardShellProfile::IsStructurallyValid() const
|
||||
{
|
||||
return !DashboardShellProfileId.IsEmpty()
|
||||
&& !ShellKind.IsEmpty()
|
||||
&& !UsageSurfaceMode.IsEmpty()
|
||||
&& !CostSurfaceMode.IsEmpty()
|
||||
&& !EscalationBannerMode.IsEmpty()
|
||||
&& !RefreshActionId.IsEmpty()
|
||||
&& !RouteInspectActionId.IsEmpty()
|
||||
&& !BudgetInspectActionId.IsEmpty()
|
||||
&& Panels.Num() > 0
|
||||
&& ActionBindings.Num() > 0
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Panels)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechUsageCostDashboardShellState::IsStructurallyValid() const
|
||||
{
|
||||
if (DashboardShellProfileId.IsEmpty()
|
||||
|| ActiveProviderProfileId.IsEmpty()
|
||||
|| ActiveProviderDisplayLabel.IsEmpty()
|
||||
|| ActiveServiceLaneId.IsEmpty()
|
||||
|| ActiveRouteStateId.IsEmpty()
|
||||
|| UsageAggregationWindowId.IsEmpty()
|
||||
|| CostAggregationWindowId.IsEmpty()
|
||||
|| UsageMeterKind.IsEmpty()
|
||||
|| CurrencyCode.IsEmpty()
|
||||
|| StatusLine.IsEmpty()
|
||||
|| DetailLine.IsEmpty()
|
||||
|| LatestUsageEventId.IsEmpty()
|
||||
|| LatestCostEventId.IsEmpty()
|
||||
|| LatestQuotaStateId.IsEmpty()
|
||||
|| LatestRouteDecisionId.IsEmpty()
|
||||
|| DisplayedUsageQuantity < 0.0f
|
||||
|| DisplayedEstimatedCostUsd < 0.0f
|
||||
|| DisplayedReportedCostUsd < 0.0f
|
||||
|| RemainingEstimatedSpendUsd < 0.0f
|
||||
|| RemainingRequestCount < 0
|
||||
|| RemainingAudioSeconds < 0
|
||||
|| ActiveIssueCount < 0
|
||||
|| AvailableActionIds.Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ActiveIssueCount != Issues.Num())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Issues)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(AvailableActionIds);
|
||||
}
|
||||
|
||||
bool FHyperTwistVisionShellProfile::IsStructurallyValid() const
|
||||
{
|
||||
return !ShellProfileId.IsEmpty()
|
||||
&& !ShellKind.IsEmpty()
|
||||
&& !DefaultLocaleCode.IsEmpty()
|
||||
&& !FontReviewProfileId.IsEmpty()
|
||||
&& FontReviewProfileDefinition.IsStructurallyValid()
|
||||
&& SupportedFontReviews.Num() > 0
|
||||
&& SupportedLocales.Num() > 0
|
||||
&& Panels.Num() > 0
|
||||
&& ActionBindings.Num() > 0
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedLocales)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedFontReviews)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Panels)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||
}
|
||||
|
||||
bool FHyperTwistVisionSolveExplanationProfile::IsStructurallyValid() const
|
||||
{
|
||||
return !ExplanationProfileId.IsEmpty()
|
||||
&& !PuzzleId.IsEmpty()
|
||||
&& !RecommendationMode.IsEmpty()
|
||||
&& !LocaleGuidanceMode.IsEmpty()
|
||||
&& !DefaultLocaleCode.IsEmpty()
|
||||
&& !StartingOrientationHint.IsEmpty()
|
||||
&& !FontReviewProfileId.IsEmpty()
|
||||
&& FontReviewProfileDefinition.IsStructurallyValid()
|
||||
&& SupportedFontReviews.Num() > 0
|
||||
&& SupportedLocales.Num() > 0
|
||||
&& Steps.Num() > 0
|
||||
&& ActionBindings.Num() > 0
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedLocales)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedFontReviews)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Steps)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||
}
|
||||
|
||||
bool FHyperTwistVisionCorrectionState::IsStructurallyValid() const
|
||||
{
|
||||
if (CorrectionProfileId.IsEmpty()
|
||||
|| MissingFaceCount < 0
|
||||
|| ContradictionCount < 0
|
||||
|| ContradictionCount != Contradictions.Num()
|
||||
|| ResolvedCorrectionCount < 0
|
||||
|| ResolvedCorrectionCount != ResolutionLedger.Num())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bCorrectionRequired
|
||||
&& (ActiveTargetFaceId.IsEmpty()
|
||||
|| !ActiveTarget.IsStructurallyValid()
|
||||
|| ActiveTargetFaceId != ActiveTarget.FaceId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(PendingTargets)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Contradictions)
|
||||
&& (LastResolution.ResolutionId.IsEmpty() || LastResolution.IsStructurallyValid())
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ResolutionLedger);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechProviderRoutingPolicy::IsStructurallyValid() const
|
||||
{
|
||||
return !ProviderRoutingPolicyId.IsEmpty()
|
||||
&& !PolicyKind.IsEmpty()
|
||||
&& !RouteSelectionMode.IsEmpty()
|
||||
&& !FallbackMode.IsEmpty()
|
||||
&& !WorkflowPolicyState.IsEmpty()
|
||||
&& !FailureEscalationMode.IsEmpty()
|
||||
&& !UserOverridePosture.IsEmpty()
|
||||
&& EligibleProviderClasses.Num() > 0
|
||||
&& EligibleEndpointClasses.Num() > 0
|
||||
&& RequiredCapabilityFlags.Num() > 0
|
||||
&& SupportedTaskKinds.Num() > 0
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(EligibleProviderClasses)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(EligibleEndpointClasses)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(RequiredCapabilityFlags)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(SupportedTaskKinds);
|
||||
}
|
||||
|
||||
namespace HyperTwistRecognitionTypeValidation
|
||||
{
|
||||
template <typename TStruct>
|
||||
bool AreStructurallyValidEntries(const TArray<TStruct>& Values)
|
||||
{
|
||||
return HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Values);
|
||||
}
|
||||
|
||||
bool AreNonEmptyStrings(const TArray<FString>& Values)
|
||||
{
|
||||
return HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(Values);
|
||||
}
|
||||
|
||||
bool AreSpeechModelPayloadsValid(const TArray<FHyperTwistSpeechModelPayloadDescriptor>& Payloads)
|
||||
{
|
||||
return AreStructurallyValidEntries(Payloads);
|
||||
}
|
||||
|
||||
bool HasValidSpeechSessionCore(const FHyperTwistSpeechSessionConfig& Session)
|
||||
{
|
||||
return !Session.SessionId.IsEmpty()
|
||||
&& !Session.ListeningContractId.IsEmpty()
|
||||
&& !Session.InputRouteId.IsEmpty()
|
||||
&& !Session.AudioEncoding.IsEmpty()
|
||||
&& Session.SampleRateHz > 0
|
||||
&& Session.ChannelCount > 0
|
||||
&& !Session.TaskKind.IsEmpty()
|
||||
&& Session.RequiredModelPayloads.Num() > 0
|
||||
&& Session.VadPolicy.IsStructurallyValid()
|
||||
&& Session.OrchestrationProfile.IsStructurallyValid();
|
||||
}
|
||||
|
||||
bool HasValidSpeechSessionProfiles(const FHyperTwistSpeechSessionConfig& Session)
|
||||
{
|
||||
return !Session.MicrophoneShellProfileId.IsEmpty()
|
||||
&& Session.MicrophoneShellProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.DevicePermissionWorkflowProfileId.IsEmpty()
|
||||
&& Session.DevicePermissionWorkflowProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.NativeCaptureRouteWorkflowProfileId.IsEmpty()
|
||||
&& Session.NativeCaptureRouteWorkflowProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.NativeCaptureRouteShellProfileId.IsEmpty()
|
||||
&& Session.NativeCaptureRouteShellProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.ExternalDictationShellProfileId.IsEmpty()
|
||||
&& Session.ExternalDictationShellProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.ProviderProfileId.IsEmpty()
|
||||
&& Session.ProviderProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.ByokCustodyProfileId.IsEmpty()
|
||||
&& Session.ByokCustodyProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.ProviderRoutingPolicyId.IsEmpty()
|
||||
&& Session.ProviderRoutingPolicyDefinition.IsStructurallyValid()
|
||||
&& Session.ProviderRouteDecision.IsStructurallyValid()
|
||||
&& !Session.UsageCostAccountingProfileId.IsEmpty()
|
||||
&& Session.UsageCostAccountingProfileDefinition.IsStructurallyValid()
|
||||
&& Session.UsageEventTemplate.IsStructurallyValid()
|
||||
&& Session.CostEventTemplate.IsStructurallyValid()
|
||||
&& !Session.UsageCostDashboardShellProfileId.IsEmpty()
|
||||
&& Session.UsageCostDashboardShellProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.UsageCostHistoryExportShellProfileId.IsEmpty()
|
||||
&& Session.UsageCostHistoryExportShellProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.ProviderReceiptReviewShellProfileId.IsEmpty()
|
||||
&& Session.ProviderReceiptReviewShellProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.ProviderBillingSettlementShellProfileId.IsEmpty()
|
||||
&& Session.ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.ProviderSettlementExceptionShellProfileId.IsEmpty()
|
||||
&& Session.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid()
|
||||
&& !Session.PayloadCustodyProfileId.IsEmpty()
|
||||
&& Session.PayloadCustodyProfileDefinition.IsStructurallyValid();
|
||||
}
|
||||
|
||||
bool HasValidTranscriptCore(const FHyperTwistSpeechTranscriptResult& Transcript)
|
||||
{
|
||||
return !Transcript.SessionId.IsEmpty()
|
||||
&& !Transcript.UtteranceId.IsEmpty()
|
||||
&& !Transcript.TaskKind.IsEmpty()
|
||||
&& (!Transcript.TranscriptText.IsEmpty() || Transcript.Segments.Num() > 0)
|
||||
&& !Transcript.OrchestrationProfileId.IsEmpty()
|
||||
&& !Transcript.ServiceLaneId.IsEmpty()
|
||||
&& Transcript.RequestedBatchSize > 0
|
||||
&& Transcript.ProcessedClipCount > 0
|
||||
&& Transcript.AppliedBatchCollectionWindowMs > 0
|
||||
&& Transcript.LanguageProbability >= 0.0f
|
||||
&& !Transcript.AppliedPromptRoutingModeId.IsEmpty()
|
||||
&& !Transcript.AppliedRetrievalContextLaneId.IsEmpty();
|
||||
}
|
||||
|
||||
bool AreTranscriptSegmentsValid(const TArray<FHyperTwistSpeechTranscriptSegment>& Segments)
|
||||
{
|
||||
return AreStructurallyValidEntries(Segments);
|
||||
}
|
||||
|
||||
bool HasValidSpeechServiceHealthCore(const FHyperTwistSpeechServiceHealth& Health)
|
||||
{
|
||||
return !Health.ProviderLabel.IsEmpty()
|
||||
&& !Health.ServiceVersion.IsEmpty()
|
||||
&& !Health.ProviderProfileId.IsEmpty()
|
||||
&& Health.ProviderProfileDefinition.IsStructurallyValid()
|
||||
&& !Health.ByokCustodyProfileId.IsEmpty()
|
||||
&& Health.ByokCustodyProfileDefinition.IsStructurallyValid()
|
||||
&& !Health.ProviderRoutingPolicyId.IsEmpty()
|
||||
&& Health.ProviderRoutingPolicyDefinition.IsStructurallyValid()
|
||||
&& Health.ProviderRouteDecision.IsStructurallyValid()
|
||||
&& !Health.UsageCostAccountingProfileId.IsEmpty()
|
||||
&& Health.UsageCostAccountingProfileDefinition.IsStructurallyValid()
|
||||
&& Health.LatestUsageEvent.IsStructurallyValid()
|
||||
&& Health.LatestCostEvent.IsStructurallyValid()
|
||||
&& Health.QuotaRateLimitState.IsStructurallyValid()
|
||||
&& !Health.PayloadCustodyProfileId.IsEmpty()
|
||||
&& Health.PayloadCustodyProfileDefinition.IsStructurallyValid();
|
||||
}
|
||||
|
||||
bool HasValidExternalDictationShellProfileCore(
|
||||
const FHyperTwistSpeechExternalDictationShellProfile& Profile)
|
||||
{
|
||||
return !Profile.ExternalDictationShellProfileId.IsEmpty()
|
||||
&& !Profile.ShellKind.IsEmpty()
|
||||
&& !Profile.TranscriptHistorySurfaceMode.IsEmpty()
|
||||
&& !Profile.OutputRoutingSurfaceMode.IsEmpty()
|
||||
&& !Profile.PostProcessOverlaySurfaceMode.IsEmpty()
|
||||
&& !Profile.GlobalHotkeySurfaceMode.IsEmpty()
|
||||
&& !Profile.InputDeviceSurfaceMode.IsEmpty()
|
||||
&& !Profile.OutputDeviceSurfaceMode.IsEmpty()
|
||||
&& !Profile.MuteSurfaceMode.IsEmpty()
|
||||
&& !Profile.MicrophoneModeSurfaceMode.IsEmpty()
|
||||
&& !Profile.LocalModelCatalogSurfaceMode.IsEmpty()
|
||||
&& !Profile.ModelIntegritySurfaceMode.IsEmpty()
|
||||
&& !Profile.ModelUnloadSurfaceMode.IsEmpty()
|
||||
&& !Profile.StartCaptureActionId.IsEmpty()
|
||||
&& !Profile.CancelCaptureActionId.IsEmpty()
|
||||
&& !Profile.CycleOutputRouteActionId.IsEmpty()
|
||||
&& !Profile.CopyTranscriptActionId.IsEmpty()
|
||||
&& !Profile.PasteTranscriptActionId.IsEmpty()
|
||||
&& !Profile.ScriptDispatchActionId.IsEmpty()
|
||||
&& !Profile.TogglePostProcessOverlayActionId.IsEmpty()
|
||||
&& !Profile.ReopenSpeechSessionActionId.IsEmpty()
|
||||
&& !Profile.RouteInspectActionId.IsEmpty()
|
||||
&& !Profile.InspectGlobalHotkeyActionId.IsEmpty()
|
||||
&& !Profile.CycleInputDeviceActionId.IsEmpty()
|
||||
&& !Profile.CycleOutputDeviceActionId.IsEmpty()
|
||||
&& !Profile.ToggleMuteWhileRecordingActionId.IsEmpty()
|
||||
&& !Profile.ToggleMicrophoneModeActionId.IsEmpty()
|
||||
&& !Profile.InspectLocalModelCatalogActionId.IsEmpty()
|
||||
&& !Profile.ReviewLocalModelIntegrityActionId.IsEmpty()
|
||||
&& !Profile.ReviewLocalModelUnloadPolicyActionId.IsEmpty()
|
||||
&& !Profile.PrimaryHotkeyBindingLabel.IsEmpty()
|
||||
&& !Profile.PostProcessHotkeyBindingLabel.IsEmpty()
|
||||
&& Profile.RetainedHistoryEntryLimit > 0
|
||||
&& Profile.Panels.Num() > 0
|
||||
&& Profile.ActionBindings.Num() > 0;
|
||||
}
|
||||
|
||||
bool HasValidExternalDictationShellStateCore(const FHyperTwistSpeechExternalDictationShellState& State)
|
||||
{
|
||||
return !State.ExternalDictationShellProfileId.IsEmpty()
|
||||
&& !State.ActiveProviderProfileId.IsEmpty()
|
||||
&& !State.ActiveServiceLaneId.IsEmpty()
|
||||
&& !State.SelectedOutputRouteId.IsEmpty()
|
||||
&& !State.TranscriptHistorySurfaceModeId.IsEmpty()
|
||||
&& !State.OutputRoutingSurfaceModeId.IsEmpty()
|
||||
&& !State.PostProcessOverlaySurfaceModeId.IsEmpty()
|
||||
&& !State.GlobalHotkeySurfaceModeId.IsEmpty()
|
||||
&& !State.InputDeviceSurfaceModeId.IsEmpty()
|
||||
&& !State.OutputDeviceSurfaceModeId.IsEmpty()
|
||||
&& !State.MuteSurfaceModeId.IsEmpty()
|
||||
&& !State.MicrophoneModeSurfaceModeId.IsEmpty()
|
||||
&& !State.LocalModelCatalogSurfaceModeId.IsEmpty()
|
||||
&& !State.ModelIntegritySurfaceModeId.IsEmpty()
|
||||
&& !State.ModelUnloadSurfaceModeId.IsEmpty()
|
||||
&& !State.LatestSourceKind.IsEmpty()
|
||||
&& !State.StatusLine.IsEmpty()
|
||||
&& !State.DetailLine.IsEmpty()
|
||||
&& !State.PrimaryHotkeyBindingLabel.IsEmpty()
|
||||
&& !State.PostProcessHotkeyBindingLabel.IsEmpty()
|
||||
&& !State.SelectedInputDeviceId.IsEmpty()
|
||||
&& !State.SelectedInputDeviceLabel.IsEmpty()
|
||||
&& !State.SelectedOutputDeviceId.IsEmpty()
|
||||
&& !State.SelectedOutputDeviceLabel.IsEmpty()
|
||||
&& !State.MicrophoneModeId.IsEmpty()
|
||||
&& !State.PayloadCustodyProfileId.IsEmpty()
|
||||
&& !State.SelectedPrimaryPayloadId.IsEmpty()
|
||||
&& State.HistoryEntryCount >= 0
|
||||
&& State.PostProcessedEntryCount >= 0
|
||||
&& State.SavedEntryCount >= 0
|
||||
&& State.ModelCatalogEntryCount >= 0
|
||||
&& State.OptionalModelCatalogEntryCount >= 0
|
||||
&& State.DownloadDeferredModelEntryCount >= 0
|
||||
&& State.IntegrityReviewRequiredEntryCount >= 0
|
||||
&& State.ActiveIssueCount >= 0
|
||||
&& State.OutputRouteOptions.Num() > 0
|
||||
&& State.InputDeviceOptions.Num() > 0
|
||||
&& State.OutputDeviceOptions.Num() > 0
|
||||
&& State.ModelCatalogEntries.Num() > 0
|
||||
&& State.AvailableActionIds.Num() > 0;
|
||||
}
|
||||
|
||||
bool HasConsistentExternalDictationShellStateCounts(const FHyperTwistSpeechExternalDictationShellState& State)
|
||||
{
|
||||
return State.HistoryEntryCount == State.HistoryEntries.Num()
|
||||
&& State.PostProcessedEntryCount <= State.HistoryEntryCount
|
||||
&& State.SavedEntryCount <= State.HistoryEntryCount
|
||||
&& State.ModelCatalogEntryCount == State.ModelCatalogEntries.Num()
|
||||
&& State.OptionalModelCatalogEntryCount <= State.ModelCatalogEntryCount
|
||||
&& State.DownloadDeferredModelEntryCount <= State.ModelCatalogEntryCount
|
||||
&& State.IntegrityReviewRequiredEntryCount <= State.ModelCatalogEntryCount;
|
||||
}
|
||||
|
||||
bool HasConsistentExternalDictationShellStateLatestEntry(const FHyperTwistSpeechExternalDictationShellState& State)
|
||||
{
|
||||
return State.HistoryEntryCount <= 0
|
||||
|| (!State.LatestHistoryEntryId.IsEmpty() && !State.LatestTranscriptText.IsEmpty());
|
||||
}
|
||||
|
||||
bool HasConsistentExternalDictationShellStateActiveIssue(const FHyperTwistSpeechExternalDictationShellState& State)
|
||||
{
|
||||
return State.ActiveIssueCount == State.Issues.Num()
|
||||
&& (State.ActiveIssueCount <= 0 || State.ActiveIssue.IsStructurallyValid());
|
||||
}
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechUsageCostHistoryExportShellState::IsStructurallyValid() const
|
||||
{
|
||||
if (HistoryExportShellProfileId.IsEmpty()
|
||||
|| ActiveProviderProfileId.IsEmpty()
|
||||
|| ActiveProviderDisplayLabel.IsEmpty()
|
||||
|| ActiveServiceLaneId.IsEmpty()
|
||||
|| SelectedHistoryWindowId.IsEmpty()
|
||||
|| ExportPreviewFormatId.IsEmpty()
|
||||
|| LatestSourceKind.IsEmpty()
|
||||
|| StatusLine.IsEmpty()
|
||||
|| DetailLine.IsEmpty()
|
||||
|| HistoryEntryCount < 0
|
||||
|| EstimateOnlyEntryCount < 0
|
||||
|| ProviderReceiptEntryCount < 0
|
||||
|| TotalUsageQuantity < 0.0f
|
||||
|| TotalEstimatedCostUsd < 0.0f
|
||||
|| TotalReportedCostUsd < 0.0f
|
||||
|| LowestRemainingEstimatedSpendUsd < 0.0f
|
||||
|| ActiveIssueCount < 0
|
||||
|| AvailableActionIds.Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return HistoryEntryCount == HistoryEntries.Num()
|
||||
&& (HistoryEntryCount <= 0 || !LatestHistoryEntryId.IsEmpty())
|
||||
&& (!bExportPreviewReady || !ExportPreviewText.IsEmpty())
|
||||
&& ActiveIssueCount == Issues.Num()
|
||||
&& (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(HistoryEntries)
|
||||
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechProviderReceiptReviewShellState::IsStructurallyValid() const
|
||||
{
|
||||
if (ReceiptReviewShellProfileId.IsEmpty()
|
||||
|| ActiveProviderProfileId.IsEmpty()
|
||||
|| ActiveProviderDisplayLabel.IsEmpty()
|
||||
|| ActiveServiceLaneId.IsEmpty()
|
||||
|| SelectedChargeWindowId.IsEmpty()
|
||||
|| PostedChargeInspectionModeId.IsEmpty()
|
||||
|| LatestSourceKind.IsEmpty()
|
||||
|| StatusLine.IsEmpty()
|
||||
|| DetailLine.IsEmpty()
|
||||
|| ReceiptEntryCount < 0
|
||||
|| ProviderPostedChargeCount < 0
|
||||
|| EstimateOnlyEntryCount < 0
|
||||
|| VarianceReviewEntryCount < 0
|
||||
|| TotalEstimatedCostUsd < 0.0f
|
||||
|| TotalReportedCostUsd < 0.0f
|
||||
|| TotalAbsoluteVarianceUsd < 0.0f
|
||||
|| HighestAbsoluteVarianceUsd < 0.0f
|
||||
|| ActiveIssueCount < 0
|
||||
|| AvailableActionIds.Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReceiptEntryCount == ReceiptEntries.Num()
|
||||
&& ProviderPostedChargeCount <= ReceiptEntryCount
|
||||
&& EstimateOnlyEntryCount <= ReceiptEntryCount
|
||||
&& VarianceReviewEntryCount <= ReceiptEntryCount
|
||||
&& (ReceiptEntryCount <= 0 || !LatestReceiptEntryId.IsEmpty())
|
||||
&& (!bReceiptSummaryReady || !ReceiptSummaryText.IsEmpty())
|
||||
&& ActiveIssueCount == Issues.Num()
|
||||
&& (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(ReceiptEntries)
|
||||
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechProviderBillingSettlementShellState::IsStructurallyValid() const
|
||||
{
|
||||
if (BillingSettlementShellProfileId.IsEmpty()
|
||||
|| ActiveProviderProfileId.IsEmpty()
|
||||
|| ActiveProviderDisplayLabel.IsEmpty()
|
||||
|| ActiveServiceLaneId.IsEmpty()
|
||||
|| SelectedSettlementWindowId.IsEmpty()
|
||||
|| SettlementSurfaceModeId.IsEmpty()
|
||||
|| InvoiceReconciliationModeId.IsEmpty()
|
||||
|| LatestSourceKind.IsEmpty()
|
||||
|| StatusLine.IsEmpty()
|
||||
|| DetailLine.IsEmpty()
|
||||
|| SettlementEntryCount < 0
|
||||
|| ProviderPostedChargeCount < 0
|
||||
|| ReconciliationReadyEntryCount < 0
|
||||
|| EstimateOnlyEntryCount < 0
|
||||
|| ManualReviewEntryCount < 0
|
||||
|| BlockedSettlementEntryCount < 0
|
||||
|| TotalEstimatedCostUsd < 0.0f
|
||||
|| TotalReportedCostUsd < 0.0f
|
||||
|| TotalSettlementDeltaUsd < 0.0f
|
||||
|| HighestSettlementDeltaUsd < 0.0f
|
||||
|| ActiveIssueCount < 0
|
||||
|| AvailableActionIds.Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return SettlementEntryCount == SettlementEntries.Num()
|
||||
&& ProviderPostedChargeCount <= SettlementEntryCount
|
||||
&& ReconciliationReadyEntryCount <= SettlementEntryCount
|
||||
&& EstimateOnlyEntryCount <= SettlementEntryCount
|
||||
&& ManualReviewEntryCount <= SettlementEntryCount
|
||||
&& BlockedSettlementEntryCount <= SettlementEntryCount
|
||||
&& (SettlementEntryCount <= 0 || !LatestSettlementEntryId.IsEmpty())
|
||||
&& (!bSettlementSummaryReady || !SettlementSummaryText.IsEmpty())
|
||||
&& ActiveIssueCount == Issues.Num()
|
||||
&& (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(SettlementEntries)
|
||||
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechProviderSettlementExceptionShellState::IsStructurallyValid() const
|
||||
{
|
||||
if (SettlementExceptionShellProfileId.IsEmpty()
|
||||
|| ActiveProviderProfileId.IsEmpty()
|
||||
|| ActiveProviderDisplayLabel.IsEmpty()
|
||||
|| ActiveServiceLaneId.IsEmpty()
|
||||
|| SelectedSettlementWindowId.IsEmpty()
|
||||
|| ExceptionSurfaceModeId.IsEmpty()
|
||||
|| ExternalPortalHandoffModeId.IsEmpty()
|
||||
|| LatestSourceKind.IsEmpty()
|
||||
|| StatusLine.IsEmpty()
|
||||
|| DetailLine.IsEmpty()
|
||||
|| ExceptionEntryCount < 0
|
||||
|| ExternalPortalHandoffReadyEntryCount < 0
|
||||
|| PendingChargeEntryCount < 0
|
||||
|| ManualReviewEntryCount < 0
|
||||
|| RouteDegradedEntryCount < 0
|
||||
|| BlockedSettlementEntryCount < 0
|
||||
|| TotalEstimatedCostUsd < 0.0f
|
||||
|| TotalReportedCostUsd < 0.0f
|
||||
|| TotalSettlementDeltaUsd < 0.0f
|
||||
|| HighestSettlementDeltaUsd < 0.0f
|
||||
|| ActiveIssueCount < 0
|
||||
|| AvailableActionIds.Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ExceptionEntryCount == ExceptionEntries.Num()
|
||||
&& ExternalPortalHandoffReadyEntryCount <= ExceptionEntryCount
|
||||
&& PendingChargeEntryCount <= ExceptionEntryCount
|
||||
&& ManualReviewEntryCount <= ExceptionEntryCount
|
||||
&& RouteDegradedEntryCount <= ExceptionEntryCount
|
||||
&& BlockedSettlementEntryCount <= ExceptionEntryCount
|
||||
&& (ExceptionEntryCount <= 0 || !LatestExceptionEntryId.IsEmpty())
|
||||
&& (!bExceptionSummaryReady || !ExceptionSummaryText.IsEmpty())
|
||||
&& ActiveIssueCount == Issues.Num()
|
||||
&& (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(ExceptionEntries)
|
||||
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechExternalDictationShellProfile::IsStructurallyValid() const
|
||||
{
|
||||
return HyperTwistRecognitionTypeValidation::HasValidExternalDictationShellProfileCore(*this)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Panels)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(ActionBindings);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechExternalDictationShellState::IsStructurallyValid() const
|
||||
{
|
||||
return HyperTwistRecognitionTypeValidation::HasValidExternalDictationShellStateCore(*this)
|
||||
&& HyperTwistRecognitionTypeValidation::HasConsistentExternalDictationShellStateCounts(*this)
|
||||
&& HyperTwistRecognitionTypeValidation::HasConsistentExternalDictationShellStateLatestEntry(*this)
|
||||
&& HyperTwistRecognitionTypeValidation::HasConsistentExternalDictationShellStateActiveIssue(*this)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(HistoryEntries)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(OutputRouteOptions)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(InputDeviceOptions)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(OutputDeviceOptions)
|
||||
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(ModelCatalogEntries)
|
||||
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechSessionConfig::IsStructurallyValid() const
|
||||
{
|
||||
return HyperTwistRecognitionTypeValidation::HasValidSpeechSessionCore(*this)
|
||||
&& HyperTwistRecognitionTypeValidation::HasValidSpeechSessionProfiles(*this)
|
||||
&& HyperTwistRecognitionTypeValidation::AreSpeechModelPayloadsValid(RequiredModelPayloads);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechTranscriptResult::IsStructurallyValid() const
|
||||
{
|
||||
if (!HyperTwistRecognitionTypeValidation::HasValidTranscriptCore(*this)
|
||||
|| RetrievedHintCount < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bUsedRetrievedHintAugmentation && RetrievedHintCount <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return RetrievedHintCount == AppliedRetrievedHints.Num()
|
||||
&& HyperTwistRecognitionTypeValidation::AreTranscriptSegmentsValid(Segments)
|
||||
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AppliedRetrievedHints);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechServiceHealth::IsStructurallyValid() const
|
||||
{
|
||||
return HyperTwistRecognitionTypeValidation::HasValidSpeechServiceHealthCore(*this)
|
||||
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(SupportedModelPayloadIds)
|
||||
&& HyperTwistRecognitionTypeValidation::AreSpeechModelPayloadsValid(SupportedModelPayloads);
|
||||
}
|
||||
|
||||
bool FHyperTwistVoiceAssetDescriptor::IsStructurallyValid() const
|
||||
{
|
||||
return !VoiceAssetId.IsEmpty()
|
||||
&& !VoiceProfileId.IsEmpty()
|
||||
&& !ArtifactName.IsEmpty()
|
||||
&& !RelativePathHint.IsEmpty()
|
||||
&& !SourceDocumentPath.IsEmpty()
|
||||
&& !ReviewDocumentPath.IsEmpty()
|
||||
&& !AcquisitionMode.IsEmpty()
|
||||
&& !ReviewPosture.IsEmpty()
|
||||
&& ApproximateSizeMiB > 0;
|
||||
}
|
||||
|
||||
bool FHyperTwistVoiceAssetReviewProfile::IsStructurallyValid() const
|
||||
{
|
||||
return !VoiceAssetReviewProfileId.IsEmpty()
|
||||
&& !VoiceAssetReviewPosture.IsEmpty()
|
||||
&& !ShippingPosture.IsEmpty()
|
||||
&& !DownloadWorkflowPosture.IsEmpty()
|
||||
&& !ProvisioningPosture.IsEmpty()
|
||||
&& !CodeLicenseBoundary.IsEmpty();
|
||||
}
|
||||
|
||||
bool FHyperTwistVoiceModelReviewDescriptor::IsStructurallyValid() const
|
||||
{
|
||||
return !VoiceModelReviewId.IsEmpty()
|
||||
&& !ModelBindingId.IsEmpty()
|
||||
&& !VocoderBindingId.IsEmpty()
|
||||
&& !VoiceProfileId.IsEmpty()
|
||||
&& !RegistryReferencePath.IsEmpty()
|
||||
&& !ReviewDocumentPath.IsEmpty()
|
||||
&& !ModelLicensePosture.IsEmpty()
|
||||
&& !PayloadLicensePosture.IsEmpty()
|
||||
&& !AcquisitionMode.IsEmpty();
|
||||
}
|
||||
|
||||
bool FHyperTwistVoiceModelReviewProfile::IsStructurallyValid() const
|
||||
{
|
||||
return !VoiceModelReviewProfileId.IsEmpty()
|
||||
&& !ReviewPosture.IsEmpty()
|
||||
&& !ShippingPosture.IsEmpty()
|
||||
&& !DownloadWorkflowPosture.IsEmpty()
|
||||
&& !CodeLicenseBoundary.IsEmpty();
|
||||
}
|
||||
|
||||
bool FHyperTwistVoiceProfileSummary::IsStructurallyValid() const
|
||||
{
|
||||
return !VoiceProfileId.IsEmpty()
|
||||
&& !VoiceName.IsEmpty()
|
||||
&& !LanguageCode.IsEmpty()
|
||||
&& !LanguageFamily.IsEmpty()
|
||||
&& !RegionCode.IsEmpty()
|
||||
&& !LanguageNameEnglish.IsEmpty()
|
||||
&& !Quality.IsEmpty()
|
||||
&& NumSpeakers > 0;
|
||||
}
|
||||
|
||||
bool FHyperTwistVoiceProfileCatalog::IsStructurallyValid() const
|
||||
{
|
||||
return Profiles.Num() > 0
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Profiles);
|
||||
}
|
||||
|
||||
bool FHyperTwistNarrationOrchestrationProfile::IsStructurallyValid() const
|
||||
{
|
||||
return !OrchestrationProfileId.IsEmpty()
|
||||
&& !ServiceLaneId.IsEmpty()
|
||||
&& !ModelBindingId.IsEmpty()
|
||||
&& !VocoderBindingId.IsEmpty()
|
||||
&& !DefaultSpeakerProfileId.IsEmpty();
|
||||
}
|
||||
|
||||
bool FHyperTwistNarrationSynthesisRequest::IsStructurallyValid() const
|
||||
{
|
||||
return !RequestId.IsEmpty()
|
||||
&& !NarrationContractId.IsEmpty()
|
||||
&& !ServiceLaneId.IsEmpty()
|
||||
&& !OutputRouteId.IsEmpty()
|
||||
&& !VoiceProfileId.IsEmpty()
|
||||
&& !LanguageCode.IsEmpty()
|
||||
&& OrchestrationProfile.IsStructurallyValid()
|
||||
&& ServiceLaneId.Equals(OrchestrationProfile.ServiceLaneId, ESearchCase::CaseSensitive)
|
||||
&& !ScriptText.IsEmpty()
|
||||
&& !AudioEncodingHint.IsEmpty()
|
||||
&& LengthScale > 0.0f
|
||||
&& NoiseScale >= 0.0f
|
||||
&& NoiseW >= 0.0f
|
||||
&& SentenceSilenceSeconds >= 0.0f;
|
||||
}
|
||||
|
||||
bool FHyperTwistNarrationSynthesisResult::IsStructurallyValid() const
|
||||
{
|
||||
return !RequestId.IsEmpty()
|
||||
&& !NarrationContractId.IsEmpty()
|
||||
&& !ServiceLaneId.IsEmpty()
|
||||
&& !OutputRouteId.IsEmpty()
|
||||
&& !VoiceProfileId.IsEmpty()
|
||||
&& !LanguageCode.IsEmpty()
|
||||
&& !OrchestrationProfileId.IsEmpty()
|
||||
&& !ModelBindingId.IsEmpty()
|
||||
&& !VocoderBindingId.IsEmpty()
|
||||
&& !SubtitleText.IsEmpty()
|
||||
&& !AudioEncoding.IsEmpty()
|
||||
&& SampleRateHz > 0
|
||||
&& ChannelCount > 0
|
||||
&& DurationMs >= 0
|
||||
&& AudioBytes.Num() > 0
|
||||
&& AppliedLengthScale > 0.0f
|
||||
&& AppliedNoiseScale >= 0.0f
|
||||
&& AppliedNoiseW >= 0.0f
|
||||
&& AppliedSentenceSilenceSeconds >= 0.0f;
|
||||
}
|
||||
|
||||
bool FHyperTwistVoiceServiceHealth::IsStructurallyValid() const
|
||||
{
|
||||
if (ProviderLabel.IsEmpty() || ServiceVersion.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((!VoiceAssetReviewProfileId.IsEmpty()
|
||||
|| VoiceAssetReviewProfileDefinition.IsStructurallyValid()
|
||||
|| SupportedVoiceAssetIds.Num() > 0
|
||||
|| SupportedVoiceAssets.Num() > 0)
|
||||
&& (VoiceAssetReviewProfileId.IsEmpty()
|
||||
|| !VoiceAssetReviewProfileDefinition.IsStructurallyValid()
|
||||
|| SupportedVoiceAssetIds.Num() != SupportedVoiceAssets.Num()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((!VoiceModelReviewProfileId.IsEmpty()
|
||||
|| VoiceModelReviewProfileDefinition.IsStructurallyValid()
|
||||
|| SupportedVoiceModelReviewIds.Num() > 0
|
||||
|| SupportedVoiceModelReviews.Num() > 0)
|
||||
&& (VoiceModelReviewProfileId.IsEmpty()
|
||||
|| !VoiceModelReviewProfileDefinition.IsStructurallyValid()
|
||||
|| SupportedVoiceModelReviewIds.Num() != SupportedVoiceModelReviews.Num()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(SupportedVoiceAssetIds)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedVoiceAssets)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(SupportedVoiceModelReviewIds)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedVoiceModelReviews);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechMicrophoneShellState::IsStructurallyValid() const
|
||||
{
|
||||
if (MicrophoneShellProfileId.IsEmpty()
|
||||
|| CaptureMode.IsEmpty()
|
||||
|| ListeningContractId.IsEmpty()
|
||||
|| InputRouteId.IsEmpty()
|
||||
|| PermissionStateId.IsEmpty()
|
||||
|| CaptureRouteStateId.IsEmpty()
|
||||
|| LastLanguageCode.IsEmpty()
|
||||
|| StatusLine.IsEmpty()
|
||||
|| DetailLine.IsEmpty()
|
||||
|| StepWindowMs <= 0
|
||||
|| CaptureWindowMs < StepWindowMs
|
||||
|| KeepWindowMs < 0
|
||||
|| KeepWindowMs > CaptureWindowMs
|
||||
|| SubmittedUtteranceCount < 0
|
||||
|| FinalTranscriptCount < 0
|
||||
|| DetectedSpeechStartMs < 0
|
||||
|| DetectedSpeechEndMs < DetectedSpeechStartMs
|
||||
|| LastSilenceGapMs < 0
|
||||
|| VadThreshold <= 0.0f
|
||||
|| ActiveIssueCount < 0
|
||||
|| AvailableActionIds.Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ActiveIssueCount == Issues.Num()
|
||||
&& (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Issues)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(AvailableActionIds);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechDevicePermissionWorkflowProfile::IsStructurallyValid() const
|
||||
{
|
||||
return !DevicePermissionWorkflowProfileId.IsEmpty()
|
||||
&& !WorkflowKind.IsEmpty()
|
||||
&& !PermissionContractId.IsEmpty()
|
||||
&& !SettingsHandoffContractId.IsEmpty()
|
||||
&& !PermissionRecheckContractId.IsEmpty()
|
||||
&& !PermissionRequestActionId.IsEmpty()
|
||||
&& !OpenSettingsActionId.IsEmpty()
|
||||
&& !PermissionRecheckActionId.IsEmpty()
|
||||
&& Panels.Num() > 0
|
||||
&& ActionBindings.Num() > 0
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Panels)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechDevicePermissionWorkflowEntry::IsStructurallyValid() const
|
||||
{
|
||||
return !EntryId.IsEmpty()
|
||||
&& !SourceKind.IsEmpty()
|
||||
&& !WorkflowStateId.IsEmpty()
|
||||
&& !StatusLine.IsEmpty()
|
||||
&& !DetailLine.IsEmpty()
|
||||
&& !RecommendedActionId.IsEmpty();
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechDevicePermissionWorkflowIssue::IsStructurallyValid() const
|
||||
{
|
||||
return !IssueId.IsEmpty()
|
||||
&& !IssueKind.IsEmpty()
|
||||
&& !StatusLine.IsEmpty()
|
||||
&& !DetailLine.IsEmpty()
|
||||
&& !RecommendedActionId.IsEmpty();
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechDevicePermissionWorkflowState::IsStructurallyValid() const
|
||||
{
|
||||
if (DevicePermissionWorkflowProfileId.IsEmpty()
|
||||
|| WorkflowStateId.IsEmpty()
|
||||
|| PermissionStateId.IsEmpty()
|
||||
|| LatestSourceKind.IsEmpty()
|
||||
|| StatusLine.IsEmpty()
|
||||
|| DetailLine.IsEmpty()
|
||||
|| WorkflowEntryCount < 0
|
||||
|| PermissionRequestEntryCount < 0
|
||||
|| SettingsHandoffEntryCount < 0
|
||||
|| PermissionRecheckEntryCount < 0
|
||||
|| ActiveIssueCount < 0
|
||||
|| AvailableActionIds.Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (WorkflowEntryCount != Entries.Num()
|
||||
|| ActiveIssueCount != Issues.Num()
|
||||
|| PermissionRequestEntryCount > WorkflowEntryCount
|
||||
|| SettingsHandoffEntryCount > WorkflowEntryCount
|
||||
|| PermissionRecheckEntryCount > WorkflowEntryCount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Entries)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Issues)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(AvailableActionIds);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechNativeCaptureRouteWorkflowProfile::IsStructurallyValid() const
|
||||
{
|
||||
return !NativeCaptureRouteWorkflowProfileId.IsEmpty()
|
||||
&& !WorkflowKind.IsEmpty()
|
||||
&& !OwnershipContractId.IsEmpty()
|
||||
&& !PreparationContractId.IsEmpty()
|
||||
&& !SessionReopenContractId.IsEmpty()
|
||||
&& !RouteInspectActionId.IsEmpty()
|
||||
&& !PreparationRetryActionId.IsEmpty()
|
||||
&& !SessionReopenActionId.IsEmpty()
|
||||
&& !PermissionDependencyActionId.IsEmpty()
|
||||
&& Panels.Num() > 0
|
||||
&& ActionBindings.Num() > 0
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Panels)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechNativeCaptureRouteWorkflowEntry::IsStructurallyValid() const
|
||||
{
|
||||
return !EntryId.IsEmpty()
|
||||
&& !SourceKind.IsEmpty()
|
||||
&& !WorkflowStateId.IsEmpty()
|
||||
&& !CaptureRouteStateId.IsEmpty()
|
||||
&& !StatusLine.IsEmpty()
|
||||
&& !DetailLine.IsEmpty()
|
||||
&& !RecommendedActionId.IsEmpty();
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechNativeCaptureRouteWorkflowIssue::IsStructurallyValid() const
|
||||
{
|
||||
return !IssueId.IsEmpty()
|
||||
&& !IssueKind.IsEmpty()
|
||||
&& !StatusLine.IsEmpty()
|
||||
&& !DetailLine.IsEmpty()
|
||||
&& !RecommendedActionId.IsEmpty();
|
||||
}
|
||||
|
||||
bool FHyperTwistSpeechNativeCaptureRouteWorkflowState::IsStructurallyValid() const
|
||||
{
|
||||
if (NativeCaptureRouteWorkflowProfileId.IsEmpty()
|
||||
|| WorkflowStateId.IsEmpty()
|
||||
|| CaptureRouteStateId.IsEmpty()
|
||||
|| PermissionStateId.IsEmpty()
|
||||
|| ActiveProviderProfileId.IsEmpty()
|
||||
|| ActiveServiceLaneId.IsEmpty()
|
||||
|| LatestSourceKind.IsEmpty()
|
||||
|| StatusLine.IsEmpty()
|
||||
|| DetailLine.IsEmpty()
|
||||
|| WorkflowEntryCount < 0
|
||||
|| PreparationEntryCount < 0
|
||||
|| RouteRetryEntryCount < 0
|
||||
|| SessionReopenEntryCount < 0
|
||||
|| PermissionDependencyEntryCount < 0
|
||||
|| ActiveIssueCount < 0
|
||||
|| AvailableActionIds.Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (WorkflowEntryCount != Entries.Num()
|
||||
|| ActiveIssueCount != Issues.Num()
|
||||
|| PreparationEntryCount > WorkflowEntryCount
|
||||
|| RouteRetryEntryCount > WorkflowEntryCount
|
||||
|| SessionReopenEntryCount > WorkflowEntryCount
|
||||
|| PermissionDependencyEntryCount > WorkflowEntryCount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Entries)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Issues)
|
||||
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(AvailableActionIds);
|
||||
}
|
||||
|
|
@ -3647,6 +3647,11 @@ UHyperTwistSkillCoreLibrary::DeriveSkillContinuityResumeState(
|
|||
}
|
||||
else if (SkillId == TEXT("skill/capture-note"))
|
||||
{
|
||||
const bool bCanCaptureNotesNow =
|
||||
KnowledgeNotesState.bUserNotesLaneAvailable
|
||||
&& !KnowledgeNotesState.UserNotesPosture.bRequiresSeparateOwnerActivation
|
||||
&& Entry->Status == EHyperTwistSkillStatus::ImplementedNow;
|
||||
|
||||
SkillState.InputStateKinds = {
|
||||
TEXT("state/memory-knowledge-notes"),
|
||||
TEXT("state/memory-user-notes-posture")
|
||||
|
|
@ -3660,14 +3665,13 @@ UHyperTwistSkillCoreLibrary::DeriveSkillContinuityResumeState(
|
|||
KnowledgeNotesState.UserNotesPosture.SummaryLine
|
||||
);
|
||||
|
||||
SkillState.AvailableItemCount =
|
||||
KnowledgeNotesState.bUserNotesLaneAvailable ? 1 : 0;
|
||||
SkillState.AvailableItemCount = bCanCaptureNotesNow ? 1 : 0;
|
||||
SkillState.BlockingItemCount =
|
||||
KnowledgeNotesState.UserNotesPosture.bRequiresSeparateOwnerActivation ? 1 : 0;
|
||||
SkillState.bLiveSkill = false;
|
||||
SkillState.bReadsAuthoritativeStores =
|
||||
KnowledgeNotesState.UserNotesPosture.bOwnershipJustified;
|
||||
SkillState.bAvailableNow = KnowledgeNotesState.bUserNotesLaneAvailable;
|
||||
SkillState.bAvailableNow = bCanCaptureNotesNow;
|
||||
SkillState.bRequiresOwnerActivation =
|
||||
KnowledgeNotesState.UserNotesPosture.bRequiresSeparateOwnerActivation;
|
||||
SkillState.Summary =
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -20,6 +20,7 @@ VectorShell.
|
|||
## Landed HyperTwist-owned entry points
|
||||
|
||||
- `.sentrux/rules.toml`
|
||||
- `scripts/bootstrap-hypertwist-sentrux.sh`
|
||||
- `scripts/run-hypertwist-sentrux-source-only.sh`
|
||||
- `scripts/run-hypertwist-gitnexus-analyze.sh`
|
||||
- `scripts/run-hypertwist-gitnexus-status.sh`
|
||||
|
|
@ -56,9 +57,16 @@ Resolution order for the analyzer binary is now HyperTwist-owned first:
|
|||
|
||||
- `HYPERTWIST_SENTRUX_BINARY` if explicitly provided
|
||||
- repo-local `./sentrux` or `./sentrux.exe` if present
|
||||
- repo-local `tools/sentrux/bin/sentrux` or `tools/sentrux/bin/sentrux.exe`
|
||||
- `sentrux` on `PATH`
|
||||
- the retained local fallback under `/home/dev/src/VectorShell/sentrux`
|
||||
|
||||
If the repo-local tools path is empty, materialize it with:
|
||||
|
||||
```bash
|
||||
scripts/bootstrap-hypertwist-sentrux.sh
|
||||
```
|
||||
|
||||
Current rules enforce:
|
||||
|
||||
- no cycles
|
||||
|
|
@ -237,6 +245,129 @@ Important nuance from the `2026-06-22` follow-up:
|
|||
family extraction or multi-header ownership separation for the remaining
|
||||
validator clusters rather than more in-place header-local helperization
|
||||
|
||||
## `2026-06-23` continuation refresh
|
||||
|
||||
Additional same-lane follow-up on `2026-06-23`:
|
||||
|
||||
- the remaining inline validator clusters in
|
||||
`HyperTwistSkillTypes.h` and `HyperTwistRecognitionTypes.h` were then moved
|
||||
further out of the public headers into dedicated private translation units:
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSkills/HyperTwistSkillTypes.cpp`
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistRecognitionTypes.cpp`
|
||||
- a public-website/manual continuation also landed in the same pass so the
|
||||
docs/resources/support surfaces now explicitly project current keyboard,
|
||||
higher-dimensional control, and unfinished XR/controller truth instead of
|
||||
only the browser-versus-desktop topology boundary
|
||||
- focused website coverage for
|
||||
`src/__tests__/public-marketing-pages.test.tsx` and
|
||||
`src/__tests__/protected-app-pages.test.tsx` passed after that public-manual
|
||||
continuation
|
||||
- a fresh full website validation pass then stayed green:
|
||||
- `npm --prefix website test -- --run`
|
||||
- `37` test files passed
|
||||
- `137` tests passed
|
||||
- the same widened public-manual packet also kept the production website build
|
||||
green under `npm --prefix website run build`
|
||||
|
||||
Current highest-signal structural truth after that `2026-06-23` continuation:
|
||||
|
||||
- `scripts/run-hypertwist-sentrux-source-only.sh` now reports `Quality: 6070`
|
||||
- the only remaining reported `sentrux` debt is still:
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h:IsStructurallyValid (308 lines)`
|
||||
- that remaining hit is now more likely a parser/grouping artifact than a new
|
||||
broad website or browser regression, because the website/manual widening
|
||||
stayed structurally clean and the real validator ownership was moved out of
|
||||
the header family rather than expanded inside it
|
||||
|
||||
Additional same-lane follow-up later on `2026-06-23`:
|
||||
|
||||
- `scripts/run-hypertwist-gitnexus-analyze.sh` re-indexed the bounded
|
||||
source-only mirror successfully at `16,042` nodes, `37,412` edges,
|
||||
`643` clusters, and `300` flows
|
||||
- `scripts/run-hypertwist-gitnexus-status.sh` again reported the bounded
|
||||
mirror `Status: up-to-date`
|
||||
- a fresh full website validation rerun stayed green again:
|
||||
- `npm --prefix website test -- --run`
|
||||
- `37` test files passed
|
||||
- `137` tests passed
|
||||
- duration `4.63s`
|
||||
- `npm --prefix website run build`
|
||||
- Vite production build succeeded
|
||||
- `scripts/run-hypertwist-sentrux-source-only.sh` improved again to
|
||||
`Quality: 6076`
|
||||
- one more out-of-header migration moved
|
||||
`FHyperTwistSpeechExternalDictationShellProfile::IsStructurallyValid()`
|
||||
into `Private/HyperTwistRecognition/HyperTwistRecognitionTypes.cpp`
|
||||
- the same remaining reported `sentrux` hit still stayed fixed at
|
||||
`HyperTwistRecognitionTypes.h:IsStructurallyValid (308 lines)` even after
|
||||
that migration, which further supports the current interpretation that this
|
||||
residual is a repeated-name/grouping artifact inside the analyzer rather
|
||||
than one newly expanded high-risk validator body
|
||||
- the public-website lane was then refactored into bounded per-page modules so
|
||||
the website manual/distribution surface is no longer concentrated in one
|
||||
large `public-pages.tsx` owner
|
||||
- the public route tree then stopped lazy-loading the old barrel and now loads
|
||||
`public-pages-marketing` and `public-pages-commerce` directly, which restored
|
||||
real route-level bundle separation instead of keeping one coarse public-pages
|
||||
chunk
|
||||
- HyperTwist also gained a repo-local `scripts/bootstrap-hypertwist-sentrux.sh`
|
||||
helper plus `tools/sentrux/bin/` landing zone so the analyzer path can be
|
||||
materialized under HyperTwist authority instead of depending only on
|
||||
cross-repo memory
|
||||
- after that same refactor/tooling continuation, the repo-local materialized
|
||||
analyzer reran successfully through
|
||||
`scripts/run-hypertwist-sentrux-source-only.sh` at `Quality: 6032`
|
||||
- the same single residual violation remained:
|
||||
`HyperTwistRecognitionTypes.h:IsStructurallyValid (308 lines)`
|
||||
- that lower numeric score did not reopen cycle debt or website/page-module
|
||||
god-file debt; it still reported the same one residual recognition-header hit
|
||||
and no new browser/website structural regression
|
||||
|
||||
Latest same-lane follow-up later on `2026-06-23`:
|
||||
|
||||
- the remaining repeated-name recognition validator hotspot was then reduced
|
||||
again by moving these larger header-local validators into
|
||||
`Private/HyperTwistRecognition/HyperTwistRecognitionTypes.cpp`:
|
||||
- `FHyperTwistVisionShellProfile::IsStructurallyValid()`
|
||||
- `FHyperTwistVisionSolveExplanationProfile::IsStructurallyValid()`
|
||||
- `FHyperTwistVisionCorrectionState::IsStructurallyValid()`
|
||||
- `FHyperTwistSpeechProviderRoutingPolicy::IsStructurallyValid()`
|
||||
- after that out-of-header continuation,
|
||||
`scripts/run-hypertwist-sentrux-source-only.sh` reached `Quality: 6112`
|
||||
and all `7` rules passed with no remaining violations
|
||||
- `scripts/run-hypertwist-gitnexus-analyze.sh` then re-indexed the bounded
|
||||
source-only mirror successfully at `16,055` nodes, `37,472` edges,
|
||||
`645` clusters, and `300` flows
|
||||
- `scripts/run-hypertwist-gitnexus-status.sh` again reported the bounded
|
||||
mirror `Status: up-to-date`
|
||||
- the tightened public/manual route split remained green under focused website
|
||||
validation:
|
||||
- `npm test -- --run src/__tests__/public-marketing-pages.test.tsx src/__tests__/protected-app-pages.test.tsx`
|
||||
- `2` test files passed
|
||||
- `11` tests passed
|
||||
- the production website build remained green under `npm run build` in
|
||||
`website/`
|
||||
- the embedded browser runtime verification and production build also remained
|
||||
green under:
|
||||
- `npm run verify:shell` in `Content/Browser/`
|
||||
- `npm run build` in `Content/Browser/`
|
||||
- the same lane also preserved remote validation truth for the repaired skill
|
||||
fixture packet:
|
||||
- all `9` Windows Unreal automation reports under
|
||||
`Skill-S3-Full-PostFixture-*` were present
|
||||
- every report recorded `Succeeded: true`
|
||||
- the green set covered `S3A`, `S3B`, and `S3C` continuity, authoritative,
|
||||
and serialization filters
|
||||
|
||||
Current highest-signal structural truth after this latest continuation:
|
||||
|
||||
- HyperTwist now has repo-owned `sentrux` and `GitNexus` entry points that are
|
||||
both revalidated and green on the bounded source-only mirror
|
||||
- the public/manual/browser lane stayed validation-clean while the Unreal
|
||||
validator ownership was pushed farther out of public headers
|
||||
- the repaired skill-memory fixture lane is now fully green on the real remote
|
||||
Windows Unreal path across all `9` focused `S3` reports
|
||||
|
||||
## Out of scope
|
||||
|
||||
This note does not:
|
||||
|
|
|
|||
|
|
@ -50,6 +50,15 @@ Fallback connect-back shape when `22022` is occupied or stale:
|
|||
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -p 22023 -l 'DESKTOP-KS3VGHU\\anthracite ace' localhost
|
||||
```
|
||||
|
||||
Verified helper shape for short Windows-side PowerShell diagnostics on the same
|
||||
lane:
|
||||
|
||||
```bash
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD='...' \
|
||||
scripts/run-hypertwist-remote-windows-powershell.sh \
|
||||
--command 'Write-Output ("ComputerName=" + $env:COMPUTERNAME)'
|
||||
```
|
||||
|
||||
## Current Linux-side pickup sequence
|
||||
|
||||
For a HyperTwist AI session that starts on Linux and needs to pick up the live
|
||||
|
|
@ -148,6 +157,27 @@ Live follow-up on `2026-06-04` established these current facts:
|
|||
- `22023` and `22024` were not visible in the latest probe and should be
|
||||
treated as unavailable until re-established
|
||||
- live `whoami` over `22022` again returned `desktop-ks3vghu\anthracite ace`
|
||||
- the repo now also carries
|
||||
`scripts/run-hypertwist-remote-windows-powershell.sh` as a bounded encoded-
|
||||
PowerShell wrapper for the reverse-SSH lane so future sessions can run short
|
||||
diagnostics or automation commands through `localhost:22022` without brittle
|
||||
nested shell quoting
|
||||
- the repo now also carries
|
||||
`scripts/run-hypertwist-remote-unreal-automation.sh` as a thin first-party
|
||||
helper that builds canonical `UnrealEditor-Cmd` automation invocations on top
|
||||
of that encoded-PowerShell wrapper for the maintained
|
||||
`C:\HyperTwist_worktrees\phase10validate` lane
|
||||
- the repo now also carries
|
||||
`scripts/run-hypertwist-remote-unreal-automation-sequence.sh` as a thin
|
||||
first-party fail-fast helper for short ordered batches of focused automation
|
||||
reruns on that same maintained validation lane
|
||||
- the repo now also carries
|
||||
`scripts/run-hypertwist-remote-unreal-build.sh` as a thin first-party helper
|
||||
that builds canonical `Build.bat` invocations on top of that same encoded-
|
||||
PowerShell wrapper for the maintained
|
||||
`C:\HyperTwist_worktrees\phase10validate` lane, with the currently verified
|
||||
reverse-lane low-memory posture `-NoUBA -MaxParallelActions=2` as its
|
||||
default build shape
|
||||
|
||||
The sensitive runbook now carries the exact current Windows-side tunnel
|
||||
commands, known-hosts scratch file, listener verification command, reverse-sync
|
||||
|
|
@ -564,6 +594,41 @@ Operational rules reinforced by this proof:
|
|||
- when the export helper reports success, do one lightweight media sanity check
|
||||
on the canonical MP4 rather than trusting file existence alone; current proof
|
||||
used `ffprobe` to confirm codec, resolution, frame count, and duration
|
||||
- when the refreshed reverse-SSH Windows lane only has about `1.2 GB` of free
|
||||
physical memory and the host is still carrying large resident
|
||||
`chrome.exe` / `Code.exe` workloads, expect UnrealBuildTool to fall back to
|
||||
one active compiler worker; that is a slow-but-healthy memory-pressure
|
||||
condition, not by itself proof of tunnel corruption or a looping build
|
||||
- when a future session needs a short bounded PowerShell diagnostic over the
|
||||
reverse-SSH lane, prefer
|
||||
`scripts/run-hypertwist-remote-windows-powershell.sh` over ad hoc nested
|
||||
shell quoting; the wrapper was re-proved on `2026-06-23` against the live
|
||||
`22022` lane with `ComputerName=DESKTOP-KS3VGHU`
|
||||
- when a future session needs a focused `UnrealEditor-Cmd` automation rerun on
|
||||
the maintained Windows lane, prefer
|
||||
`scripts/run-hypertwist-remote-unreal-automation.sh`; its generated command
|
||||
shape was dry-run-validated on `2026-06-23` for
|
||||
`HyperTwist.FirstParty.Skill` before the next live post-build invocation
|
||||
- when that future session instead needs a short ordered batch of those focused
|
||||
automation reruns, prefer
|
||||
`scripts/run-hypertwist-remote-unreal-automation-sequence.sh`; its generated
|
||||
command shape was dry-run-validated on `2026-06-23`
|
||||
|
||||
Additional `2026-06-23` lane truth:
|
||||
|
||||
- the primary tunnel on `localhost:22022` remained healthy for a same-lane
|
||||
skill-memory repair continuation
|
||||
- the isolated Windows build root again stayed
|
||||
`C:\HyperTwist_worktrees\phase10validate`
|
||||
- a safe narrow-sync route was re-proved from the Windows side by using the
|
||||
remote host's own `scp.exe` plus the retained VPS key to pull changed files
|
||||
directly from `root@212.227.13.220` into that isolated worktree
|
||||
- that route is now suitable when the active lane needs only a few touched
|
||||
Unreal files refreshed in the maintained validation tree and a broad
|
||||
reverse-sync would be wasteful or riskier than necessary
|
||||
- after that narrow sync, the authoritative incremental Unreal editor rebuild
|
||||
succeeded and the widened `S3` skill-memory automation packet finished with
|
||||
all `9` expected `Skill-S3-Full-PostFixture-*` reports present and green
|
||||
|
||||
## Addendum - 2026-06-03 (stale-listener recovery)
|
||||
|
||||
|
|
|
|||
|
|
@ -267,6 +267,55 @@ Operational reading:
|
|||
it as stale and shift to `localhost:22023`
|
||||
- if `22023` is not visibly listening, do not assume fallback availability;
|
||||
re-establish it first or report that only the primary lane is currently live
|
||||
- when a short bounded Windows-side PowerShell diagnostic or automation command
|
||||
is needed from Linux over the tunnel, prefer
|
||||
`scripts/run-hypertwist-remote-windows-powershell.sh` over ad hoc nested
|
||||
shell quoting; the wrapper encodes the payload for `powershell -EncodedCommand`
|
||||
and was re-proved against the live `22022` lane on `2026-06-23`
|
||||
- when the next step is a focused `UnrealEditor-Cmd` automation rerun on the
|
||||
maintained Windows worktree, prefer
|
||||
`scripts/run-hypertwist-remote-unreal-automation.sh`; it composes canonical
|
||||
`-NullRHI`, `-ReportExportPath`, `-AbsLog`, and single-filter
|
||||
`Automation RunTests ...; Quit` invocations on top of that encoded-
|
||||
PowerShell wrapper, and its generated command shape was dry-run-validated on
|
||||
`2026-06-23`
|
||||
- when the next step is a short ordered batch of focused automation reruns on
|
||||
that same maintained Windows worktree, prefer
|
||||
`scripts/run-hypertwist-remote-unreal-automation-sequence.sh`; it composes
|
||||
repeated fail-fast invocations of the single-filter wrapper in the exact
|
||||
order provided, and its generated command shape was dry-run-validated on
|
||||
`2026-06-23`
|
||||
- when the next step is the authoritative remote Unreal editor build itself on
|
||||
the maintained Windows worktree, prefer
|
||||
`scripts/run-hypertwist-remote-unreal-build.sh`; it composes the verified
|
||||
`Build.bat` invocation on top of that same encoded-PowerShell wrapper,
|
||||
defaults the reverse-SSH lane to the maintained
|
||||
`C:\HyperTwist_worktrees\phase10validate` root, and bakes in the
|
||||
currently-verified low-memory recovery posture `-NoUBA -MaxParallelActions=2`
|
||||
unless explicitly overridden. Its generated command shape was dry-run-
|
||||
validated on `2026-06-23`
|
||||
|
||||
Canonical helper shape for the next focused post-build reruns:
|
||||
|
||||
```bash
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD='...' \
|
||||
scripts/run-hypertwist-remote-unreal-automation.sh \
|
||||
--filter HyperTwist.FirstParty.Skill \
|
||||
--report-name Skill-Verify
|
||||
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD='...' \
|
||||
scripts/run-hypertwist-remote-unreal-automation.sh \
|
||||
--filter HyperTwist.Permissive.WhisperCpp \
|
||||
--report-name WhisperCpp-Verify
|
||||
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD='...' \
|
||||
scripts/run-hypertwist-remote-unreal-automation-sequence.sh \
|
||||
--report-prefix Skill-S3 \
|
||||
--filter HyperTwist.FirstParty.Skill.PhaseS3A.ContinuityResumeCoverage \
|
||||
--filter HyperTwist.FirstParty.Skill.PhaseS3A.AuthoritativeStoreRead \
|
||||
--filter HyperTwist.FirstParty.Skill.PhaseS3A.SerializationRoundTrip
|
||||
```
|
||||
|
||||
- when recovering from a stale listener, run a split sequence:
|
||||
1. smoke login/identity check
|
||||
2. canonical Unreal build command with explicit result markers
|
||||
|
|
@ -305,11 +354,52 @@ Operational reading:
|
|||
before building; preserving old mtimes can leave stale
|
||||
`Intermediate\Build\Win64\UnrealEditor\Inc\UnrealHyperTwist` outputs in place
|
||||
and surface misleading reflected-type or generated-header failures
|
||||
- when the reverse-SSH Windows lane only has about `1.2 GB` of free physical
|
||||
memory and the host still has large resident `chrome.exe` / `Code.exe`
|
||||
workloads, expect UnrealBuildTool to serialize down to one active compiler
|
||||
worker; treat that as a slow-but-healthy memory-pressure condition rather than
|
||||
as tunnel corruption or a looping build unless the compile output itself
|
||||
stalls or errors
|
||||
- when a logical slice authors new `.umap` or `.uasset` authority surfaces in an
|
||||
isolated Windows worktree, pull those assets back into the tracked repo before
|
||||
calling the slice landed; remote-only authored assets are validation evidence,
|
||||
not final repo truth
|
||||
|
||||
## Addendum - 2026-06-23 (skill-memory fixture repair and full `S3` proof)
|
||||
|
||||
Live follow-up on `2026-06-23` established these additional facts:
|
||||
|
||||
- the maintained validation root again stayed
|
||||
`C:\HyperTwist_worktrees\phase10validate`
|
||||
- Windows-side `scp.exe` running on the remote host was re-proved as a safe
|
||||
narrow-sync route from the VPS into that isolated worktree for touched
|
||||
source files, avoiding a broad reverse-sync just to refresh a few Unreal
|
||||
translation units
|
||||
- after the touched skill-memory source files were hash-matched into that
|
||||
worktree, the authoritative incremental editor rebuild succeeded there with
|
||||
`Result: Succeeded` and UnrealBuildTool `Total execution time: 104.88
|
||||
seconds`
|
||||
- a fresh focused `S3A` rerun then passed on that rebuilt binary
|
||||
- the widened same-lane follow-up then produced all `9` expected
|
||||
`Skill-S3-Full-PostFixture-*` automation report folders under
|
||||
`Saved\AutomationReports`
|
||||
- each of those `9` report `index.json` files recorded a green result with
|
||||
`Succeeded: true`, `SucceededCount: 1`, and `FailedCount: 0`
|
||||
- that green set covered:
|
||||
- `HyperTwist.FirstParty.Skill.PhaseS3A.ContinuityResumeCoverage`
|
||||
- `HyperTwist.FirstParty.Skill.PhaseS3A.AuthoritativeStoreRead`
|
||||
- `HyperTwist.FirstParty.Skill.PhaseS3A.SerializationRoundTrip`
|
||||
- `HyperTwist.FirstParty.Skill.PhaseS3B.RecallCompactViewCoverage`
|
||||
- `HyperTwist.FirstParty.Skill.PhaseS3B.AuthoritativeCompactViewLinkage`
|
||||
- `HyperTwist.FirstParty.Skill.PhaseS3B.SerializationRoundTrip`
|
||||
- `HyperTwist.FirstParty.Skill.PhaseS3C.WorkflowMemoryCaptureCoverage`
|
||||
- `HyperTwist.FirstParty.Skill.PhaseS3C.AuthoritativeWorkflowCaptureBoundaries`
|
||||
- `HyperTwist.FirstParty.Skill.PhaseS3C.SerializationRoundTrip`
|
||||
|
||||
This extends the doctrine from classic-cube, package, higher-dimensional, and
|
||||
media-export proof into the real remote skill-memory validation lane on the
|
||||
same maintained reverse-SSH path.
|
||||
|
||||
## Closeout wording requirement
|
||||
|
||||
Every Unreal C++ closeout should say one of these explicitly:
|
||||
|
|
|
|||
|
|
@ -71,6 +71,18 @@ Current truthful product wording should say:
|
|||
- full VR/controller/settings polish lane: not yet complete enough to market as
|
||||
finished
|
||||
|
||||
## Public-surface consequence
|
||||
|
||||
The same truth should remain visible on the public website and public operator
|
||||
manual:
|
||||
|
||||
- docs/resources/support surfaces should explicitly distinguish current keyboard
|
||||
and higher-dimensional control ownership from unfinished VR/controller claims
|
||||
- public copy should not imply that `EnhancedInput` plus motion-controller axis
|
||||
groundwork already equals a finished OpenXR/runtime or rebinding lane
|
||||
- browser/distribution pages may describe the simulator input posture, but they
|
||||
must not market browser control parity with the native runtime
|
||||
|
||||
## Next clean implementation packet
|
||||
|
||||
If the project wants to raise this lane from “groundwork exists” to “shipping
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
82
scripts/bootstrap-hypertwist-sentrux.sh
Normal file
82
scripts/bootstrap-hypertwist-sentrux.sh
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
dest_dir="$repo_root/tools/sentrux/bin"
|
||||
dest_linux="$dest_dir/sentrux"
|
||||
dest_windows="$dest_dir/sentrux.exe"
|
||||
|
||||
vector_sentrux_binary="/home/dev/src/VectorShell/sentrux/target/release/sentrux"
|
||||
vector_sentrux_manifest="/home/dev/src/VectorShell/sentrux/Cargo.toml"
|
||||
scriptorium_sentrux_windows="/home/dev/src/ScriptoriumAI/sentrux.exe"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
scripts/bootstrap-hypertwist-sentrux.sh [--if-missing]
|
||||
|
||||
Materializes a repo-local sentrux binary under tools/sentrux/bin/ using the
|
||||
best available retained source on this machine.
|
||||
|
||||
Resolution order:
|
||||
1. existing VectorShell Linux build artifact
|
||||
2. local VectorShell source build via cargo
|
||||
3. local ScriptoriumAI Windows binary
|
||||
EOF
|
||||
}
|
||||
|
||||
copy_binary() {
|
||||
local source_path="$1"
|
||||
local destination_path="$2"
|
||||
|
||||
mkdir -p "$dest_dir"
|
||||
cp "$source_path" "$destination_path"
|
||||
chmod +x "$destination_path" 2>/dev/null || true
|
||||
printf 'Materialized sentrux at %s from %s\n' "$destination_path" "$source_path"
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "--if-missing" ]]; then
|
||||
if [[ -x "$dest_linux" || -x "$dest_windows" ]]; then
|
||||
printf 'Repo-local sentrux already present under %s\n' "$dest_dir"
|
||||
exit 0
|
||||
fi
|
||||
elif [[ $# -gt 0 ]]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -x "$vector_sentrux_binary" ]]; then
|
||||
copy_binary "$vector_sentrux_binary" "$dest_linux"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if command -v cargo >/dev/null 2>&1 && [[ -f "$vector_sentrux_manifest" ]]; then
|
||||
cargo build --quiet --release --manifest-path "$vector_sentrux_manifest" --bin sentrux
|
||||
if [[ -x "$vector_sentrux_binary" ]]; then
|
||||
copy_binary "$vector_sentrux_binary" "$dest_linux"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "$scriptorium_sentrux_windows" ]]; then
|
||||
copy_binary "$scriptorium_sentrux_windows" "$dest_windows"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cat >&2 <<EOF
|
||||
Unable to materialize a repo-local sentrux binary.
|
||||
|
||||
Checked:
|
||||
- $vector_sentrux_binary
|
||||
- $vector_sentrux_manifest
|
||||
- $scriptorium_sentrux_windows
|
||||
|
||||
Provide HYPERTWIST_SENTRUX_BINARY directly or place a compatible sentrux binary
|
||||
under tools/sentrux/bin/.
|
||||
EOF
|
||||
exit 1
|
||||
102
scripts/run-hypertwist-remote-unreal-automation-sequence.sh
Normal file
102
scripts/run-hypertwist-remote-unreal-automation-sequence.sh
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||
scripts/run-hypertwist-remote-unreal-automation-sequence.sh \
|
||||
--filter HyperTwist.FirstParty.Skill.PhaseS3A.ContinuityResumeCoverage \
|
||||
--filter HyperTwist.FirstParty.Skill.PhaseS3A.AuthoritativeStoreRead
|
||||
|
||||
Options:
|
||||
--filter <automation-filter>
|
||||
Required. May be repeated. Filters are run sequentially in the order given.
|
||||
|
||||
--report-prefix <name>
|
||||
Optional prefix for per-filter report names.
|
||||
|
||||
--extra-arg <value>
|
||||
Append an extra UnrealEditor-Cmd.exe argument to every filter run.
|
||||
|
||||
--dry-run
|
||||
Print the underlying wrapper invocations instead of executing them.
|
||||
EOF
|
||||
}
|
||||
|
||||
filters=()
|
||||
report_prefix=""
|
||||
dry_run="false"
|
||||
extra_args=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--filter)
|
||||
filters+=("${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
--report-prefix)
|
||||
report_prefix="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--extra-arg)
|
||||
extra_args+=("${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
dry_run="true"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ${#filters[@]} -eq 0 ]]; then
|
||||
echo "Provide at least one --filter." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
single_wrapper="${script_dir}/run-hypertwist-remote-unreal-automation.sh"
|
||||
|
||||
if [[ ! -x "$single_wrapper" ]]; then
|
||||
echo "Expected executable wrapper at $single_wrapper" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sanitize_report_name() {
|
||||
printf '%s' "$1" | sed 's/[^[:alnum:]._+-]/_/g'
|
||||
}
|
||||
|
||||
for filter in "${filters[@]}"; do
|
||||
report_name="$(sanitize_report_name "$filter")"
|
||||
if [[ -n "$report_prefix" ]]; then
|
||||
report_name="${report_prefix}-$(sanitize_report_name "$filter")"
|
||||
fi
|
||||
|
||||
command=(
|
||||
"$single_wrapper"
|
||||
--filter "$filter"
|
||||
--report-name "$report_name"
|
||||
)
|
||||
|
||||
for extra_arg in "${extra_args[@]}"; do
|
||||
command+=(--extra-arg "$extra_arg")
|
||||
done
|
||||
|
||||
if [[ "$dry_run" == "true" ]]; then
|
||||
printf '%q ' "${command[@]}"
|
||||
printf '\n'
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Running remote Unreal automation filter: $filter"
|
||||
"${command[@]}"
|
||||
done
|
||||
161
scripts/run-hypertwist-remote-unreal-automation.sh
Normal file
161
scripts/run-hypertwist-remote-unreal-automation.sh
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||
scripts/run-hypertwist-remote-unreal-automation.sh --filter HyperTwist.FirstParty.Skill
|
||||
|
||||
Options:
|
||||
--filter <automation-filter>
|
||||
Required Unreal automation filter.
|
||||
|
||||
--report-name <name>
|
||||
Optional report/log folder suffix. Defaults to a sanitized filter name.
|
||||
|
||||
--extra-arg <value>
|
||||
Append an extra UnrealEditor-Cmd.exe argument. May be repeated.
|
||||
|
||||
--dry-run
|
||||
Print the generated remote PowerShell payload instead of executing it.
|
||||
|
||||
Environment:
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD Required by the underlying tunnel wrapper.
|
||||
HYPERTWIST_REMOTE_WINDOWS_WORKTREE_ROOT Optional, defaults to C:\HyperTwist_worktrees\phase10validate
|
||||
HYPERTWIST_REMOTE_WINDOWS_PROJECT_PATH Optional, defaults beneath the worktree root.
|
||||
HYPERTWIST_REMOTE_WINDOWS_UNREAL_EDITOR_CMD
|
||||
Optional, defaults to the UE 5.7 UnrealEditor-Cmd.exe path.
|
||||
HYPERTWIST_REMOTE_WINDOWS_AUTOMATION_REPORT_ROOT
|
||||
Optional, defaults beneath Saved\AutomationReports.
|
||||
HYPERTWIST_REMOTE_WINDOWS_LOG_ROOT Optional, defaults beneath Saved\Logs.
|
||||
EOF
|
||||
}
|
||||
|
||||
filter=""
|
||||
report_name=""
|
||||
dry_run="false"
|
||||
extra_args=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--filter)
|
||||
filter="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--report-name)
|
||||
report_name="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--extra-arg)
|
||||
extra_args+=("${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
dry_run="true"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$filter" ]]; then
|
||||
echo "Provide --filter." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
remote_ps_wrapper="${script_dir}/run-hypertwist-remote-windows-powershell.sh"
|
||||
|
||||
if [[ ! -x "$remote_ps_wrapper" ]]; then
|
||||
echo "Expected executable wrapper at $remote_ps_wrapper" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
worktree_root="${HYPERTWIST_REMOTE_WINDOWS_WORKTREE_ROOT:-C:\\HyperTwist_worktrees\\phase10validate}"
|
||||
project_path="${HYPERTWIST_REMOTE_WINDOWS_PROJECT_PATH:-${worktree_root}\\UnrealHyperTwist\\UnrealHyperTwist.uproject}"
|
||||
editor_cmd_path="${HYPERTWIST_REMOTE_WINDOWS_UNREAL_EDITOR_CMD:-C:\\Program Files\\Epic Games\\UE_5.7\\Engine\\Binaries\\Win64\\UnrealEditor-Cmd.exe}"
|
||||
report_root="${HYPERTWIST_REMOTE_WINDOWS_AUTOMATION_REPORT_ROOT:-${worktree_root}\\UnrealHyperTwist\\Saved\\AutomationReports}"
|
||||
log_root="${HYPERTWIST_REMOTE_WINDOWS_LOG_ROOT:-${worktree_root}\\UnrealHyperTwist\\Saved\\Logs}"
|
||||
|
||||
if [[ -z "$report_name" ]]; then
|
||||
report_name="$(printf '%s' "$filter" | sed 's/[^[:alnum:]._+-]/_/g')"
|
||||
fi
|
||||
|
||||
report_export_path="${report_root}\\${report_name}"
|
||||
abs_log_path="${log_root}\\${report_name}.log"
|
||||
|
||||
powershell_payload="$(
|
||||
python3 - <<'PY' \
|
||||
"$editor_cmd_path" \
|
||||
"$project_path" \
|
||||
"$report_export_path" \
|
||||
"$abs_log_path" \
|
||||
"$filter" \
|
||||
"${extra_args[@]}"
|
||||
import sys
|
||||
|
||||
editor_cmd_path, project_path, report_export_path, abs_log_path, filter_name, *extra_args = sys.argv[1:]
|
||||
|
||||
def ps_single_quote(value: str) -> str:
|
||||
return "'" + value.replace("'", "''") + "'"
|
||||
|
||||
lines = [
|
||||
f"$EditorCmdPath = {ps_single_quote(editor_cmd_path)}",
|
||||
f"$ProjectPath = {ps_single_quote(project_path)}",
|
||||
f"$ReportExportPath = {ps_single_quote(report_export_path)}",
|
||||
f"$AbsLogPath = {ps_single_quote(abs_log_path)}",
|
||||
f"$Filter = {ps_single_quote(filter_name)}",
|
||||
"",
|
||||
"New-Item -ItemType Directory -Force -Path $ReportExportPath | Out-Null",
|
||||
"New-Item -ItemType Directory -Force -Path (Split-Path -Parent $AbsLogPath) | Out-Null",
|
||||
"",
|
||||
"if (-not (Test-Path -LiteralPath $EditorCmdPath)) {",
|
||||
" throw \"UnrealEditor-Cmd.exe was not found at '$EditorCmdPath'.\"",
|
||||
"}",
|
||||
"",
|
||||
"if (-not (Test-Path -LiteralPath $ProjectPath)) {",
|
||||
" throw \"Project file was not found at '$ProjectPath'.\"",
|
||||
"}",
|
||||
"",
|
||||
"& $EditorCmdPath `",
|
||||
" $ProjectPath `",
|
||||
" -unattended `",
|
||||
" -nop4 `",
|
||||
" -nosplash `",
|
||||
" -NullRHI `",
|
||||
" -log `",
|
||||
" -stdout `",
|
||||
" -FullStdOutLogOutput `",
|
||||
" \"-AbsLog=$AbsLogPath\" `",
|
||||
" \"-ReportExportPath=$ReportExportPath\" `",
|
||||
" \"-ExecCmds=Automation RunTests $Filter\" `",
|
||||
]
|
||||
|
||||
if extra_args:
|
||||
for extra_arg in extra_args:
|
||||
lines.append(f" {ps_single_quote(extra_arg)} `")
|
||||
|
||||
lines.extend([
|
||||
" \"-TestExit=Automation Test Queue Empty\"",
|
||||
"",
|
||||
"exit $LASTEXITCODE",
|
||||
])
|
||||
|
||||
print("\n".join(lines))
|
||||
PY
|
||||
)"
|
||||
|
||||
if [[ "$dry_run" == "true" ]]; then
|
||||
printf '%s\n' "$powershell_payload"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec "$remote_ps_wrapper" --command "$powershell_payload"
|
||||
213
scripts/run-hypertwist-remote-unreal-build.sh
Normal file
213
scripts/run-hypertwist-remote-unreal-build.sh
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||
scripts/run-hypertwist-remote-unreal-build.sh
|
||||
|
||||
Options:
|
||||
--target <name>
|
||||
Optional Unreal build target. Defaults to UnrealHyperTwistEditor.
|
||||
|
||||
--platform <name>
|
||||
Optional Unreal platform. Defaults to Win64.
|
||||
|
||||
--configuration <name>
|
||||
Optional Unreal configuration. Defaults to Development.
|
||||
|
||||
--worktree-root <path>
|
||||
Optional Windows worktree root. Defaults to C:\HyperTwist_worktrees\phase10validate.
|
||||
|
||||
--project-path <path>
|
||||
Optional Windows .uproject path. Defaults beneath the worktree root.
|
||||
|
||||
--build-batch-path <path>
|
||||
Optional Build.bat path. Defaults to the UE 5.7 installed-engine path.
|
||||
|
||||
--max-parallel-actions <count>
|
||||
Optional MaxParallelActions override. Defaults to 2 for the remote lane.
|
||||
|
||||
--allow-uba
|
||||
Allow UBA instead of forcing the verified remote-lane -NoUBA posture.
|
||||
|
||||
--extra-arg <value>
|
||||
Append an extra Build.bat argument. May be repeated.
|
||||
|
||||
--dry-run
|
||||
Print the generated remote PowerShell payload instead of executing it.
|
||||
|
||||
Environment:
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD Required by the underlying tunnel wrapper.
|
||||
HYPERTWIST_REMOTE_WINDOWS_WORKTREE_ROOT Optional default for --worktree-root.
|
||||
HYPERTWIST_REMOTE_WINDOWS_PROJECT_PATH Optional default for --project-path.
|
||||
HYPERTWIST_REMOTE_WINDOWS_BUILD_BAT Optional default for --build-batch-path.
|
||||
HYPERTWIST_REMOTE_WINDOWS_MAX_PARALLEL_ACTIONS
|
||||
Optional default for --max-parallel-actions.
|
||||
EOF
|
||||
}
|
||||
|
||||
target_name="UnrealHyperTwistEditor"
|
||||
platform_name="Win64"
|
||||
configuration_name="Development"
|
||||
worktree_root="${HYPERTWIST_REMOTE_WINDOWS_WORKTREE_ROOT:-C:\\HyperTwist_worktrees\\phase10validate}"
|
||||
project_path=""
|
||||
build_batch_path="${HYPERTWIST_REMOTE_WINDOWS_BUILD_BAT:-C:\\Program Files\\Epic Games\\UE_5.7\\Engine\\Build\\BatchFiles\\Build.bat}"
|
||||
max_parallel_actions="${HYPERTWIST_REMOTE_WINDOWS_MAX_PARALLEL_ACTIONS:-2}"
|
||||
allow_uba="false"
|
||||
dry_run="false"
|
||||
extra_args=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--target)
|
||||
target_name="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--platform)
|
||||
platform_name="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--configuration)
|
||||
configuration_name="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--worktree-root)
|
||||
worktree_root="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--project-path)
|
||||
project_path="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--build-batch-path)
|
||||
build_batch_path="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--max-parallel-actions)
|
||||
max_parallel_actions="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--allow-uba)
|
||||
allow_uba="true"
|
||||
shift
|
||||
;;
|
||||
--extra-arg)
|
||||
extra_args+=("${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
dry_run="true"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$project_path" ]]; then
|
||||
project_path="${HYPERTWIST_REMOTE_WINDOWS_PROJECT_PATH:-${worktree_root}\\UnrealHyperTwist\\UnrealHyperTwist.uproject}"
|
||||
fi
|
||||
|
||||
if [[ -z "$target_name" || -z "$platform_name" || -z "$configuration_name" || -z "$project_path" || -z "$build_batch_path" ]]; then
|
||||
echo "Target, platform, configuration, project path, and Build.bat path must all be populated." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$max_parallel_actions" && ! "$max_parallel_actions" =~ ^[0-9]+$ ]]; then
|
||||
echo "--max-parallel-actions must be numeric." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
remote_ps_wrapper="${script_dir}/run-hypertwist-remote-windows-powershell.sh"
|
||||
|
||||
if [[ ! -x "$remote_ps_wrapper" ]]; then
|
||||
echo "Expected executable wrapper at $remote_ps_wrapper" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
powershell_payload="$(
|
||||
python3 - <<'PY' \
|
||||
"$build_batch_path" \
|
||||
"$target_name" \
|
||||
"$platform_name" \
|
||||
"$configuration_name" \
|
||||
"$project_path" \
|
||||
"$max_parallel_actions" \
|
||||
"$allow_uba" \
|
||||
"${extra_args[@]}"
|
||||
import sys
|
||||
|
||||
build_batch_path, target_name, platform_name, configuration_name, project_path, max_parallel_actions, allow_uba, *extra_args = sys.argv[1:]
|
||||
|
||||
def ps_single_quote(value: str) -> str:
|
||||
return "'" + value.replace("'", "''") + "'"
|
||||
|
||||
lines = [
|
||||
f"$BuildBatchPath = {ps_single_quote(build_batch_path)}",
|
||||
f"$TargetName = {ps_single_quote(target_name)}",
|
||||
f"$PlatformName = {ps_single_quote(platform_name)}",
|
||||
f"$ConfigurationName = {ps_single_quote(configuration_name)}",
|
||||
f"$ProjectPath = {ps_single_quote(project_path)}",
|
||||
f"$MaxParallelActions = {max_parallel_actions}",
|
||||
f"$AllowUba = ${'true' if allow_uba == 'true' else 'false'}",
|
||||
"$ExtraArgs = @(",
|
||||
]
|
||||
|
||||
for value in extra_args:
|
||||
lines.append(f" {ps_single_quote(value)}")
|
||||
|
||||
lines.extend([
|
||||
")",
|
||||
"",
|
||||
"if (-not (Test-Path -LiteralPath $BuildBatchPath)) {",
|
||||
" throw \"Build.bat was not found at '$BuildBatchPath'.\"",
|
||||
"}",
|
||||
"",
|
||||
"if (-not (Test-Path -LiteralPath $ProjectPath)) {",
|
||||
" throw \"Project file was not found at '$ProjectPath'.\"",
|
||||
"}",
|
||||
"",
|
||||
"$BuildArgs = @(",
|
||||
" $TargetName",
|
||||
" $PlatformName",
|
||||
" $ConfigurationName",
|
||||
" $ProjectPath",
|
||||
" '-WaitMutex'",
|
||||
" '-NoHotReloadFromIDE'",
|
||||
")",
|
||||
"",
|
||||
"if (-not $AllowUba) {",
|
||||
" $BuildArgs += '-NoUBA'",
|
||||
"}",
|
||||
"",
|
||||
"if ($MaxParallelActions -gt 0) {",
|
||||
" $BuildArgs += \"-MaxParallelActions=$MaxParallelActions\"",
|
||||
"}",
|
||||
"",
|
||||
"if ($ExtraArgs.Count -gt 0) {",
|
||||
" $BuildArgs += $ExtraArgs",
|
||||
"}",
|
||||
"",
|
||||
"& $BuildBatchPath @BuildArgs",
|
||||
"exit $LASTEXITCODE",
|
||||
])
|
||||
|
||||
print("\n".join(lines))
|
||||
PY
|
||||
)"
|
||||
|
||||
if [[ "$dry_run" == "true" ]]; then
|
||||
printf '%s\n' "$powershell_payload"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec "$remote_ps_wrapper" --command "$powershell_payload"
|
||||
142
scripts/run-hypertwist-remote-windows-file-sync.sh
Normal file
142
scripts/run-hypertwist-remote-windows-file-sync.sh
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||
scripts/run-hypertwist-remote-windows-file-sync.sh \
|
||||
--file UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp
|
||||
|
||||
Options:
|
||||
--file <path>
|
||||
Relative repo path to copy into the remote Windows worktree. May be repeated.
|
||||
|
||||
--remote-root <path>
|
||||
Optional Windows worktree root. Defaults to C:\HyperTwist_worktrees\phase10validate.
|
||||
|
||||
--dry-run
|
||||
Print the copy plan without transferring files.
|
||||
|
||||
Environment:
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD Required password for the reverse-SSH Windows user.
|
||||
HYPERTWIST_REMOTE_TUNNEL_PORT Optional, defaults to 22022.
|
||||
HYPERTWIST_REMOTE_WINDOWS_USER Optional, defaults to "anthracite ace".
|
||||
HYPERTWIST_REMOTE_TUNNEL_HOST Optional, defaults to "localhost".
|
||||
EOF
|
||||
}
|
||||
|
||||
if ! command -v sshpass >/dev/null 2>&1; then
|
||||
echo "sshpass is required but was not found on PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v sha256sum >/dev/null 2>&1; then
|
||||
echo "sha256sum is required but was not found on PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${HYPERTWIST_REMOTE_WINDOWS_PASSWORD:-}" ]]; then
|
||||
echo "Set HYPERTWIST_REMOTE_WINDOWS_PASSWORD before using this wrapper." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
remote_root='C:\HyperTwist_worktrees\phase10validate'
|
||||
chunk_size_bytes=65536
|
||||
dry_run="false"
|
||||
files=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--file)
|
||||
files+=("${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
--remote-root)
|
||||
remote_root="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
dry_run="true"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ${#files[@]} -eq 0 ]]; then
|
||||
echo "Provide at least one --file." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
remote_port="${HYPERTWIST_REMOTE_TUNNEL_PORT:-22022}"
|
||||
remote_user="${HYPERTWIST_REMOTE_WINDOWS_USER:-anthracite ace}"
|
||||
remote_host="${HYPERTWIST_REMOTE_TUNNEL_HOST:-localhost}"
|
||||
|
||||
for relative_path in "${files[@]}"; do
|
||||
if [[ "$relative_path" = /* ]]; then
|
||||
echo "Use repo-relative paths only: $relative_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$relative_path" ]]; then
|
||||
echo "File not found: $relative_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
windows_relative_path="${relative_path//\//\\}"
|
||||
windows_dest_path="${remote_root}\\${windows_relative_path}"
|
||||
local_hash="$(sha256sum "$relative_path" | awk '{print tolower($1)}')"
|
||||
|
||||
if [[ "$dry_run" == "true" ]]; then
|
||||
printf '%s -> %s (%s)\n' "$relative_path" "$windows_dest_path" "$local_hash"
|
||||
continue
|
||||
fi
|
||||
|
||||
ssh_base=(
|
||||
sshpass -p "${HYPERTWIST_REMOTE_WINDOWS_PASSWORD}"
|
||||
ssh
|
||||
-o StrictHostKeyChecking=no
|
||||
-o PreferredAuthentications=password
|
||||
-o PubkeyAuthentication=no
|
||||
-p "${remote_port}"
|
||||
-l "${remote_user}"
|
||||
"${remote_host}"
|
||||
)
|
||||
|
||||
"${ssh_base[@]}" \
|
||||
"powershell -NoProfile -Command \"\$dest='${windows_dest_path}'; \$destDir = Split-Path -Parent \$dest; New-Item -ItemType Directory -Force -Path \$destDir | Out-Null; \$outputStream = [IO.File]::Open(\$dest, [IO.FileMode]::Create, [IO.FileAccess]::Write, [IO.FileShare]::None); \$outputStream.Dispose()\""
|
||||
|
||||
tmp_chunk_dir="$(mktemp -d)"
|
||||
split -b "${chunk_size_bytes}" --numeric-suffixes=1 --suffix-length=4 \
|
||||
"$relative_path" "${tmp_chunk_dir}/chunk-"
|
||||
|
||||
for chunk_path in "${tmp_chunk_dir}"/chunk-*; do
|
||||
cat "$chunk_path" | \
|
||||
"${ssh_base[@]}" \
|
||||
"powershell -NoProfile -Command \"\$dest='${windows_dest_path}'; \$outputStream = [IO.File]::Open(\$dest, [IO.FileMode]::Append, [IO.FileAccess]::Write, [IO.FileShare]::None); try { [Console]::OpenStandardInput().CopyTo(\$outputStream) } finally { \$outputStream.Dispose() }\""
|
||||
done
|
||||
|
||||
remote_hash="$(
|
||||
"${ssh_base[@]}" \
|
||||
"powershell -NoProfile -Command \"[Console]::Out.Write((Get-FileHash -LiteralPath '${windows_dest_path}' -Algorithm SHA256).Hash.ToLowerInvariant())\""
|
||||
)"
|
||||
|
||||
rm -rf "$tmp_chunk_dir"
|
||||
|
||||
if [[ "$remote_hash" != "$local_hash" ]]; then
|
||||
echo "Hash mismatch for $relative_path" >&2
|
||||
echo " local : $local_hash" >&2
|
||||
echo " remote: $remote_hash" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'Synced %s -> %s (%s)\n' "$relative_path" "$windows_dest_path" "$local_hash"
|
||||
done
|
||||
90
scripts/run-hypertwist-remote-windows-powershell.sh
Normal file
90
scripts/run-hypertwist-remote-windows-powershell.sh
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||
scripts/run-hypertwist-remote-windows-powershell.sh --command "Write-Output 'hello'"
|
||||
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||
scripts/run-hypertwist-remote-windows-powershell.sh --script-file ./script.ps1
|
||||
|
||||
Options:
|
||||
--command <powershell>
|
||||
Run the provided PowerShell source text.
|
||||
|
||||
--script-file <path>
|
||||
Read PowerShell source from the given file.
|
||||
|
||||
Environment:
|
||||
HYPERTWIST_REMOTE_WINDOWS_PASSWORD Required password for the reverse-SSH Windows user.
|
||||
HYPERTWIST_REMOTE_TUNNEL_PORT Optional, defaults to 22022.
|
||||
HYPERTWIST_REMOTE_WINDOWS_USER Optional, defaults to "anthracite ace".
|
||||
HYPERTWIST_REMOTE_TUNNEL_HOST Optional, defaults to "localhost".
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ $# -lt 2 ]]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v sshpass >/dev/null 2>&1; then
|
||||
echo "sshpass is required but was not found on PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${HYPERTWIST_REMOTE_WINDOWS_PASSWORD:-}" ]]; then
|
||||
echo "Set HYPERTWIST_REMOTE_WINDOWS_PASSWORD before using this wrapper." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
script_text=""
|
||||
|
||||
case "$1" in
|
||||
--command)
|
||||
script_text="$2"
|
||||
;;
|
||||
--script-file)
|
||||
if [[ ! -f "$2" ]]; then
|
||||
echo "Script file not found: $2" >&2
|
||||
exit 1
|
||||
fi
|
||||
script_text="$(<"$2")"
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
remote_port="${HYPERTWIST_REMOTE_TUNNEL_PORT:-22022}"
|
||||
remote_user="${HYPERTWIST_REMOTE_WINDOWS_USER:-anthracite ace}"
|
||||
remote_host="${HYPERTWIST_REMOTE_TUNNEL_HOST:-localhost}"
|
||||
script_preamble=$'$ProgressPreference = \'SilentlyContinue\'\n$ErrorActionPreference = \'Stop\'\n'
|
||||
script_payload="${script_preamble}${script_text}"
|
||||
|
||||
encoded_command="$(
|
||||
python3 - <<'PY' "$script_payload"
|
||||
import base64
|
||||
import sys
|
||||
|
||||
script = sys.argv[1]
|
||||
print(base64.b64encode(script.encode("utf-16le")).decode("ascii"))
|
||||
PY
|
||||
)"
|
||||
|
||||
exec sshpass -p "${HYPERTWIST_REMOTE_WINDOWS_PASSWORD}" \
|
||||
ssh \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o PreferredAuthentications=password \
|
||||
-o PubkeyAuthentication=no \
|
||||
-p "${remote_port}" \
|
||||
-l "${remote_user}" \
|
||||
"${remote_host}" \
|
||||
"powershell -NoProfile -EncodedCommand ${encoded_command}"
|
||||
|
|
@ -5,6 +5,8 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|||
temp_root="${TMPDIR:-/tmp}/hypertwist-sentrux-source-only"
|
||||
repo_local_sentrux_binary="$repo_root/sentrux"
|
||||
repo_local_sentrux_windows_binary="$repo_root/sentrux.exe"
|
||||
repo_tools_sentrux_binary="$repo_root/tools/sentrux/bin/sentrux"
|
||||
repo_tools_sentrux_windows_binary="$repo_root/tools/sentrux/bin/sentrux.exe"
|
||||
local_sentrux_binary="/home/dev/src/VectorShell/sentrux/target/release/sentrux"
|
||||
local_sentrux_manifest="/home/dev/src/VectorShell/sentrux/Cargo.toml"
|
||||
|
||||
|
|
@ -24,6 +26,16 @@ resolve_sentrux_command() {
|
|||
return 0
|
||||
fi
|
||||
|
||||
if [[ -x "$repo_tools_sentrux_binary" ]]; then
|
||||
printf '%s\n' "$repo_tools_sentrux_binary"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -x "$repo_tools_sentrux_windows_binary" ]]; then
|
||||
printf '%s\n' "$repo_tools_sentrux_windows_binary"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v sentrux >/dev/null 2>&1; then
|
||||
printf 'sentrux\n'
|
||||
return 0
|
||||
|
|
@ -43,7 +55,7 @@ resolve_sentrux_command() {
|
|||
}
|
||||
|
||||
sentrux_command="$(resolve_sentrux_command)" || {
|
||||
echo "Unable to locate sentrux. Provide HYPERTWIST_SENTRUX_BINARY, add sentrux to PATH, place a repo-local sentrux binary at $repo_root, or keep /home/dev/src/VectorShell/sentrux available." >&2
|
||||
echo "Unable to locate sentrux. Provide HYPERTWIST_SENTRUX_BINARY, run scripts/bootstrap-hypertwist-sentrux.sh, add sentrux to PATH, place a repo-local sentrux binary at $repo_root, or keep /home/dev/src/VectorShell/sentrux available." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
|
|
|||
15
tools/sentrux/README.md
Normal file
15
tools/sentrux/README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# HyperTwist-local sentrux landing zone
|
||||
|
||||
This directory is the HyperTwist-owned location for a repo-local `sentrux`
|
||||
binary.
|
||||
|
||||
Do not commit platform binaries here.
|
||||
|
||||
Use:
|
||||
|
||||
```bash
|
||||
scripts/bootstrap-hypertwist-sentrux.sh
|
||||
```
|
||||
|
||||
That helper materializes a local binary under `tools/sentrux/bin/` from the
|
||||
best retained source available on this machine.
|
||||
1
tools/sentrux/bin/.gitkeep
Normal file
1
tools/sentrux/bin/.gitkeep
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
import { ROUTER_FUTURE_FLAGS } from '../router/router-future'
|
||||
|
||||
const mockUsePlatformAuth = vi.fn()
|
||||
const mockGetAuthHealth = vi.fn()
|
||||
|
|
@ -31,7 +32,7 @@ function renderPage(page: React.ReactNode, initialEntry: string) {
|
|||
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter initialEntries={[initialEntry]}>
|
||||
<MemoryRouter initialEntries={[initialEntry]} future={ROUTER_FUTURE_FLAGS}>
|
||||
{page}
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>,
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('Selected help lane')).toBeTruthy()
|
||||
expect(screen.getByText('Launch readiness')).toBeTruthy()
|
||||
expect(screen.getByText(/turning the preview lane into a public launch/i)).toBeTruthy()
|
||||
expect(screen.getByText('If the desktop app is primary, why keep the web version?')).toBeTruthy()
|
||||
expect(screen.getByText('Is VR/controller support already fully finished?')).toBeTruthy()
|
||||
expect(screen.getByText('Support lanes')).toBeTruthy()
|
||||
})
|
||||
|
||||
|
|
@ -650,6 +652,10 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getAllByText(/MagicCube5D dedicated-family training map: passed/i).length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Operator playbooks')).toBeTruthy()
|
||||
expect(screen.getByText('Simulator use today')).toBeTruthy()
|
||||
expect(screen.getByText('Higher-dimensional runtime guide')).toBeTruthy()
|
||||
expect(screen.getByText('Current control and device posture')).toBeTruthy()
|
||||
expect(screen.getByText('Deployment readiness snapshot')).toBeTruthy()
|
||||
expect(screen.getByText('XR groundwork exists, but the full VR lane is not finished')).toBeTruthy()
|
||||
expect(screen.getByText('Higher-dimensional runtime ownership')).toBeTruthy()
|
||||
})
|
||||
|
||||
|
|
@ -715,6 +721,10 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('Operator manual')).toBeTruthy()
|
||||
expect(screen.getByText('1. Start in the browser shell')).toBeTruthy()
|
||||
expect(screen.getByText('Simulator manual')).toBeTruthy()
|
||||
expect(screen.getByText('Higher-dimensional family guide')).toBeTruthy()
|
||||
expect(screen.getByText('Deployment readiness manual')).toBeTruthy()
|
||||
expect(screen.getByText('Input and device posture')).toBeTruthy()
|
||||
expect(screen.getAllByText('Keyboard and mouse ship today').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Feature-registry-backed wording only')).toBeTruthy()
|
||||
expect(await screen.findByRole('link', { name: /open public docs portal/i })).toBeTruthy()
|
||||
})
|
||||
|
|
|
|||
88
website/src/pages/public-page-helpers.tsx
Normal file
88
website/src/pages/public-page-helpers.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import type { ReactNode } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import {
|
||||
brandConfig,
|
||||
downloadTargets,
|
||||
mplSourceUrl,
|
||||
openSourceRepoUrl,
|
||||
planCatalog,
|
||||
publicDocsUrl,
|
||||
releaseNotesUrl,
|
||||
} from '../site-config'
|
||||
import { isExternalHref } from '../site-routes'
|
||||
|
||||
export const supportTopicGuidance: Record<string, { title: string; description: string }> = {
|
||||
'launch-readiness': {
|
||||
title: 'Launch readiness',
|
||||
description:
|
||||
'Need help turning the preview lane into a public launch? We can walk through checkout wiring, download release targets, notices, and corresponding-source publication.',
|
||||
},
|
||||
'operator-access': {
|
||||
title: 'Operator access',
|
||||
description:
|
||||
'Use this lane when you need operator checkout, entitlement enablement, or help reaching the protected desktop-download surface.',
|
||||
},
|
||||
'studio-rollout': {
|
||||
title: 'Studio rollout',
|
||||
description:
|
||||
'Use this lane for higher-dimensional rollout planning, deployment coordination, or production-lane package and notice readiness.',
|
||||
},
|
||||
}
|
||||
|
||||
export const explorerFallbackPlan = planCatalog.find((plan) => plan.key === 'explorer') ?? planCatalog[0]
|
||||
export const operatorFallbackPlan = planCatalog.find((plan) => plan.key === 'operator') ?? planCatalog[1]
|
||||
export const studioFallbackPlan = planCatalog.find((plan) => plan.key === 'studio') ?? planCatalog[2]
|
||||
|
||||
export const releaseCommerceFallback = {
|
||||
operatorCheckoutUrl: isExternalHref(operatorFallbackPlan.ctaHref) ? operatorFallbackPlan.ctaHref : '',
|
||||
studioCheckoutUrl: isExternalHref(studioFallbackPlan.ctaHref) ? studioFallbackPlan.ctaHref : '',
|
||||
planPriceOperator: operatorFallbackPlan.price,
|
||||
planPriceStudio: studioFallbackPlan.price,
|
||||
}
|
||||
|
||||
export function buildPublicReleaseManifestFallback(supportEmail = brandConfig.contact.email) {
|
||||
return {
|
||||
downloadTargets,
|
||||
publicDocsUrl,
|
||||
releaseNotesUrl,
|
||||
correspondingSourceUrl: mplSourceUrl,
|
||||
openSourceRepoUrl,
|
||||
supportEmail,
|
||||
}
|
||||
}
|
||||
|
||||
export function Section({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
}: {
|
||||
title: string
|
||||
description?: string
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<section className="page-section">
|
||||
<div className="section-heading">
|
||||
<h2>{title}</h2>
|
||||
{description ? <p>{description}</p> : null}
|
||||
</div>
|
||||
{children}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export function PlanActionLink({ href, label }: { href: string; label: string }) {
|
||||
if (isExternalHref(href)) {
|
||||
return (
|
||||
<a className="button button--primary button--full" href={href} target="_blank" rel="noreferrer">
|
||||
{label}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link className="button button--primary button--full" to={href}>
|
||||
{label}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
536
website/src/pages/public-pages-commerce.tsx
Normal file
536
website/src/pages/public-pages-commerce.tsx
Normal file
|
|
@ -0,0 +1,536 @@
|
|||
import { useMemo } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { getReleaseManifest } from '../auth/auth-api'
|
||||
import { MarketingShell } from '../components/layout/MarketingShell'
|
||||
import { SiteMetadata } from '../components/seo/SiteMetadata'
|
||||
import { PublicLaunchStatus } from '../components/ui/PublicLaunchStatus'
|
||||
import { ReleaseValidationSummary } from '../components/ui/ReleaseValidationSummary'
|
||||
import { paddleReadyDescription } from '../site-config'
|
||||
import {
|
||||
buildReleaseMetadataItems,
|
||||
resolveReleaseCommerceView,
|
||||
resolveReleaseManifestView,
|
||||
} from '../release-manifest'
|
||||
import { buildProtectedDownloadPath, buildSupportPath } from '../site-routes'
|
||||
import {
|
||||
deliverySurfaceCards,
|
||||
desktopDownloadSteps,
|
||||
desktopReleaseSignals,
|
||||
digitalDeliveryCards,
|
||||
distributionDoctrineCards,
|
||||
openSourceNotices,
|
||||
privacyBoundaryCards,
|
||||
termsBoundaryCards,
|
||||
} from '../site-data'
|
||||
import {
|
||||
buildPublicReleaseManifestFallback,
|
||||
explorerFallbackPlan,
|
||||
operatorFallbackPlan,
|
||||
PlanActionLink,
|
||||
releaseCommerceFallback,
|
||||
Section,
|
||||
studioFallbackPlan,
|
||||
} from './public-page-helpers'
|
||||
|
||||
export function PricingPage() {
|
||||
const releaseManifestQuery = useQuery({
|
||||
queryKey: ['release-manifest', 'public'],
|
||||
queryFn: getReleaseManifest,
|
||||
retry: false,
|
||||
})
|
||||
const releaseCommerce = useMemo(
|
||||
() => resolveReleaseCommerceView(releaseManifestQuery.data?.manifest, releaseCommerceFallback),
|
||||
[releaseManifestQuery.data?.manifest],
|
||||
)
|
||||
const runtimePlanCatalog = useMemo(() => ([
|
||||
explorerFallbackPlan,
|
||||
{
|
||||
...operatorFallbackPlan,
|
||||
price: releaseCommerce.plan_price_operator,
|
||||
ctaLabel: releaseCommerce.operator_checkout_url ? 'Open Paddle checkout' : 'Request operator access',
|
||||
ctaHref: releaseCommerce.operator_checkout_url || buildSupportPath('operator-access'),
|
||||
},
|
||||
{
|
||||
...studioFallbackPlan,
|
||||
price: releaseCommerce.plan_price_studio,
|
||||
ctaLabel: releaseCommerce.studio_checkout_url ? 'Open Paddle checkout' : 'Talk to HyperTwist',
|
||||
ctaHref: releaseCommerce.studio_checkout_url || buildSupportPath('studio-rollout'),
|
||||
},
|
||||
]), [releaseCommerce])
|
||||
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist pricing"
|
||||
description="View HyperTwist browser-access, operator, and studio pricing with Paddle-ready checkout posture and launch-honest download/legal guidance."
|
||||
canonicalPath="/pricing"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Paddle-ready plans"
|
||||
title="Pricing that matches the actual delivery model."
|
||||
lede="The browser shell handles account, release, and billing access. The simulator remains desktop-first. Prices and checkouts can be switched live through the same Paddle-ready structure used across the broader product family."
|
||||
>
|
||||
<Section title="Plan lineup" description={paddleReadyDescription}>
|
||||
<div className="card-grid card-grid--pricing">
|
||||
{runtimePlanCatalog.map((plan) => (
|
||||
<article key={plan.key} className="card card--pricing">
|
||||
<p className="status-pill">{plan.name}</p>
|
||||
<h3>{plan.price}</h3>
|
||||
<p>{plan.notes}</p>
|
||||
<ul className="list">
|
||||
{plan.features.map((feature) => (
|
||||
<li key={feature}>{feature}</li>
|
||||
))}
|
||||
</ul>
|
||||
<PlanActionLink href={plan.ctaHref} label={plan.ctaLabel} />
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Launch posture">
|
||||
<PublicLaunchStatus />
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Why plans live in the browser while training stays native"
|
||||
description="Commercial access, entitlement, and launch-readiness posture belong to the browser shell so the simulator can stay focused on training quality."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{deliverySurfaceCards.slice(0, 3).map((surface) => (
|
||||
<article key={surface.title} className="card">
|
||||
<h3>{surface.title}</h3>
|
||||
<p>{surface.description}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Important launch note">
|
||||
<article className="callout">
|
||||
<p>
|
||||
Public pricing, checkout, and download pages are distribution surfaces. Before external launch,
|
||||
keep their legal footer and open-source notices link live and ensure the corresponding-source URL is configured for any downloadable build containing MPL-covered material.
|
||||
</p>
|
||||
</article>
|
||||
</Section>
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function DownloadPage() {
|
||||
const releaseManifestQuery = useQuery({
|
||||
queryKey: ['release-manifest', 'public'],
|
||||
queryFn: getReleaseManifest,
|
||||
retry: false,
|
||||
})
|
||||
|
||||
const releaseManifest = useMemo(
|
||||
() => resolveReleaseManifestView(releaseManifestQuery.data?.manifest, buildPublicReleaseManifestFallback()),
|
||||
[releaseManifestQuery.data?.manifest],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="Download HyperTwist desktop"
|
||||
description="Download the HyperTwist desktop build, preserve your target platform into the protected release surface, and pair the installed app with your browser account."
|
||||
canonicalPath="/download"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Desktop distribution"
|
||||
title="Download the desktop build and pair it with your browser account."
|
||||
lede="The website provides account, release, and legal surfaces. The actual simulator ships through the desktop lane, with package validation and release discipline carried over from the HyperTwist roadmap."
|
||||
>
|
||||
{releaseManifestQuery.isLoading ? (
|
||||
<Section title="Release manifest status">
|
||||
<article className="callout">
|
||||
<p>Loading the current server-backed release manifest. Static preview metadata remains visible until the live manifest arrives.</p>
|
||||
</article>
|
||||
</Section>
|
||||
) : null}
|
||||
|
||||
{releaseManifestQuery.isError ? (
|
||||
<Section title="Release manifest status">
|
||||
<article className="callout">
|
||||
<p>
|
||||
The live release manifest could not be loaded from the auth server right now.
|
||||
This page is showing bounded fallback site metadata instead of current runtime release authority.
|
||||
</p>
|
||||
</article>
|
||||
</Section>
|
||||
) : null}
|
||||
|
||||
<Section title="Available targets">
|
||||
<div className="card-grid">
|
||||
{releaseManifest.platforms.map((platform) => {
|
||||
const metadataItems = buildReleaseMetadataItems(platform)
|
||||
return (
|
||||
<article key={platform.platform_key} className="card">
|
||||
<h3>{platform.platform}</h3>
|
||||
<p className="status-pill">{platform.subtitle}</p>
|
||||
<p>{platform.details}</p>
|
||||
{metadataItems.length > 0 ? (
|
||||
<ul className="list top-gap">
|
||||
{metadataItems.map((item) => (
|
||||
<li key={`${platform.platform_key}-${item.label}`}>
|
||||
{item.label}: {item.value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
<ReleaseValidationSummary platform={platform} />
|
||||
{platform.configured ? (
|
||||
<Link className="button button--primary button--full" to={buildProtectedDownloadPath(platform.platform_key)}>
|
||||
Sign in for {platform.platform} access
|
||||
</Link>
|
||||
) : (
|
||||
<div className="button button--ghost button--full is-disabled" aria-disabled="true">
|
||||
Release URL not configured yet
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="How the release lane works">
|
||||
<div className="card-grid">
|
||||
{desktopDownloadSteps.map((step, index) => (
|
||||
<article key={step} className="card card--compact">
|
||||
<p className="status-pill">{index + 1}</p>
|
||||
<p>{step}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Release posture">
|
||||
<PublicLaunchStatus title="Desktop release access stays launch-honest" />
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Browser and desktop responsibilities"
|
||||
description="The release lane is easier to trust when the site explains why some actions stay public, some stay protected, and the simulator itself stays native."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{deliverySurfaceCards.map((surface) => (
|
||||
<article key={surface.title} className="card">
|
||||
<h3>{surface.title}</h3>
|
||||
<p>{surface.description}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Release integrity and documentation">
|
||||
<div className="card-grid">
|
||||
{desktopReleaseSignals.map((signal) => (
|
||||
<article key={signal.title} className="card">
|
||||
<h3>{signal.title}</h3>
|
||||
<p>{signal.description}</p>
|
||||
</article>
|
||||
))}
|
||||
<article className="card">
|
||||
<h3>Operator rollout references</h3>
|
||||
<ul className="list">
|
||||
{releaseManifest.public_docs_url ? (
|
||||
<li>
|
||||
Public docs:{' '}
|
||||
<a href={releaseManifest.public_docs_url} target="_blank" rel="noreferrer">
|
||||
{releaseManifest.public_docs_url}
|
||||
</a>
|
||||
</li>
|
||||
) : (
|
||||
<li>Public docs URL not configured yet.</li>
|
||||
)}
|
||||
{releaseManifest.release_notes_url ? (
|
||||
<li>
|
||||
Release notes:{' '}
|
||||
<a href={releaseManifest.release_notes_url} target="_blank" rel="noreferrer">
|
||||
{releaseManifest.release_notes_url}
|
||||
</a>
|
||||
</li>
|
||||
) : (
|
||||
<li>Release notes URL not configured yet.</li>
|
||||
)}
|
||||
<li>
|
||||
Notices and source posture:{' '}
|
||||
{releaseManifest.corresponding_source_url ? (
|
||||
<a href={releaseManifest.corresponding_source_url} target="_blank" rel="noreferrer">
|
||||
Corresponding source
|
||||
</a>
|
||||
) : (
|
||||
'configure the corresponding-source URL before external launch'
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Why this page does not expose raw download URLs">
|
||||
<article className="callout">
|
||||
<p>
|
||||
HyperTwist treats desktop distribution as an account-gated release surface.
|
||||
Public pages can describe supported targets and release posture, but the actual
|
||||
download links live behind the protected dashboard where plan and entitlement
|
||||
state are resolved.
|
||||
</p>
|
||||
<Link className="button button--ghost" to="/login">
|
||||
Sign in to check access
|
||||
</Link>
|
||||
</article>
|
||||
</Section>
|
||||
|
||||
<Section title="Pair the desktop app with your browser account">
|
||||
<article className="callout">
|
||||
<p>
|
||||
After sign-in, open the operator dashboard to generate a desktop-link token.
|
||||
That token is designed to hand browser identity and plan posture over to the local desktop app without exposing your password.
|
||||
</p>
|
||||
<Link className="button button--ghost" to="/app">
|
||||
Open dashboard
|
||||
</Link>
|
||||
</article>
|
||||
</Section>
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function OpenSourceNoticesPage() {
|
||||
const releaseManifestQuery = useQuery({
|
||||
queryKey: ['release-manifest', 'public'],
|
||||
queryFn: getReleaseManifest,
|
||||
retry: false,
|
||||
})
|
||||
const releaseManifest = useMemo(
|
||||
() => resolveReleaseManifestView(releaseManifestQuery.data?.manifest, buildPublicReleaseManifestFallback()),
|
||||
[releaseManifestQuery.data?.manifest],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist open-source notices"
|
||||
description="Review HyperTwist public open-source notices and corresponding-source guidance for downloadable distribution surfaces."
|
||||
canonicalPath="/open-source-notices"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Legal and notices"
|
||||
title="Open-source notices for public distribution surfaces."
|
||||
lede="HyperTwist pricing, checkout, release, and download pages must make legal and corresponding-source guidance visible whenever shipped builds include MPL-covered material."
|
||||
>
|
||||
<Section title="Key components">
|
||||
<div className="card-grid">
|
||||
{openSourceNotices.map((item) => (
|
||||
<article className="card" key={item.component}>
|
||||
<h3>{item.component}</h3>
|
||||
<p className="status-pill">{item.license}</p>
|
||||
<p>{item.whyItMatters}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Corresponding source">
|
||||
<div className="card">
|
||||
<p>
|
||||
MPL-covered shipped builds need a stable corresponding-source location for the exact distributed material.
|
||||
</p>
|
||||
<ul className="list">
|
||||
<li>
|
||||
Public corresponding-source URL:{' '}
|
||||
{releaseManifest.corresponding_source_url ? (
|
||||
<a href={releaseManifest.corresponding_source_url} target="_blank" rel="noreferrer">{releaseManifest.corresponding_source_url}</a>
|
||||
) : (
|
||||
'configure the public corresponding-source URL before external launch'
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
Public repository / notices reference:{' '}
|
||||
{releaseManifest.open_source_repo_url ? (
|
||||
<a href={releaseManifest.open_source_repo_url} target="_blank" rel="noreferrer">{releaseManifest.open_source_repo_url}</a>
|
||||
) : (
|
||||
'configure the public repository/notices URL before external launch'
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Official MPL 2.0 license text:{' '}
|
||||
<a href="https://www.mozilla.org/en-US/MPL/2.0/" target="_blank" rel="noreferrer">
|
||||
https://www.mozilla.org/en-US/MPL/2.0/
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Distribution readiness">
|
||||
<PublicLaunchStatus title="Notices and corresponding-source readiness" />
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Distribution doctrine"
|
||||
description="These public legal surfaces should explain why the website is part of the release story without pretending it has replaced the native simulator."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{distributionDoctrineCards.map((card) => (
|
||||
<article className="card" key={card.title}>
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function PrivacyPage() {
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist privacy"
|
||||
description="Understand the HyperTwist privacy posture for browser account access, desktop-link issuance, and the desktop-first simulator boundary."
|
||||
canonicalPath="/privacy"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Privacy"
|
||||
title="Privacy posture"
|
||||
lede="HyperTwist keeps the browser shell narrow and the simulator desktop-first. Privacy descriptions must reflect that separation clearly."
|
||||
>
|
||||
<Section title="Core points">
|
||||
<article className="card">
|
||||
<ul className="list">
|
||||
<li>The public website stores account/session data needed for authentication, plan access, and desktop-link issuance.</li>
|
||||
<li>The browser shell does not claim ownership over the full simulator runtime state unless a future browser-client packet is explicitly opened.</li>
|
||||
<li>Support, billing, and release operations should collect only the data required to deliver digital access and maintain legal compliance.</li>
|
||||
</ul>
|
||||
</article>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Practical privacy boundary"
|
||||
description="Privacy wording should follow the actual product split instead of flattening the browser shell and native simulator into one vague surface."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{privacyBoundaryCards.map((card) => (
|
||||
<article key={card.title} className="card">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function TermsPage() {
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist terms"
|
||||
description="Review the HyperTwist terms of access for browser account routes, desktop delivery, and open-source notice obligations."
|
||||
canonicalPath="/terms"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Terms"
|
||||
title="Terms of access"
|
||||
lede="HyperTwist access is digital-first and plan-gated. Terms should match the actual delivery and account model."
|
||||
>
|
||||
<Section title="Service posture">
|
||||
<article className="card">
|
||||
<ul className="list">
|
||||
<li>Browser access covers public pages, account, release, download, and operator/dashboard surfaces.</li>
|
||||
<li>The simulator itself is delivered through the desktop lane unless a later browser-client branch is explicitly opened.</li>
|
||||
<li>Downloaded builds and their public distribution pages remain subject to open-source notice and corresponding-source disclosure rules where applicable.</li>
|
||||
</ul>
|
||||
</article>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Terms in practice"
|
||||
description="These terms-oriented boundaries keep the public site, protected dashboard, and desktop simulator aligned with the actual delivery model."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{termsBoundaryCards.map((card) => (
|
||||
<article key={card.title} className="card">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function ShippingPaymentPage() {
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist shipping and payment"
|
||||
description="HyperTwist is delivered digitally through account access, release pages, and desktop downloads with Paddle-ready billing posture."
|
||||
canonicalPath="/shipping-payment"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Shipping & payment"
|
||||
title="Digital delivery only"
|
||||
lede="HyperTwist is not a physical-goods storefront. Delivery happens through authenticated account access, release pages, and desktop downloads."
|
||||
>
|
||||
<Section title="Delivery model">
|
||||
<article className="card">
|
||||
<p>{paddleReadyDescription}</p>
|
||||
<ul className="list">
|
||||
<li>No physical goods ship through this site.</li>
|
||||
<li>Pricing and checkout are structured for Paddle-backed digital plans.</li>
|
||||
<li>Desktop downloads must remain paired with public notices and legal links when required by shipped-code obligations.</li>
|
||||
</ul>
|
||||
</article>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Digital delivery workflow"
|
||||
description="A professional digital-delivery lane does more than expose a buy button. It keeps release posture, entitlement, and the desktop handoff coherent."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{digitalDeliveryCards.map((card) => (
|
||||
<article key={card.title} className="card">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
733
website/src/pages/public-pages-marketing.tsx
Normal file
733
website/src/pages/public-pages-marketing.tsx
Normal file
|
|
@ -0,0 +1,733 @@
|
|||
import { useDeferredValue, useMemo, useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { ArrowRight, BookOpenText, Boxes, Download, ExternalLink, Landmark, MonitorCog, Sparkles } from 'lucide-react'
|
||||
import { Link, useSearchParams } from 'react-router-dom'
|
||||
import { getReleaseManifest } from '../auth/auth-api'
|
||||
import { MarketingShell } from '../components/layout/MarketingShell'
|
||||
import { SiteMetadata } from '../components/seo/SiteMetadata'
|
||||
import { PublicLaunchStatus } from '../components/ui/PublicLaunchStatus'
|
||||
import { brandConfig } from '../site-config'
|
||||
import { formatReleasePublishedAt, resolveReleaseManifestView } from '../release-manifest'
|
||||
import { getReleasePlatformValidationSummary } from '../shared/package-validation'
|
||||
import {
|
||||
capabilityPillars,
|
||||
changelogEntries,
|
||||
companyNarrative,
|
||||
deliverySurfaceCards,
|
||||
deploymentReadinessTracks,
|
||||
heroMetrics,
|
||||
higherDimensionalRuntimeGuideCards,
|
||||
inputAndDevicePostureCards,
|
||||
operatorManualTracks,
|
||||
operatorPlaybooks,
|
||||
publicDocumentationPrinciples,
|
||||
releaseStoryCards,
|
||||
resourceCollections,
|
||||
roadmapHonestyCards,
|
||||
shippingNowCards,
|
||||
simulatorManualCards,
|
||||
supportFaqs,
|
||||
} from '../site-data'
|
||||
import { buildPublicReleaseManifestFallback, Section, supportTopicGuidance } from './public-page-helpers'
|
||||
|
||||
export function HomeLanding() {
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist"
|
||||
description="HyperTwist is a native cube and hypercube training environment for recognition, replay, coaching, higher-dimensional runtime ownership, and desktop-first operator workflows."
|
||||
canonicalPath="/"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Desktop-first training. Browser-first operator access."
|
||||
title="HyperTwist turns cube practice into a real operator-grade training stack."
|
||||
lede="Recognition, replay, coaching, analytics, higher-dimensional runtime ownership, public distribution, and browser-based account access all live under one honest product story."
|
||||
>
|
||||
<section className="hero-panel">
|
||||
<div className="hero-grid">
|
||||
<div className="hero-copy">
|
||||
<p>
|
||||
HyperTwist is a native training environment for classic cube, higher-dimensional
|
||||
families, browser-assisted recognition, replay explanation, and desktop packaging.
|
||||
The public site is intentionally honest: the desktop runtime is real, the browser
|
||||
account/dashboard is real, and the optional full-browser simulator path remains spec-only.
|
||||
</p>
|
||||
<div className="button-row">
|
||||
<Link className="button button--primary" to="/download">
|
||||
Download desktop app
|
||||
</Link>
|
||||
<Link className="button button--ghost" to="/app">
|
||||
Open operator dashboard
|
||||
</Link>
|
||||
<Link className="button button--ghost" to="/pricing">
|
||||
View pricing
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hero-visual-card">
|
||||
<img
|
||||
src="/branding/hypertwist-3d-symbol.png"
|
||||
alt="HyperTwist symbol"
|
||||
className="hero-visual-card__image"
|
||||
/>
|
||||
<p className="hero-visual-card__caption">
|
||||
Browser account shell outside the simulator. Native Unreal runtime inside the simulator.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="metric-grid">
|
||||
{heroMetrics.map((metric) => (
|
||||
<article key={metric.label} className="metric-card">
|
||||
<strong>{metric.value}</strong>
|
||||
<span>{metric.label}</span>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Section
|
||||
title="Current public site status"
|
||||
description="The homepage now exposes the same bounded preview-versus-launch truth that governs the release and pricing lanes."
|
||||
>
|
||||
<PublicLaunchStatus title="Public website and release posture" />
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="What ships now"
|
||||
description="The public site only describes current product truth or explicitly marked retained/spec-only branches."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{shippingNowCards.map((item) => (
|
||||
<article key={item} className="card card--compact">
|
||||
<Sparkles size={18} />
|
||||
<p>{item}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Capability pillars"
|
||||
description="The page structure borrows the FamiliarOS and ScriptoriumAI public-shell discipline, then rebrands the actual product around HyperTwist runtime authority."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{capabilityPillars.map((pillar) => (
|
||||
<article key={pillar.title} className="card">
|
||||
<h3>{pillar.title}</h3>
|
||||
<p>{pillar.description}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Roadmap-honest posture"
|
||||
description="Important boundaries stay visible instead of being blurred into vague marketing claims."
|
||||
>
|
||||
<div className="split-grid">
|
||||
{roadmapHonestyCards.map((item) => (
|
||||
<article key={item} className="callout">
|
||||
<p>{item}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Delivery surfaces"
|
||||
description="Use the website for account, release, pricing, and notices. Use the desktop runtime for the core simulator."
|
||||
>
|
||||
<div className="feature-band">
|
||||
<article className="feature-band__card">
|
||||
<MonitorCog size={22} />
|
||||
<h3>Browser account and operator shell</h3>
|
||||
<p>Authenticated browser access for release posture, desktop pairing, notices, and operator state.</p>
|
||||
<Link to="/app" className="inline-link">
|
||||
Open dashboard <ArrowRight size={15} />
|
||||
</Link>
|
||||
</article>
|
||||
<article className="feature-band__card">
|
||||
<Download size={22} />
|
||||
<h3>Desktop download and package lane</h3>
|
||||
<p>Public download posture for the native Unreal build, with legal linkage already wired in.</p>
|
||||
<Link to="/download" className="inline-link">
|
||||
Open download center <ArrowRight size={15} />
|
||||
</Link>
|
||||
</article>
|
||||
<article className="feature-band__card">
|
||||
<Landmark size={22} />
|
||||
<h3>Checkout and notices discipline</h3>
|
||||
<p>Paddle-ready pricing plus public notices surfaces for any downloadable build containing MPL-covered material.</p>
|
||||
<Link to="/open-source-notices" className="inline-link">
|
||||
Review notices <ArrowRight size={15} />
|
||||
</Link>
|
||||
</article>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Why both browser and desktop stay"
|
||||
description="The public site owns operator and distribution work the simulator should not dilute, while the simulator stays native for the runtime-heavy training job."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{deliverySurfaceCards.map((surface) => (
|
||||
<article key={surface.title} className="card">
|
||||
<h3>{surface.title}</h3>
|
||||
<p>{surface.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{surface.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function AboutPage() {
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="About HyperTwist"
|
||||
description="Learn why HyperTwist exists, how it treats higher-dimensional training as first-class work, and why the public site stays honest about desktop-first simulator truth."
|
||||
canonicalPath="/about"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Why HyperTwist exists"
|
||||
title="A training stack serious enough for higher-dimensional cubing."
|
||||
lede="HyperTwist exists because cubers deserve one coherent system for physical recognition, explanation, practice, replay, analytics, and hyper puzzle ownership."
|
||||
>
|
||||
<Section title="Mission">
|
||||
<div className="card">
|
||||
<p>{companyNarrative.mission}</p>
|
||||
<p>{companyNarrative.posture}</p>
|
||||
<p>{companyNarrative.distribution}</p>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Why the web surface remains necessary"
|
||||
description="Keeping the website does not weaken the desktop-first thesis. It keeps public distribution, release, and operator-governance work outside the simulator proper."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{deliverySurfaceCards.map((surface) => (
|
||||
<article key={surface.title} className="card">
|
||||
<h3>{surface.title}</h3>
|
||||
<p>{surface.description}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="What makes the product different">
|
||||
<div className="card-grid">
|
||||
<article className="card">
|
||||
<Boxes size={22} />
|
||||
<h3>It treats higher-dimensional puzzles as first-class work</h3>
|
||||
<p>120-cell and 5D runtime ownership are not hand-wavy aspirations. They are part of the current product truth.</p>
|
||||
</article>
|
||||
<article className="card">
|
||||
<BookOpenText size={22} />
|
||||
<h3>It stays roadmap-honest</h3>
|
||||
<p>Shipped, retained, and spec-only surfaces remain clearly separated so public copy matches actual authority.</p>
|
||||
</article>
|
||||
<article className="card">
|
||||
<MonitorCog size={22} />
|
||||
<h3>It separates browser shell from simulator truth</h3>
|
||||
<p>The public web surface helps operators access the product without pretending the browser already replaces the desktop runtime.</p>
|
||||
</article>
|
||||
</div>
|
||||
</Section>
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function ResourcesPage() {
|
||||
const [query, setQuery] = useState('')
|
||||
const deferredQuery = useDeferredValue(query)
|
||||
const windowsValidationSummary = getReleasePlatformValidationSummary('windows')
|
||||
|
||||
const filteredCollections = useMemo(() => {
|
||||
const normalized = deferredQuery.trim().toLowerCase()
|
||||
if (!normalized) return resourceCollections
|
||||
return resourceCollections
|
||||
.map((collection) => ({
|
||||
...collection,
|
||||
items: collection.items.filter((item) => item.toLowerCase().includes(normalized) || collection.title.toLowerCase().includes(normalized)),
|
||||
}))
|
||||
.filter((collection) => collection.items.length > 0)
|
||||
}, [deferredQuery])
|
||||
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist resources"
|
||||
description="Browse product-safe HyperTwist resources for rollout, release notes, documentation, operator onboarding, and higher-dimensional product truth."
|
||||
canonicalPath="/resources"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Public resources"
|
||||
title="Resources that explain the product without leaking operator-only internals."
|
||||
lede="This surface is shaped after the FamiliarOS and ScriptoriumAI public resource pages, but constrained to safe HyperTwist product-facing material."
|
||||
>
|
||||
<Section title="Resource finder" description="Search the public resource categories that matter to operators and buyers.">
|
||||
<label className="input-label" htmlFor="resource-query">Filter resources</label>
|
||||
<input
|
||||
id="resource-query"
|
||||
className="input"
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder="Search training, rollout, notices..."
|
||||
/>
|
||||
<div className="card-grid top-gap">
|
||||
{filteredCollections.map((collection) => (
|
||||
<article key={collection.title} className="card">
|
||||
<h3>{collection.title}</h3>
|
||||
<ul className="list">
|
||||
{collection.items.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Direct routes">
|
||||
<div className="feature-band">
|
||||
<Link to="/docs" className="feature-band__card feature-band__card--link">
|
||||
<BookOpenText size={22} />
|
||||
<h3>Docs landing</h3>
|
||||
<p>Product-facing documentation, boundaries, and rollout guidance.</p>
|
||||
</Link>
|
||||
<Link to="/support" className="feature-band__card feature-band__card--link">
|
||||
<ExternalLink size={22} />
|
||||
<h3>Support</h3>
|
||||
<p>Contact, rollout questions, and account/download help.</p>
|
||||
</Link>
|
||||
<Link to="/changelog" className="feature-band__card feature-band__card--link">
|
||||
<Sparkles size={22} />
|
||||
<h3>Release notes</h3>
|
||||
<p>Recent public-facing packets and posture updates.</p>
|
||||
</Link>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Operator playbooks"
|
||||
description="These are the public-safe working patterns that matter once a team moves from curiosity into real rollout."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{operatorPlaybooks.map((playbook) => (
|
||||
<article key={playbook.title} className="card">
|
||||
<h3>{playbook.title}</h3>
|
||||
<ul className="list top-gap">
|
||||
{playbook.steps.map((step) => (
|
||||
<li key={step}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Simulator use today"
|
||||
description="This summary is deliberately practical: what you actually do in the desktop runtime once browser identity and release posture are already resolved."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{simulatorManualCards.map((card) => (
|
||||
<article key={card.title} className="card">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Higher-dimensional runtime guide"
|
||||
description="These public-safe cards explain which higher-dimensional family lanes are already real and what runtime posture each one actually uses."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{higherDimensionalRuntimeGuideCards.map((card) => (
|
||||
<article key={card.title} className="card">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Current control and device posture"
|
||||
description="This keeps public resources honest about what input/runtime ownership is already strong and what still remains a later native completion packet."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{inputAndDevicePostureCards.map((card) => (
|
||||
<article key={card.title} className="card">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Deployment readiness snapshot"
|
||||
description="The public site can be detailed without becoming misleading when rollout guidance stays separated into identity, package, and legal lanes."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{deploymentReadinessTracks.map((track) => (
|
||||
<article key={track.title} className="card">
|
||||
<h3>{track.title}</h3>
|
||||
<ul className="list top-gap">
|
||||
{track.steps.map((step) => (
|
||||
<li key={step}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{windowsValidationSummary ? (
|
||||
<Section title="Current packaged desktop proof">
|
||||
<article className="card">
|
||||
<h3>{windowsValidationSummary.lane}</h3>
|
||||
<p>
|
||||
Latest public-safe package evidence was generated{' '}
|
||||
{formatReleasePublishedAt(windowsValidationSummary.generated_at) || windowsValidationSummary.generated_at}
|
||||
{' '}in {windowsValidationSummary.configuration} mode and passed across{' '}
|
||||
{windowsValidationSummary.smoke_map_count} dedicated-family higher-dimensional training map{windowsValidationSummary.smoke_map_count === 1 ? '' : 's'}.
|
||||
</p>
|
||||
<ul className="list top-gap">
|
||||
{windowsValidationSummary.smoke_maps.map((map) => (
|
||||
<li key={map.map_url}>
|
||||
{map.label}: {map.result}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="button-row top-gap">
|
||||
<Link className="button button--ghost" to="/download">
|
||||
Open download center
|
||||
</Link>
|
||||
<Link className="button button--ghost" to="/app">
|
||||
Open operator dashboard
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
</Section>
|
||||
) : null}
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function DocsPage() {
|
||||
const releaseManifestQuery = useQuery({
|
||||
queryKey: ['release-manifest', 'public'],
|
||||
queryFn: getReleaseManifest,
|
||||
retry: false,
|
||||
})
|
||||
const releaseManifest = useMemo(
|
||||
() => resolveReleaseManifestView(releaseManifestQuery.data?.manifest, buildPublicReleaseManifestFallback()),
|
||||
[releaseManifestQuery.data?.manifest],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist documentation"
|
||||
description="Open the HyperTwist public documentation lane for feature-registry-backed product truth, roadmap honesty, and distribution guidance."
|
||||
canonicalPath="/docs"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Documentation posture"
|
||||
title="HyperTwist documentation stays capability-accurate."
|
||||
lede="The public docs surface points users to product-safe truth: feature registry discipline, roadmap honesty, release notes, and distribution/legal guidance."
|
||||
>
|
||||
<Section title="Public documentation lanes">
|
||||
<div className="card-grid">
|
||||
{publicDocumentationPrinciples.map((principle) => (
|
||||
<article key={principle.title} className="card">
|
||||
<h3>{principle.title}</h3>
|
||||
<p>{principle.description}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Operator manual"
|
||||
description="This is the public-facing manual for how HyperTwist is actually used today: browser first for identity and release posture, desktop first for the simulator."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{operatorManualTracks.map((track) => (
|
||||
<article key={track.title} className="card">
|
||||
<h3>{track.title}</h3>
|
||||
<p>{track.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{track.steps.map((step) => (
|
||||
<li key={step}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Browser versus simulator boundary"
|
||||
description="The docs stay professional by being explicit about what each surface does better."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{deliverySurfaceCards.map((surface) => (
|
||||
<article key={surface.title} className="card">
|
||||
<h3>{surface.title}</h3>
|
||||
<p>{surface.description}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Simulator manual"
|
||||
description="These are the current product-safe usage tracks for the native runtime itself."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{simulatorManualCards.map((card) => (
|
||||
<article key={card.title} className="card">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Higher-dimensional family guide"
|
||||
description="These are the currently represented higher-dimensional lanes and the truthful host/runtime posture for each."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{higherDimensionalRuntimeGuideCards.map((card) => (
|
||||
<article key={card.title} className="card">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Deployment readiness manual"
|
||||
description="This is the public-safe checklist for moving from preview posture to operator-grade rollout."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{deploymentReadinessTracks.map((track) => (
|
||||
<article key={track.title} className="card">
|
||||
<h3>{track.title}</h3>
|
||||
<ul className="list top-gap">
|
||||
{track.steps.map((step) => (
|
||||
<li key={step}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Input and device posture"
|
||||
description="Public documentation should be explicit about the current control/runtime truth instead of blurring groundwork and finished VR claims together."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{inputAndDevicePostureCards.map((card) => (
|
||||
<article key={card.title} className="card">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{releaseManifest.public_docs_url ? (
|
||||
<Section title="External docs portal">
|
||||
<a className="button button--primary" href={releaseManifest.public_docs_url} target="_blank" rel="noreferrer">
|
||||
Open public docs portal
|
||||
</a>
|
||||
</Section>
|
||||
) : null}
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function SupportPage() {
|
||||
const [searchParams] = useSearchParams()
|
||||
const supportTopic = searchParams.get('topic')
|
||||
const selectedSupportTopic = supportTopic ? supportTopicGuidance[supportTopic] : null
|
||||
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist support"
|
||||
description="Get help with HyperTwist rollout, desktop downloads, pricing, legal readiness, and browser-to-desktop operator access."
|
||||
canonicalPath="/support"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Operator help"
|
||||
title="Support for rollout, downloads, pricing, and browser-to-desktop access."
|
||||
lede="Support is focused on helping operators understand what the browser shell does, what the desktop build does, and how the two connect."
|
||||
>
|
||||
{selectedSupportTopic ? (
|
||||
<Section title="Selected help lane">
|
||||
<article className="callout">
|
||||
<p className="status-pill status-pill--info">{selectedSupportTopic.title}</p>
|
||||
<p>{selectedSupportTopic.description}</p>
|
||||
</article>
|
||||
</Section>
|
||||
) : null}
|
||||
|
||||
<Section title="Contact">
|
||||
<article className="card">
|
||||
<p>
|
||||
Email <a href={brandConfig.contact.emailHref}>{brandConfig.contact.email}</a> for account activation,
|
||||
release enablement, pricing configuration, or deployment help.
|
||||
</p>
|
||||
</article>
|
||||
</Section>
|
||||
|
||||
<Section title="Frequently asked questions">
|
||||
<div className="card-grid">
|
||||
{supportFaqs.map((faq) => (
|
||||
<article className="card" key={faq.question}>
|
||||
<h3>{faq.question}</h3>
|
||||
<p>{faq.answer}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Support lanes"
|
||||
description="Support works best when operators know whether they need public guidance, protected browser access, or native desktop follow-through."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{operatorPlaybooks.map((playbook) => (
|
||||
<article key={playbook.title} className="card">
|
||||
<h3>{playbook.title}</h3>
|
||||
<ul className="list top-gap">
|
||||
{playbook.steps.map((step) => (
|
||||
<li key={step}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function ChangelogPage() {
|
||||
const releaseManifestQuery = useQuery({
|
||||
queryKey: ['release-manifest', 'public'],
|
||||
queryFn: getReleaseManifest,
|
||||
retry: false,
|
||||
})
|
||||
const releaseManifest = useMemo(
|
||||
() => resolveReleaseManifestView(releaseManifestQuery.data?.manifest, buildPublicReleaseManifestFallback()),
|
||||
[releaseManifestQuery.data?.manifest],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist release notes"
|
||||
description="Review recent public-facing HyperTwist changes across browser operator access, package hardening, diagnostics, and distribution readiness."
|
||||
canonicalPath="/changelog"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Release notes"
|
||||
title="Recent public-facing HyperTwist changes."
|
||||
lede="This page focuses on user-visible posture changes: browser operator access, package hardening, diagnostics, and distribution readiness."
|
||||
>
|
||||
<Section title="Latest changes">
|
||||
<div className="timeline">
|
||||
{changelogEntries.map((entry) => (
|
||||
<article key={`${entry.date}-${entry.title}`} className="timeline-entry">
|
||||
<p className="timeline-entry__date">{entry.date}</p>
|
||||
<h3>{entry.title}</h3>
|
||||
<p>{entry.details}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="How to read the release feed"
|
||||
description="HyperTwist release notes stay useful when they distinguish simulator/runtime work, browser-operator work, and distribution/legal hardening instead of collapsing them together."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{releaseStoryCards.map((card) => (
|
||||
<article key={card.title} className="card">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
<ul className="list top-gap">
|
||||
{card.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{releaseManifest.release_notes_url ? (
|
||||
<Section title="External release notes">
|
||||
<a className="button button--ghost" href={releaseManifest.release_notes_url} target="_blank" rel="noreferrer">
|
||||
Open release feed
|
||||
</a>
|
||||
</Section>
|
||||
) : null}
|
||||
</MarketingShell>
|
||||
</>
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,18 +2,18 @@ import { Suspense, lazy, type ReactNode } from 'react'
|
|||
import { Route } from 'react-router-dom'
|
||||
import { GeneralPageLoader } from '../components/ui/Skeletons'
|
||||
|
||||
const HomeLanding = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.HomeLanding })))
|
||||
const AboutPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.AboutPage })))
|
||||
const ResourcesPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.ResourcesPage })))
|
||||
const DocsPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.DocsPage })))
|
||||
const SupportPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.SupportPage })))
|
||||
const ChangelogPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.ChangelogPage })))
|
||||
const PricingPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.PricingPage })))
|
||||
const DownloadPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.DownloadPage })))
|
||||
const OpenSourceNoticesPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.OpenSourceNoticesPage })))
|
||||
const PrivacyPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.PrivacyPage })))
|
||||
const TermsPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.TermsPage })))
|
||||
const ShippingPaymentPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.ShippingPaymentPage })))
|
||||
const HomeLanding = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.HomeLanding })))
|
||||
const AboutPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.AboutPage })))
|
||||
const ResourcesPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.ResourcesPage })))
|
||||
const DocsPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.DocsPage })))
|
||||
const SupportPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.SupportPage })))
|
||||
const ChangelogPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.ChangelogPage })))
|
||||
const PricingPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.PricingPage })))
|
||||
const DownloadPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.DownloadPage })))
|
||||
const OpenSourceNoticesPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.OpenSourceNoticesPage })))
|
||||
const PrivacyPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.PrivacyPage })))
|
||||
const TermsPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.TermsPage })))
|
||||
const ShippingPaymentPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.ShippingPaymentPage })))
|
||||
const LoginPage = lazy(() => import('../pages/auth-pages').then((m) => ({ default: m.LoginPage })))
|
||||
const RegisterPage = lazy(() => import('../pages/auth-pages').then((m) => ({ default: m.RegisterPage })))
|
||||
|
||||
|
|
|
|||
|
|
@ -168,6 +168,93 @@ export const simulatorManualCards = [
|
|||
},
|
||||
] as const
|
||||
|
||||
export const higherDimensionalRuntimeGuideCards = [
|
||||
{
|
||||
title: 'Magic120Cell packaged runtime',
|
||||
description: 'Use the packaged native training lane when you need real 120-cell runtime-state, projection ownership, and persistence-aware family behavior.',
|
||||
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.',
|
||||
'Treat the public website as documentation and rollout support, not as the runtime that executes this family.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'MagicCube5D packaged runtime',
|
||||
description: 'Use the packaged native training lane when you need real 5D projection, stereo, focus, and face-visibility ownership.',
|
||||
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.',
|
||||
'Keep this lane described honestly as packaged native behavior rather than browser simulator parity.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'MagicTile embedded-browser runtime',
|
||||
description: 'Use the embedded browser lane for the current non-Euclidean tiling host posture while native renderer widening remains intentionally gated.',
|
||||
bullets: [
|
||||
'Treat the embedded browser/CEF shell as the current shipped host for the live MagicTile interaction lane.',
|
||||
'Keep shared scramble normalization, timer transport, and state-bridge ownership attached to the first-party native shell and bridge.',
|
||||
'Do not describe a separate native renderer port as live while the renderer-widening gate stays explicit No-Go.',
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
||||
export const inputAndDevicePostureCards = [
|
||||
{
|
||||
title: 'Keyboard and mouse ship today',
|
||||
description: 'Classic-cube play and bounded keyboard-driven higher-dimensional interaction are already real current product lanes.',
|
||||
bullets: [
|
||||
'Classic-cube runtime input already includes click, touch, orbit, zoom, and bounded keyboard move intent.',
|
||||
'The current classic keyboard profile is the shipped `classic-wca-keyboard/v1` mapping.',
|
||||
'Treat this as real simulator input ownership, not as a browser-side placeholder.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Higher-dimensional view controls are already owned',
|
||||
description: 'Magic120Cell and MagicCube5D already carry first-party projection, focus, symmetry or stereo, and visibility defaults in the packaged runtime.',
|
||||
bullets: [
|
||||
'Magic120Cell keeps symmetry, logical-visibility depth, and center-cell focus posture explicit.',
|
||||
'MagicCube5D keeps projection-distance, stereo, face-visibility, and focus posture explicit.',
|
||||
'These controls belong to the dedicated-family desktop runtime lanes, not to the public website.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'XR groundwork exists, but the full VR lane is not finished',
|
||||
description: 'EnhancedInput posture and motion-controller groundwork exist, but HyperTwist does not yet market a fully finished OpenXR/controller/rebinding runtime lane.',
|
||||
bullets: [
|
||||
'Project config already carries EnhancedInput plus Vive, Oculus Touch, Mixed Reality, and Valve Index axis groundwork.',
|
||||
'Current product truth should not overclaim headset-specific runtime ownership or polished user-facing input rebinding.',
|
||||
'A later native XR completion packet still needs dedicated runtime owners, user-facing settings, and Windows package validation with controller truth.',
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
||||
export const deploymentReadinessTracks = [
|
||||
{
|
||||
title: '1. Identity and access posture',
|
||||
steps: [
|
||||
'Confirm shared browser auth is live before inviting operators into the protected release lane.',
|
||||
'Use the protected dashboard to resolve plan, entitlement, and desktop-link token posture.',
|
||||
'Keep browser-to-desktop handoff explicit so the installed app never depends on password reuse.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '2. Release and package posture',
|
||||
steps: [
|
||||
'Publish release-manifest truth for the active desktop targets before widening any public launch language.',
|
||||
'Keep package validation proof visible beside download posture so rollout remains evidence-backed.',
|
||||
'Differentiate browser-shell changes from simulator/package changes in release notes and support guidance.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '3. Legal and source posture',
|
||||
steps: [
|
||||
'Keep notices and corresponding-source links visible anywhere pricing, checkout, or downloads are exposed.',
|
||||
'Treat public pricing/download surfaces as part of the distributed product, not detached brochure pages.',
|
||||
'Do not let launch copy outrun the actual checkout, release, or source-availability configuration.',
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
||||
export const publicDocumentationPrinciples = [
|
||||
{
|
||||
title: 'Feature-registry-backed wording only',
|
||||
|
|
@ -388,6 +475,15 @@ export const resourceCollections = [
|
|||
'120-cell and 5D dedicated-family runtime ownership overview',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Deployment readiness',
|
||||
items: [
|
||||
'Shared auth and browser-to-desktop pairing posture',
|
||||
'Package validation proof and release-manifest interpretation',
|
||||
'Pricing, checkout, notices, and corresponding-source coordination',
|
||||
'Launch-readiness distinction between preview, protected, and public lanes',
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
||||
export const changelogEntries = [
|
||||
|
|
@ -454,6 +550,18 @@ export const supportFaqs = [
|
|||
question: 'Is the simulator fully in the browser?',
|
||||
answer: 'No. The current shipping lane is desktop-first and Unreal-backed. The public website offers account, operator, support, and download access, while the optional full-browser simulator path remains spec-only.',
|
||||
},
|
||||
{
|
||||
question: 'If the desktop app is primary, why keep the web version?',
|
||||
answer: 'Because the browser shell owns the parts that should stay outside the simulator: public positioning, account access, billing, release posture, download gating, notices, and browser-to-desktop pairing. Keeping that work on the web makes the native runtime easier to trust and easier to operate.',
|
||||
},
|
||||
{
|
||||
question: 'Is VR/controller support already fully finished?',
|
||||
answer: 'Not yet. HyperTwist already has real EnhancedInput posture and motion-controller groundwork, but it does not yet claim a fully finished OpenXR/controller/runtime or polished rebinding lane.',
|
||||
},
|
||||
{
|
||||
question: 'Can I already customize controls and higher-dimensional view posture?',
|
||||
answer: 'Partly. The current desktop runtime already owns the classic keyboard profile plus higher-dimensional projection, focus, and visibility defaults, but a broader polished user-facing preferences and rebinding layer is still a later native packet.',
|
||||
},
|
||||
{
|
||||
question: 'Can I download a build immediately after sign-in?',
|
||||
answer: 'Yes, once a release URL is configured for your plan. The dashboard also exposes a desktop-link token so the browser account can pair with the desktop app safely.',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue