diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 1c4a77d..e4a211c 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -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) { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index 864bda8..6aabbb2 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -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; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index c02db10..2c03bbc 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -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 { diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index eb9d4b6..1737174 100644 --- a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md +++ b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md @@ -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 diff --git a/docs/arch/HYPERTWIST_PHASE4_DASHBOARD_LIVE_GUIDANCE_PREVIEW_CASE_NAVIGATION_CONTROL_INSPECT_SURFACE_PACKET_2026-05-10.md b/docs/arch/HYPERTWIST_PHASE4_DASHBOARD_LIVE_GUIDANCE_PREVIEW_CASE_NAVIGATION_CONTROL_INSPECT_SURFACE_PACKET_2026-05-10.md new file mode 100644 index 0000000..493eceb --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_DASHBOARD_LIVE_GUIDANCE_PREVIEW_CASE_NAVIGATION_CONTROL_INSPECT_SURFACE_PACKET_2026-05-10.md @@ -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.