diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index bf01416..87229e5 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -9247,6 +9247,116 @@ UHyperTwistCoachDashboardWidget::GetSelectedTemplateLaunchHistoryInspectSurface( return Surface; } +FHyperTwistTrainingCoachDashboardTemplateLaunchHistoryNavigationInspectSurface +UHyperTwistCoachDashboardWidget::GetDisplayedTemplateLaunchHistoryNavigationInspectSurface() const +{ + FHyperTwistTrainingCoachDashboardTemplateLaunchHistoryNavigationInspectSurface Surface; + Surface.Headline = TEXT("Template launch history navigation"); + + const FHyperTwistCoachDashboardTemplateLaunchHistoryEntry* SelectedEntry = + SelectedTemplateLaunchHistoryIndex >= 0 + && SelectedTemplateLaunchHistoryIndex < TemplateLaunchHistoryEntries.Num() + ? &TemplateLaunchHistoryEntries[SelectedTemplateLaunchHistoryIndex] + : nullptr; + const FHyperTwistTrainingTemplateLaunchScopedStats* SelectedStats = + SelectedEntry != nullptr + ? FindCachedTemplateLaunchScopedStats( + SelectedEntry->TemplateId, + SelectedEntry->UserId) + : nullptr; + const bool bNavigationEnabled = TemplateLaunchHistoryEntries.Num() > 1; + + Surface.PreviousLabel = !DisplayedPreviousTemplateLaunchNavigationLabel.IsEmpty() + ? DisplayedPreviousTemplateLaunchNavigationLabel + : TEXT("Prev Template Launch"); + Surface.NextLabel = !DisplayedNextTemplateLaunchNavigationLabel.IsEmpty() + ? DisplayedNextTemplateLaunchNavigationLabel + : TEXT("Next Template Launch"); + Surface.SelectedEntryIndex = SelectedTemplateLaunchHistoryIndex; + Surface.HistoryEntryCount = TemplateLaunchHistoryEntries.Num(); + Surface.bHasSelectedEntry = + SelectedEntry != nullptr && SelectedEntry->IsStructurallyValid(); + Surface.bSelectedEntryIsLatest = + Surface.bHasSelectedEntry + && SelectedTemplateLaunchHistoryIndex == TemplateLaunchHistoryEntries.Num() - 1; + Surface.bPreviousEnabled = bNavigationEnabled; + Surface.bNextEnabled = bNavigationEnabled; + if (!Surface.bHasSelectedEntry) + { + Surface.SelectedEntryOrdinal = TEXT("none"); + Surface.RecordedAtUtc = TEXT("n/a"); + Surface.CompletedAtUtc = TEXT("n/a"); + Surface.TemplateId = TEXT("n/a"); + Surface.SessionStateLabel = TEXT("n/a"); + Surface.AnalyticsOutcomeLine = TEXT("n/a"); + Surface.SelectionDetailLine = TEXT(""); + Surface.SuggestedModeLabel = TEXT("n/a"); + Surface.SuggestedSelectionLabel = TEXT("n/a"); + Surface.StatusLine = + TEXT("Template launch navigation: no completed template-backed runs retained yet."); + Surface.DetailLine = FString::Printf( + TEXT("%s: %s | %s: %s | selected latest: no"), + *Surface.PreviousLabel, + Surface.bPreviousEnabled ? TEXT("enabled") : TEXT("disabled"), + *Surface.NextLabel, + Surface.bNextEnabled ? TEXT("enabled") : TEXT("disabled")); + return Surface; + } + + Surface.bHasScopedStats = + SelectedStats != nullptr && SelectedStats->IsStructurallyValid(); + Surface.bHasSelectionProvenance = + SelectedEntry->SelectionProvenance.IsStructurallyValid(); + Surface.SelectedEntryOrdinal = FString::Printf( + TEXT("%d/%d"), + SelectedTemplateLaunchHistoryIndex + 1, + TemplateLaunchHistoryEntries.Num()); + Surface.RecordedAtUtc = SelectedEntry->RecordedAtUtc.IsEmpty() + ? TEXT("n/a") + : SelectedEntry->RecordedAtUtc; + Surface.CompletedAtUtc = + SelectedEntry->CompletedAtUtc.IsEmpty() ? TEXT("n/a") : SelectedEntry->CompletedAtUtc; + Surface.TemplateId = + SelectedEntry->TemplateId.IsEmpty() ? TEXT("n/a") : SelectedEntry->TemplateId; + Surface.SessionStateLabel = + HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + SelectedEntry->SessionState); + Surface.AnalyticsOutcomeLine = + HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsOutcomeFragment( + SelectedStats); + Surface.SelectionDetailLine = + HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchSelectionDetailFragment( + Surface.bHasSelectionProvenance ? &SelectedEntry->SelectionProvenance : nullptr); + Surface.SuggestedModeLabel = + HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + SelectedEntry->SuggestedMode); + Surface.SuggestedSelectionLabel = + HyperTwistCoachDashboardWidgetInternal::EnumDisplayName( + SelectedEntry->SuggestedSelectionPolicy); + Surface.StatusLine = FString::Printf( + TEXT("Template launch navigation: selected %s | Template: %s | State: %s | Completed: %s"), + *Surface.SelectedEntryOrdinal, + *Surface.TemplateId, + *Surface.SessionStateLabel, + *Surface.CompletedAtUtc); + Surface.DetailLine = FString::Printf( + TEXT("%s: %s | %s: %s | aggregate: %s | mode: %s | selection: %s | selected latest: %s"), + *Surface.PreviousLabel, + Surface.bPreviousEnabled ? TEXT("enabled") : TEXT("disabled"), + *Surface.NextLabel, + Surface.bNextEnabled ? TEXT("enabled") : TEXT("disabled"), + *Surface.AnalyticsOutcomeLine, + *Surface.SuggestedModeLabel, + *Surface.SuggestedSelectionLabel, + Surface.bSelectedEntryIsLatest ? TEXT("yes") : TEXT("no")); + if (!Surface.SelectionDetailLine.IsEmpty()) + { + Surface.DetailLine += TEXT(" | "); + Surface.DetailLine += Surface.SelectionDetailLine; + } + return Surface; +} + FHyperTwistTrainingCoachDashboardGuidanceComparisonInspectSurface UHyperTwistCoachDashboardWidget::GetDisplayedGuidanceComparisonInspectSurface() const { @@ -24623,8 +24733,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (PreviousTemplateLaunchHistoryButtonLabel != nullptr) { + DisplayedPreviousTemplateLaunchNavigationLabel = TEXT("Prev Template Launch"); PreviousTemplateLaunchHistoryButtonLabel->SetText(FText::FromString( - TEXT("Prev Template Launch"))); + DisplayedPreviousTemplateLaunchNavigationLabel)); } if (PreviousTemplateLaunchHistoryButton != nullptr) { @@ -24632,8 +24743,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (NextTemplateLaunchHistoryButtonLabel != nullptr) { + DisplayedNextTemplateLaunchNavigationLabel = TEXT("Next Template Launch"); NextTemplateLaunchHistoryButtonLabel->SetText(FText::FromString( - TEXT("Next Template Launch"))); + DisplayedNextTemplateLaunchNavigationLabel)); } if (NextTemplateLaunchHistoryButton != nullptr) { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index d060361..05adaba 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -791,6 +791,10 @@ public: FHyperTwistTrainingCoachDashboardTemplateLaunchHistoryInspectSurface GetSelectedTemplateLaunchHistoryInspectSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard") + FHyperTwistTrainingCoachDashboardTemplateLaunchHistoryNavigationInspectSurface + GetDisplayedTemplateLaunchHistoryNavigationInspectSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard") FHyperTwistTrainingCoachDashboardGuidanceComparisonInspectSurface GetDisplayedGuidanceComparisonInspectSurface() const; @@ -2032,6 +2036,8 @@ private: FString DisplayedNextHandoffHistoryNavigationLabel; FString DisplayedPreviousGuidanceDecisionNavigationLabel; FString DisplayedNextGuidanceDecisionNavigationLabel; + FString DisplayedPreviousTemplateLaunchNavigationLabel; + FString DisplayedNextTemplateLaunchNavigationLabel; FString DisplayedPreviousAttemptNavigationLabel; FString DisplayedNextAttemptNavigationLabel; EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidancePreviewLane = diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 5d8d964..50a24f9 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -10113,6 +10113,87 @@ struct FHyperTwistTrainingCoachDashboardGuidanceDecisionHistoryInspectSurface } }; +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachDashboardTemplateLaunchHistoryNavigationInspectSurface +{ + 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 SelectedEntryOrdinal; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RecordedAtUtc; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CompletedAtUtc; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString TemplateId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SessionStateLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString AnalyticsOutcomeLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectionDetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SuggestedModeLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SuggestedSelectionLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SelectedEntryIndex = INDEX_NONE; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 HistoryEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSelectedEntry = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSelectedEntryIsLatest = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bPreviousEnabled = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bNextEnabled = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasScopedStats = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSelectionProvenance = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !DetailLine.IsEmpty() + || bHasSelectedEntry + || bPreviousEnabled + || bNextEnabled; + } +}; + USTRUCT(BlueprintType) struct FHyperTwistTrainingCoachDashboardTemplateLaunchHistoryInspectSurface { diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 706d3df..7096fd7 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 on that same retained-history navigation/control lane and adds the guidance decision history navigation control inspect surface over already-retained authored decision outcomes, so gameplay/Blueprint callers can inspect the live prev/next decision labels, enablement posture, selected retained decision ordinal, and lane/scope/action/latest-selection context without scraping button text or reopening retained-history derivation - - the next clean move is to continue that same retained-history navigation/control lane and expose the template launch history navigation control surface rather than drifting back into backend continuity or reopening the already-inspectable history detail seams + - the latest landed bounded `Phase 4` packet stays on that same retained-history navigation/control lane and adds the template launch history navigation control inspect surface over already-retained template-backed runs, so gameplay/Blueprint callers can inspect the live prev/next template-launch labels, enablement posture, selected retained launch ordinal, and template/state/analytics/latest-selection context without scraping button text or reopening retained-history derivation + - the retained-history navigation/control lane now appears complete enough to reassess whether to pivot deliberately into the next broader `Phase 4` roadmap lane rather than widening this seam by drift - 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_TEMPLATE_LAUNCH_HISTORY_NAVIGATION_CONTROL_INSPECT_SURFACE_PACKET_2026-05-10.md b/docs/arch/HYPERTWIST_PHASE4_DASHBOARD_TEMPLATE_LAUNCH_HISTORY_NAVIGATION_CONTROL_INSPECT_SURFACE_PACKET_2026-05-10.md new file mode 100644 index 0000000..e110996 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_DASHBOARD_TEMPLATE_LAUNCH_HISTORY_NAVIGATION_CONTROL_INSPECT_SURFACE_PACKET_2026-05-10.md @@ -0,0 +1,52 @@ +# HyperTwist Phase 4 dashboard template launch history navigation control inspect surface packet + +- bounded Phase `4` dashboard-consumer slice +- date: `2026-05-10` + +## Intent + +Stay on the retained-history navigation/control lane inside `UHyperTwistCoachDashboardWidget` and expose the template launch history navigation controls directly to gameplay and Blueprint callers. + +## Why this packet exists + +The dashboard already derived and displayed: + +- the previous template-launch-history button label +- the next template-launch-history button label +- the enablement posture for both controls + +But that control state still only surfaced through button text and button enabled state. Callers still had to inspect widget controls directly to understand whether retained template-launch navigation is available and which retained template-backed run is currently selected. + +## What changed + +- added `FHyperTwistTrainingCoachDashboardTemplateLaunchHistoryNavigationInspectSurface` +- added `GetDisplayedTemplateLaunchHistoryNavigationInspectSurface()` +- retained the exact displayed previous/next template-launch-history labels inside the widget so the new inspect surface reports 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: + +- the live previous/next template-launch-history labels +- whether template-launch-history navigation is currently enabled +- the selected retained launch ordinal, recorded/completed time, template id, and session state +- the retained analytics outcome line, selection-detail posture, suggested mode/selection posture, and latest-selection posture + +without scraping button text or reopening retained-history derivation. + +## Validation + +- full `Development Editor | Win64` MSBuild on `UnrealHyperTwist.sln` + - passed with `0 Warning(s), 0 Error(s)` + +## Next clean slice + +Reassess whether the retained-history navigation/control lane is now complete enough to pivot deliberately into the next broader `Phase 4` roadmap lane.