Expose dashboard attempt review, run control, and queue action surfaces

This commit is contained in:
axiomlogicnexus 2026-05-09 21:17:24 +02:00
parent ea0739b680
commit e53b48c026
5 changed files with 493 additions and 10 deletions

View file

@ -11153,6 +11153,229 @@ UHyperTwistCoachDashboardWidget::GetDisplayedAttemptResultActionEnablementInspec
return Surface;
}
FHyperTwistTrainingCoachDashboardAttemptReviewNavigationInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedAttemptReviewNavigationInspectSurface() const
{
FHyperTwistTrainingCoachDashboardAttemptReviewNavigationInspectSurface Surface;
Surface.Headline = TEXT("Attempt-review navigation");
const bool bHasReviewedAttempt =
SelectedAttemptIndex >= 0 && SelectedAttemptIndex < CachedRunState.Attempts.Num();
const int32 TotalAttemptCount = CachedRunState.Attempts.Num();
const bool bNavigationEnabled = TotalAttemptCount > 1;
Surface.PreviousLabel = !DisplayedPreviousAttemptNavigationLabel.IsEmpty()
? DisplayedPreviousAttemptNavigationLabel
: TEXT("Prev Attempt");
Surface.NextLabel = !DisplayedNextAttemptNavigationLabel.IsEmpty()
? DisplayedNextAttemptNavigationLabel
: TEXT("Next Attempt");
Surface.SelectedAttemptIndex = SelectedAttemptIndex;
Surface.TotalAttemptCount = TotalAttemptCount;
Surface.bHasReviewedAttempt = bHasReviewedAttempt;
Surface.bCanSelectPreviousAttempt = bNavigationEnabled;
Surface.bCanSelectNextAttempt = bNavigationEnabled;
Surface.AttemptOrdinal = bHasReviewedAttempt
? FString::Printf(TEXT("%d/%d"), SelectedAttemptIndex + 1, TotalAttemptCount)
: TEXT("none");
Surface.StatusLine = FString::Printf(
TEXT("%s: %s | %s: %s | selected attempt: %s"),
*Surface.PreviousLabel,
Surface.bCanSelectPreviousAttempt ? TEXT("enabled") : TEXT("disabled"),
*Surface.NextLabel,
Surface.bCanSelectNextAttempt ? TEXT("enabled") : TEXT("disabled"),
*Surface.AttemptOrdinal);
return Surface;
}
FHyperTwistTrainingCoachDashboardRunControlEnablementInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedRunControlEnablementInspectSurface() const
{
FHyperTwistTrainingCoachDashboardRunControlEnablementInspectSurface Surface;
Surface.Headline = TEXT("Run-control enablement");
const FHyperTwistTrainingCoachDashboardTransitionStatusInspectSurface TransitionSurface =
GetDisplayedTransitionStatusInspectSurface();
const FHyperTwistTrainingCoachDashboardRunActionLabelsInspectSurface RunActionSurface =
GetDisplayedRunActionLabelsInspectSurface();
FString PrimaryLabel = !DisplayedPrimaryActionLabel.IsEmpty()
? DisplayedPrimaryActionLabel
: CachedCoachPanelState.PrimaryActionLabel;
if (PrimaryLabel.IsEmpty())
{
PrimaryLabel = TEXT("Refresh");
}
Surface.PrimaryLabel = PrimaryLabel;
Surface.ContinueLabel = RunActionSurface.ContinueRunLabel;
Surface.CompleteLabel = !DisplayedCompleteRunControlLabel.IsEmpty()
? DisplayedCompleteRunControlLabel
: TEXT("Complete Run");
Surface.ClearLabel = !DisplayedClearRunControlLabel.IsEmpty()
? DisplayedClearRunControlLabel
: TEXT("Clear Run");
Surface.bHasActiveRun = TransitionSurface.bHasActiveRun;
Surface.bHasRunnableCurrentCase =
TransitionSurface.bHasActiveRun
&& CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid();
Surface.bHasLiveAttemptTimer = TransitionSurface.bHasLiveAttemptTimer;
Surface.bLiveInspectionPhase = TransitionSurface.bLiveInspectionPhase;
Surface.bCanContinueCurrentRun = TransitionSurface.bCanContinueCurrentRun;
Surface.bCanLaunchCoachRun = TransitionSurface.bCanLaunchCoachRun;
Surface.bPrimaryEnabled = TransitionSurface.bCanLaunchCoachRun;
Surface.bContinueEnabled =
(!TransitionSurface.bHasLiveAttemptTimer && TransitionSurface.bCanContinueCurrentRun)
|| (TransitionSurface.bLiveInspectionPhase && Surface.bHasRunnableCurrentCase)
|| TransitionSurface.bCanLaunchCoachRun;
Surface.bCompleteEnabled =
TransitionSurface.bHasActiveRun && !TransitionSurface.bHasLiveAttemptTimer;
Surface.bClearEnabled =
TransitionSurface.bHasActiveRun && !TransitionSurface.bHasLiveAttemptTimer;
Surface.StatusLine = FString::Printf(
TEXT("%s: %s | %s: %s | %s: %s | %s: %s"),
*Surface.PrimaryLabel,
Surface.bPrimaryEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.ContinueLabel,
Surface.bContinueEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.CompleteLabel,
Surface.bCompleteEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.ClearLabel,
Surface.bClearEnabled ? TEXT("enabled") : TEXT("disabled"));
Surface.DetailLine = FString::Printf(
TEXT("Run-control detail: active run %s | runnable case %s | live timer %s | inspection %s | continue-current-run %s | coach-launch %s"),
Surface.bHasActiveRun ? TEXT("yes") : TEXT("no"),
Surface.bHasRunnableCurrentCase ? TEXT("yes") : TEXT("no"),
Surface.bHasLiveAttemptTimer ? TEXT("yes") : TEXT("no"),
Surface.bLiveInspectionPhase ? TEXT("yes") : TEXT("no"),
Surface.bCanContinueCurrentRun ? TEXT("yes") : TEXT("no"),
Surface.bCanLaunchCoachRun ? TEXT("yes") : TEXT("no"));
return Surface;
}
FHyperTwistTrainingCoachDashboardQueueActionEnablementInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedQueueActionEnablementInspectSurface() const
{
FHyperTwistTrainingCoachDashboardQueueActionEnablementInspectSurface Surface;
Surface.Headline = TEXT("Queue-action enablement");
const FHyperTwistTrainingCoachSessionQueueStateEntry* SelectedQueueEntry = FindSelectedQueueEntry();
const FString DeferredQueueEntryId = FindFirstDeferredQueueEntryId();
const bool bQueueRecoveryMemorySource =
HyperTwistCoachDashboardWidgetInternal::IsQueueRecoveryMemorySource(
CachedCoachPanelState.PreferredGuidanceSourceLabel);
const bool bQueueRecoveryFollowUp =
CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp;
const bool bQueueRecoveryRecommended =
CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended;
const FHyperTwistCoachSignal* QueueDeferredFirstSuppressionSignal =
HyperTwistCoachDashboardWidgetInternal::FindCoachSignalById(
CachedCoachSignals,
TEXT("queue_suppression_deferred_first_continuation"));
const bool bHasDeferredFirstQueueSuppressionSignal =
QueueDeferredFirstSuppressionSignal != nullptr;
const bool bCanLaunchQueueRecovery =
CachedCoachPanelState.bCanStartQueuedRun
|| (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance)
|| CachedCoachPanelState.bCanStartCoachRecommendedRun;
const bool bUpstreamPrefersLaunchRecovery =
CachedCoachPanelState.PreferredQueueRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery;
const FHyperTwistCoachDashboardQueueRecoveryWeighting QueueRecoveryWeighting =
BuildQueueRecoveryWeighting();
const bool bPreferLaunchRecoveryAction =
!DeferredQueueEntryId.IsEmpty()
&& bCanLaunchQueueRecovery
&& !(bHasDeferredFirstQueueSuppressionSignal && bQueueRecoveryFollowUp)
&& (QueueRecoveryWeighting.PreferredActionKind
== FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery
|| (!QueueRecoveryWeighting.IsStructurallyValid() && bUpstreamPrefersLaunchRecovery));
const bool bHasQueueRecoveryFriction =
CachedCoachSchedulePolicySummary.bHasBlockedFollowUpDeferrals
|| CachedCoachSchedulePolicySummary.bHasArchiveAwareDeferrals
|| CachedCoachSchedulePolicySummary.bNeedsManualRescheduleReview
|| CachedCoachQueueExecutionSummary.bHasOutstandingDeferredWork
|| CachedCoachQueueExecutionSummary.bHasCompletionGap;
const bool bClosedLoopIdleDashboard =
IsCoachLoopActionabilityClosed()
&& (
bQueueRecoveryMemorySource
|| !CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty()
|| DisplayedCoachHandoffRecommendationKind != EHyperTwistTrainingCoachHandoffKind::None);
const bool bCanApplyQueueRecovery =
!bClosedLoopIdleDashboard
&& !bHasLiveAttemptTimer
&& (bQueueRecoveryMemorySource || bHasQueueRecoveryFriction)
&& (!DeferredQueueEntryId.IsEmpty()
|| CachedCoachPanelState.bCanStartQueuedRun
|| (bQueueRecoveryFollowUp
&& CachedCoachPanelState.bCanStartCoachFollowUpRun
&& CachedCoachPanelState.bHasFollowUpGuidance)
|| (bQueueRecoveryRecommended
&& CachedCoachPanelState.bCanStartCoachRecommendedRun)
|| (!bQueueRecoveryMemorySource
&& (CachedCoachPanelState.bCanStartCoachFollowUpRun
|| CachedCoachPanelState.bCanStartCoachRecommendedRun)));
const FString RecoveryLabel = !DisplayedQueueRecoveryActionLabel.IsEmpty()
? DisplayedQueueRecoveryActionLabel
: (bClosedLoopIdleDashboard
? TEXT("No Recovery Action")
: (!DeferredQueueEntryId.IsEmpty() && !bPreferLaunchRecoveryAction
? (bQueueRecoveryFollowUp
? TEXT("Promote Follow-Up Recovery")
: (bQueueRecoveryRecommended
? TEXT("Promote Recommended Recovery")
: TEXT("Promote Recovery")))
: (bQueueRecoveryFollowUp
? TEXT("Start Follow-Up Recovery")
: (bQueueRecoveryRecommended
? TEXT("Start Recommended Recovery")
: (CachedCoachPanelState.bCanStartQueuedRun
? TEXT("Launch Queue Recovery")
: TEXT("Apply Recovery"))))));
Surface.DeferLabel = !DisplayedDeferQueueActionLabel.IsEmpty()
? DisplayedDeferQueueActionLabel
: TEXT("Defer 24h");
Surface.PromoteLabel = !DisplayedPromoteQueueActionLabel.IsEmpty()
? DisplayedPromoteQueueActionLabel
: TEXT("Promote Deferred");
Surface.RecoveryLabel = RecoveryLabel;
Surface.SelectedQueueEntryId =
SelectedQueueEntry != nullptr && !SelectedQueueEntry->EntryId.IsEmpty()
? SelectedQueueEntry->EntryId
: TEXT("none");
Surface.DeferredQueueEntryId = DeferredQueueEntryId.IsEmpty()
? TEXT("none")
: DeferredQueueEntryId;
Surface.bHasLiveAttemptTimer = bHasLiveAttemptTimer;
Surface.bHasSelectedQueueEntry = SelectedQueueEntry != nullptr;
Surface.bHasDeferredQueueEntry = !DeferredQueueEntryId.IsEmpty();
Surface.bQueueRecoveryMemorySource = bQueueRecoveryMemorySource;
Surface.bHasQueueRecoveryFriction = bHasQueueRecoveryFriction;
Surface.bClosedLoopIdleDashboard = bClosedLoopIdleDashboard;
Surface.bDeferEnabled = !bHasLiveAttemptTimer && SelectedQueueEntry != nullptr;
Surface.bPromoteEnabled = !bHasLiveAttemptTimer && !DeferredQueueEntryId.IsEmpty();
Surface.bRecoveryEnabled = bCanApplyQueueRecovery;
Surface.StatusLine = FString::Printf(
TEXT("%s: %s | %s: %s | %s: %s"),
*Surface.DeferLabel,
Surface.bDeferEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.PromoteLabel,
Surface.bPromoteEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.RecoveryLabel,
Surface.bRecoveryEnabled ? TEXT("enabled") : TEXT("disabled"));
Surface.DetailLine = FString::Printf(
TEXT("Queue-action detail: selected entry %s | deferred entry %s | live timer %s | recovery-memory-source %s | recovery-friction %s | closed-loop idle %s"),
*Surface.SelectedQueueEntryId,
*Surface.DeferredQueueEntryId,
Surface.bHasLiveAttemptTimer ? TEXT("yes") : TEXT("no"),
Surface.bQueueRecoveryMemorySource ? TEXT("yes") : TEXT("no"),
Surface.bHasQueueRecoveryFriction ? TEXT("yes") : TEXT("no"),
Surface.bClosedLoopIdleDashboard ? TEXT("yes") : TEXT("no"));
return Surface;
}
FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedLastAttemptReviewSummaryInspectSurface() const
{
@ -23976,7 +24199,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (PrimaryActionButtonLabel != nullptr)
{
PrimaryActionButtonLabel->SetText(FText::FromString(PrimaryLabel));
DisplayedPrimaryActionLabel = PrimaryLabel;
PrimaryActionButtonLabel->SetText(FText::FromString(DisplayedPrimaryActionLabel));
}
if (PrimaryActionButton != nullptr)
{
@ -23996,7 +24220,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (DeferButtonLabel != nullptr)
{
DeferButtonLabel->SetText(FText::FromString(TEXT("Defer 24h")));
DisplayedDeferQueueActionLabel = TEXT("Defer 24h");
DeferButtonLabel->SetText(FText::FromString(DisplayedDeferQueueActionLabel));
}
if (DeferButton != nullptr)
{
@ -24004,7 +24229,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (PromoteButtonLabel != nullptr)
{
PromoteButtonLabel->SetText(FText::FromString(TEXT("Promote Deferred")));
DisplayedPromoteQueueActionLabel = TEXT("Promote Deferred");
PromoteButtonLabel->SetText(FText::FromString(DisplayedPromoteQueueActionLabel));
}
if (PromoteButton != nullptr)
{
@ -24012,7 +24238,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (QueueRecoveryActionButtonLabel != nullptr)
{
QueueRecoveryActionButtonLabel->SetText(FText::FromString(QueueRecoveryActionLabel));
DisplayedQueueRecoveryActionLabel = QueueRecoveryActionLabel;
QueueRecoveryActionButtonLabel->SetText(FText::FromString(DisplayedQueueRecoveryActionLabel));
}
if (QueueRecoveryActionButton != nullptr)
{
@ -24020,7 +24247,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (CompleteRunButtonLabel != nullptr)
{
CompleteRunButtonLabel->SetText(FText::FromString(TEXT("Complete Run")));
DisplayedCompleteRunControlLabel = TEXT("Complete Run");
CompleteRunButtonLabel->SetText(FText::FromString(DisplayedCompleteRunControlLabel));
}
if (CompleteRunButton != nullptr)
{
@ -24040,7 +24268,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (ClearRunButtonLabel != nullptr)
{
ClearRunButtonLabel->SetText(FText::FromString(TEXT("Clear Run")));
DisplayedClearRunControlLabel = TEXT("Clear Run");
ClearRunButtonLabel->SetText(FText::FromString(DisplayedClearRunControlLabel));
}
if (ClearRunButton != nullptr)
{
@ -24154,7 +24383,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (PreviousAttemptButtonLabel != nullptr)
{
PreviousAttemptButtonLabel->SetText(FText::FromString(TEXT("Prev Attempt")));
DisplayedPreviousAttemptNavigationLabel = TEXT("Prev Attempt");
PreviousAttemptButtonLabel->SetText(FText::FromString(DisplayedPreviousAttemptNavigationLabel));
}
if (PreviousAttemptButton != nullptr)
{
@ -24162,7 +24392,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (NextAttemptButtonLabel != nullptr)
{
NextAttemptButtonLabel->SetText(FText::FromString(TEXT("Next Attempt")));
DisplayedNextAttemptNavigationLabel = TEXT("Next Attempt");
NextAttemptButtonLabel->SetText(FText::FromString(DisplayedNextAttemptNavigationLabel));
}
if (NextAttemptButton != nullptr)
{

View file

@ -839,6 +839,18 @@ public:
FHyperTwistTrainingCoachDashboardAttemptResultActionEnablementInspectSurface
GetDisplayedAttemptResultActionEnablementInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardAttemptReviewNavigationInspectSurface
GetDisplayedAttemptReviewNavigationInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardRunControlEnablementInspectSurface
GetDisplayedRunControlEnablementInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardQueueActionEnablementInspectSurface
GetDisplayedQueueActionEnablementInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface
GetDisplayedLastAttemptReviewSummaryInspectSurface() const;
@ -1915,10 +1927,18 @@ private:
FString DisplayedTimerStatusLine;
FString DisplayedRecommendedDeckPreviewLine;
FString DisplayedFollowUpDeckPreviewLine;
FString DisplayedPrimaryActionLabel;
FString DisplayedDeferQueueActionLabel;
FString DisplayedPromoteQueueActionLabel;
FString DisplayedQueueRecoveryActionLabel;
FString DisplayedCompleteRunControlLabel;
FString DisplayedClearRunControlLabel;
FString DisplayedContinueRunLabel;
FString DisplayedQueuedRunLabel;
FString DisplayedFollowUpRunLabel;
FString DisplayedRecommendedRunLabel;
FString DisplayedPreviousAttemptNavigationLabel;
FString DisplayedNextAttemptNavigationLabel;
EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidancePreviewLane =
EHyperTwistCoachDashboardGuidancePreviewLane::None;
int32 SelectedGuidancePreviewCaseIndex = INDEX_NONE;

View file

@ -11006,6 +11006,183 @@ struct FHyperTwistTrainingCoachDashboardAttemptResultActionEnablementInspectSurf
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardAttemptReviewNavigationInspectSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreviousLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString NextLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString AttemptOrdinal;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SelectedAttemptIndex = INDEX_NONE;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 TotalAttemptCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasReviewedAttempt = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCanSelectPreviousAttempt = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCanSelectNextAttempt = false;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| bHasReviewedAttempt
|| bCanSelectPreviousAttempt
|| bCanSelectNextAttempt;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardRunControlEnablementInspectSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PrimaryLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ContinueLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CompleteLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ClearLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasActiveRun = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasRunnableCurrentCase = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasLiveAttemptTimer = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bLiveInspectionPhase = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCanContinueCurrentRun = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCanLaunchCoachRun = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPrimaryEnabled = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bContinueEnabled = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCompleteEnabled = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bClearEnabled = false;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| !DetailLine.IsEmpty()
|| bPrimaryEnabled
|| bContinueEnabled
|| bCompleteEnabled
|| bClearEnabled;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardQueueActionEnablementInspectSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DeferLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PromoteLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RecoveryLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SelectedQueueEntryId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DeferredQueueEntryId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasLiveAttemptTimer = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasSelectedQueueEntry = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasDeferredQueueEntry = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bQueueRecoveryMemorySource = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasQueueRecoveryFriction = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bClosedLoopIdleDashboard = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bDeferEnabled = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPromoteEnabled = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bRecoveryEnabled = false;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| !DetailLine.IsEmpty()
|| bDeferEnabled
|| bPromoteEnabled
|| bRecoveryEnabled;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface
{

View file

@ -69,8 +69,8 @@ Current correction call from the source-exposed audit and current execution refr
- the latest landed bounded `Phase 4` packet closes the retained-idle comparison/outcome actionability gap at the end of the recognition-assisted coach-to-review continuity lane, and the final retained-surface confirmation pass now leaves that closure lane functionally complete
- the retained idle widget surface now keeps retained comparison and decision history inspectable without continuing to advertise live launch or guidance-outcome actions once the coach-to-review loop has already collapsed to truthful closed-loop idle
- the now-complete deliberate `UHyperTwistTrainingRuntimeLibrary` world-context consumer lane reaches through active generated-mode selector option owned-execution split-phase current-selection merged ordered roster phase-id lookup, normalized-order lookup, and evaluation-order lookup inspect surfaces, so gameplay/Blueprint callers can inspect one retained merged-roster row directly by phase id, normalized order, or evaluation order without scanning the merged roster array or re-running inspect derivation details directly
- the latest landed bounded `Phase 4` packet stays on that same live `UHyperTwistCoachDashboardWidget` guidance-consumer lane and adds attempt-time input hint, live timer / split control label, and attempt result-action enablement inspect surfaces over already-derived live dashboard execution-control presentation state, so gameplay/Blueprint callers can inspect the current attempt-time hint, quick-fill affordance posture, timer/split control labels, and result-action enablement without scraping widget controls or reopening execution-control derivation
- the next clean move is to stay on that same live dashboard guidance-consumer lane and land the attempt-review navigation, primary / continue / clear / complete run-control enablement, and queue defer / promote / recovery action enablement inspect surface group rather than drifting back into backend continuity or reopening the already-complete retained-history seam
- the latest landed bounded `Phase 4` packet stays on that same live `UHyperTwistCoachDashboardWidget` guidance-consumer lane and adds attempt-review navigation, primary / continue / clear / complete run-control enablement, and queue defer / promote / recovery action enablement inspect surfaces over already-derived live dashboard review-navigation and execution-control presentation state, so gameplay/Blueprint callers can inspect retained attempt-review cycling plus the current run / queue-control labels and enablement without scraping widget buttons or reopening execution-control derivation
- the next clean move is to stay on that same live dashboard guidance-consumer lane and land the verification action, selected-queue navigation, and start-selected action enablement inspect surface group rather than drifting back into backend continuity or reopening the already-complete retained-history seam
- the current execution-discipline rule that broad refactor / monolith-splitting work should not interrupt the active bounded roadmap packet unless structure is actually blocking it
## Current execution-reality references

View file

@ -0,0 +1,55 @@
# HyperTwist Phase 4 dashboard live attempt-review navigation, run-control, and queue-action enablement inspect surface packet
- bounded Phase `4` dashboard-consumer slice
- date: `2026-05-09`
## Intent
Stay on the deliberate live `UHyperTwistCoachDashboardWidget` guidance-consumer lane and expose the remaining attempt-review navigation plus primary run-control and queue-action control state directly to gameplay and Blueprint callers.
## Why this packet exists
The dashboard already derived:
- the displayed previous/next attempt-review navigation labels and enablement posture
- the displayed primary / continue / complete / clear run-control labels and enablement posture
- the displayed defer / promote / recovery queue-action labels and enablement posture
But that live state still only surfaced through widget buttons. Callers still had to scrape button labels or enabled state to inspect what the dashboard was currently presenting.
## What changed
- added `FHyperTwistTrainingCoachDashboardAttemptReviewNavigationInspectSurface`
- added `FHyperTwistTrainingCoachDashboardRunControlEnablementInspectSurface`
- added `FHyperTwistTrainingCoachDashboardQueueActionEnablementInspectSurface`
- added `GetDisplayedAttemptReviewNavigationInspectSurface()`
- added `GetDisplayedRunControlEnablementInspectSurface()`
- added `GetDisplayedQueueActionEnablementInspectSurface()`
- retained the exact displayed primary, defer, promote, queue-recovery, complete-run, clear-run, and attempt-navigation labels inside the widget so the new inspect surfaces report the same live strings the dashboard is already presenting
- kept the packet bounded to `UHyperTwistCoachDashboardWidget` plus shared surface types, with no dashboard redesign and no backend continuity changes
## Files
- `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist\Public\HyperTwistTraining\HyperTwistTrainingTypes.h`
- `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist\Public\HyperTwistTraining\HyperTwistCoachDashboardWidget.h`
- `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist\Private\HyperTwistTraining\HyperTwistCoachDashboardWidget.cpp`
- `C:\HyperTwist\docs\HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md`
## Outcome
Gameplay and Blueprint callers can now inspect:
- previous/next retained attempt-review navigation labels and their enablement posture
- the primary, continue, complete-run, and clear-run labels plus their enablement posture
- the defer, promote, and queue-recovery labels plus their enablement posture
without scraping widget buttons or reopening run / queue-control derivation.
## Validation
- full `Development Editor | Win64` MSBuild on `UnrealHyperTwist.sln`
- passed with `0 Warning(s), 0 Error(s)`
## Next clean slice
Stay on the same live dashboard guidance-consumer lane and land the verification action, selected-queue navigation, and start-selected action enablement inspect surface group.