Expose dashboard guidance preview case navigation surface

This commit is contained in:
axiomlogicnexus 2026-05-10 16:34:48 +02:00
parent 9fafb7d5c7
commit 47cf3bb7fd
5 changed files with 233 additions and 4 deletions

View file

@ -10551,6 +10551,105 @@ UHyperTwistCoachDashboardWidget::GetDisplayedGuidancePreviewCaseDetailInspectSur
return Surface;
}
FHyperTwistTrainingCoachDashboardGuidancePreviewCaseNavigationInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedGuidancePreviewCaseNavigationInspectSurface() const
{
FHyperTwistTrainingCoachDashboardGuidancePreviewCaseNavigationInspectSurface Surface;
Surface.Headline = TEXT("Preview case navigation");
const auto BuildPreviewLaneLabel = [](const EHyperTwistCoachDashboardGuidancePreviewLane Lane) -> FString
{
return Lane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended
? TEXT("recommended")
: (Lane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
? TEXT("follow-up")
: TEXT("none"));
};
const FHyperTwistTrainingDeck* ActivePreviewDeck = GetActiveGuidancePreviewDeck();
const FHyperTwistTrainingCase* SelectedGuidancePreviewCase =
ActivePreviewDeck != nullptr
&& SelectedGuidancePreviewCaseIndex >= 0
&& SelectedGuidancePreviewCaseIndex < ActivePreviewDeck->Cases.Num()
? &ActivePreviewDeck->Cases[SelectedGuidancePreviewCaseIndex]
: nullptr;
const bool bNavigationEnabled = ActivePreviewDeck != nullptr && ActivePreviewDeck->Cases.Num() > 1;
Surface.PreviousLabel = !DisplayedPreviousGuidanceCaseNavigationLabel.IsEmpty()
? DisplayedPreviousGuidanceCaseNavigationLabel
: TEXT("Prev Guidance Case");
Surface.NextLabel = !DisplayedNextGuidanceCaseNavigationLabel.IsEmpty()
? DisplayedNextGuidanceCaseNavigationLabel
: TEXT("Next Guidance Case");
Surface.ActivePreviewLaneLabel = BuildPreviewLaneLabel(ActiveGuidancePreviewLane);
Surface.DeckLabel =
ActivePreviewDeck != nullptr
? (ActivePreviewDeck->Title.IsEmpty() ? ActivePreviewDeck->DeckId : ActivePreviewDeck->Title)
: TEXT("n/a");
Surface.SelectedCaseId =
SelectedGuidancePreviewCase != nullptr && !SelectedGuidancePreviewCase->CaseId.IsEmpty()
? SelectedGuidancePreviewCase->CaseId
: TEXT("n/a");
Surface.SelectedCasePromptLabel =
SelectedGuidancePreviewCase != nullptr
&& !SelectedGuidancePreviewCase->PromptLabel.IsEmpty()
? SelectedGuidancePreviewCase->PromptLabel
: TEXT("n/a");
Surface.SelectedCaseOrdinal =
SelectedGuidancePreviewCase != nullptr && ActivePreviewDeck != nullptr
? FString::Printf(TEXT("%d/%d"), SelectedGuidancePreviewCaseIndex + 1, ActivePreviewDeck->Cases.Num())
: TEXT("none");
Surface.SelectedCaseIndex = SelectedGuidancePreviewCaseIndex;
Surface.TotalCaseCount = ActivePreviewDeck != nullptr ? ActivePreviewDeck->Cases.Num() : 0;
Surface.bHasActivePreviewDeck = ActivePreviewDeck != nullptr;
Surface.bHasSelectedCase = SelectedGuidancePreviewCase != nullptr;
Surface.bPreviousEnabled = bNavigationEnabled;
Surface.bNextEnabled = bNavigationEnabled;
const bool bQueueRecoveryMemorySource =
HyperTwistCoachDashboardWidgetInternal::IsQueueRecoveryMemorySource(
CachedCoachPanelState.PreferredGuidanceSourceLabel);
Surface.bClosedLoopIdleDashboard =
IsCoachLoopActionabilityClosed()
&& (
bQueueRecoveryMemorySource
|| !CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty()
|| DisplayedCoachHandoffRecommendationKind != EHyperTwistTrainingCoachHandoffKind::None);
Surface.StatusLine =
SelectedGuidancePreviewCase != nullptr
? FString::Printf(
TEXT("Preview navigation: lane %s | selected %s | case %s | deck %s"),
*Surface.ActivePreviewLaneLabel,
*Surface.SelectedCaseOrdinal,
*Surface.SelectedCaseId,
*Surface.DeckLabel)
: FString::Printf(
TEXT("Preview navigation: lane %s | no active preview case is currently selected"),
*Surface.ActivePreviewLaneLabel);
Surface.DetailLine =
ActivePreviewDeck != nullptr
? FString::Printf(
TEXT("%s: %s | %s: %s | selected prompt %s"),
*Surface.PreviousLabel,
Surface.bPreviousEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.NextLabel,
Surface.bNextEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.SelectedCasePromptLabel)
: FString::Printf(
TEXT("%s: disabled | %s: disabled | build a preview deck before navigating guidance cases"),
*Surface.PreviousLabel,
*Surface.NextLabel);
if (Surface.bClosedLoopIdleDashboard)
{
Surface.StatusLine =
TEXT("Preview navigation: no active guidance preview remains in the closed-loop idle state.");
Surface.DetailLine =
TEXT("Preview navigation detail: retained coach memory remains inspectable, but live preview-case navigation is inactive.");
}
return Surface;
}
FHyperTwistTrainingCoachDashboardMethodDrillDiagnosticsInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedMethodDrillDiagnosticsInspectSurface() const
{
@ -26354,7 +26453,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (PreviousGuidanceCaseButtonLabel != nullptr)
{
PreviousGuidanceCaseButtonLabel->SetText(FText::FromString(TEXT("Prev Guidance Case")));
DisplayedPreviousGuidanceCaseNavigationLabel = TEXT("Prev Guidance Case");
PreviousGuidanceCaseButtonLabel->SetText(FText::FromString(
DisplayedPreviousGuidanceCaseNavigationLabel));
}
if (PreviousGuidanceCaseButton != nullptr)
{
@ -26364,7 +26465,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (NextGuidanceCaseButtonLabel != nullptr)
{
NextGuidanceCaseButtonLabel->SetText(FText::FromString(TEXT("Next Guidance Case")));
DisplayedNextGuidanceCaseNavigationLabel = TEXT("Next Guidance Case");
NextGuidanceCaseButtonLabel->SetText(FText::FromString(
DisplayedNextGuidanceCaseNavigationLabel));
}
if (NextGuidanceCaseButton != nullptr)
{

View file

@ -831,6 +831,10 @@ public:
FHyperTwistTrainingCoachDashboardGuidancePreviewCaseDetailInspectSurface
GetDisplayedGuidancePreviewCaseDetailInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardGuidancePreviewCaseNavigationInspectSurface
GetDisplayedGuidancePreviewCaseNavigationInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardMethodDrillDiagnosticsInspectSurface
GetDisplayedMethodDrillDiagnosticsInspectSurface() const;
@ -2121,6 +2125,8 @@ private:
FString DisplayedNextTemplateLaunchNavigationLabel;
FString DisplayedPreviousAttemptNavigationLabel;
FString DisplayedNextAttemptNavigationLabel;
FString DisplayedPreviousGuidanceCaseNavigationLabel;
FString DisplayedNextGuidanceCaseNavigationLabel;
EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidancePreviewLane =
EHyperTwistCoachDashboardGuidancePreviewLane::None;
int32 SelectedGuidancePreviewCaseIndex = INDEX_NONE;

View file

@ -11303,6 +11303,73 @@ struct FHyperTwistTrainingCoachDashboardGuidancePreviewCaseDetailInspectSurface
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardGuidancePreviewCaseNavigationInspectSurface
{
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 PreviousLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString NextLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ActivePreviewLaneLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DeckLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SelectedCaseId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SelectedCasePromptLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SelectedCaseOrdinal;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SelectedCaseIndex = INDEX_NONE;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 TotalCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasActivePreviewDeck = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasSelectedCase = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bClosedLoopIdleDashboard = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPreviousEnabled = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNextEnabled = false;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| !DetailLine.IsEmpty()
|| bHasActivePreviewDeck
|| bHasSelectedCase
|| bPreviousEnabled
|| bNextEnabled;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardGuidanceRationaleInspectSurface
{

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 adjacent in that same live dashboard consumer lane and adds the preferred template-launch plus recommended/follow-up preview action control surface trio over the already-derived launch/preview button labels and enablement posture, so gameplay/Blueprint callers can inspect the exact surfaced template/preview action labels and their gating state without scraping widget button text
- the next best clean move is to stay adjacent in that same live dashboard consumer lane and expose the guidance preview case navigation control surface pair over the already-derived `Prev Guidance Case` / `Next Guidance Case` labels and enablement posture
- the latest landed bounded `Phase 4` packet stays adjacent in that same live dashboard consumer lane and adds the guidance preview case navigation control surface over the already-derived `Prev Guidance Case` / `Next Guidance Case` labels and enablement posture, so gameplay/Blueprint callers can inspect the exact surfaced preview-case navigation labels and their gating state without scraping widget button text
- the next best clean move is to stay adjacent in that same live dashboard consumer lane and expose the preview-deck plus start-selected-preview-case action control pair over the already-derived launch button labels and enablement posture
- 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
## Donor portfolio phase taxonomy

View file

@ -0,0 +1,53 @@
# HyperTwist Phase 4 dashboard live guidance preview case navigation control inspect surface packet
- bounded Phase `4` dashboard-consumer slice
- date: `2026-05-10`
## Intent
Stay adjacent in the live dashboard consumer lane and expose the guidance preview case navigation controls directly to gameplay and Blueprint callers.
## Why this packet exists
`UHyperTwistCoachDashboardWidget` already displayed:
- the `Prev Guidance Case` action label
- the `Next Guidance Case` action label
- the enablement posture behind those two preview-case navigation controls
But that control band still only surfaced through button labels and `SetIsEnabled` state. Callers still had to scrape widget controls to understand whether the dashboard currently has a navigable preview deck, which preview case is selected, and whether the previous/next navigation pair is actually actionable.
## What changed
- added `FHyperTwistTrainingCoachDashboardGuidancePreviewCaseNavigationInspectSurface`
- added `GetDisplayedGuidancePreviewCaseNavigationInspectSurface()`
- retained the exact displayed `Prev Guidance Case` and `Next Guidance Case` labels inside the widget so the new surface reports the same live strings the dashboard is already presenting
- kept the packet bounded to `UHyperTwistCoachDashboardWidget` plus shared surface types, with no preview-deck derivation refactor 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:
- the exact displayed `Prev Guidance Case` label
- the exact displayed `Next Guidance Case` label
- whether previous navigation is currently enabled
- whether next navigation is currently enabled
- the active preview lane, selected case ordinal, selected case id, and surfaced prompt behind those controls
without scraping widget button text or reopening preview-case navigation gating logic.
## 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 consumer lane and expose the preview-deck plus start-selected-preview-case action control pair over the already-derived launch button labels and enablement posture.