Expose dashboard template launch affordance
This commit is contained in:
parent
84b426443a
commit
5be3abea59
3 changed files with 401 additions and 0 deletions
|
|
@ -264,6 +264,55 @@ namespace HyperTwistCoachDashboardWidgetInternal
|
|||
}
|
||||
}
|
||||
|
||||
int32 ScoreTemplateForDashboardLaunch(const FHyperTwistTrainingSessionTemplate& Template)
|
||||
{
|
||||
if (!Template.IsStructurallyValid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 Score = 0;
|
||||
switch (Template.TemplateKind)
|
||||
{
|
||||
case EHyperTwistTrainingSessionTemplateKind::ReviewPlan:
|
||||
Score = 500;
|
||||
break;
|
||||
case EHyperTwistTrainingSessionTemplateKind::Coach:
|
||||
Score = Template.bPreferReviewRun || Template.bCoachReview ? 450 : 0;
|
||||
break;
|
||||
case EHyperTwistTrainingSessionTemplateKind::Recovery:
|
||||
Score = 400;
|
||||
break;
|
||||
default:
|
||||
Score = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Score <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Template.bPreferReviewRun)
|
||||
{
|
||||
Score += 30;
|
||||
}
|
||||
if (!Template.SourceReviewPlanId.IsEmpty())
|
||||
{
|
||||
Score += 20;
|
||||
}
|
||||
if (Template.bUserCreated)
|
||||
{
|
||||
Score += 10;
|
||||
}
|
||||
if (Template.bUserCustomized)
|
||||
{
|
||||
Score += 5;
|
||||
}
|
||||
|
||||
return Score;
|
||||
}
|
||||
|
||||
EMethodDrillRecoveryMemoryBias ResolveMethodDrillRecoveryMemoryBias(
|
||||
const int32 HistoryCount,
|
||||
const int32 StrongCount,
|
||||
|
|
@ -3447,6 +3496,43 @@ FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::PreviewFollowUpCoachDec
|
|||
return Deck;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartPreferredActiveSessionTemplateRun()
|
||||
{
|
||||
if (bHasLiveAttemptTimer || CachedRunState.Session.IsStructurallyValid())
|
||||
{
|
||||
UpdateDashboardPresentation();
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingSessionTemplate* PreferredTemplate = FindPreferredDashboardTemplateLaunch();
|
||||
if (PreferredTemplate == nullptr)
|
||||
{
|
||||
UpdateDashboardPresentation();
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
const FString ResolvedUserId = PreferredTemplate->UserId.IsEmpty()
|
||||
? DefaultUserId
|
||||
: PreferredTemplate->UserId;
|
||||
const FString LaunchSessionId = FString::Printf(
|
||||
TEXT("%s_template_%s_%s"),
|
||||
*DefaultSessionId,
|
||||
*PreferredTemplate->TemplateId,
|
||||
*FGuid::NewGuid().ToString(EGuidFormats::Digits)
|
||||
);
|
||||
LastStepResult = FHyperTwistTrainingRunStepResult();
|
||||
const FHyperTwistTrainingRunState RunState = StartTrainingRunFromTemplate(
|
||||
PreferredTemplate->TemplateId,
|
||||
ResolvedUserId,
|
||||
LaunchSessionId
|
||||
);
|
||||
SyncRetainedRunRecap();
|
||||
SyncSelectedQueueEntry();
|
||||
SyncSelectedAttempt();
|
||||
UpdateDashboardPresentation();
|
||||
return RunState;
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::SelectPreviousGuidancePreviewCase()
|
||||
{
|
||||
const FHyperTwistTrainingDeck* Deck = GetActiveGuidancePreviewDeck();
|
||||
|
|
@ -4439,6 +4525,11 @@ void UHyperTwistCoachDashboardWidget::HandleStartRecommendedRunClicked()
|
|||
UpdateDashboardPresentation();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleStartTemplateLaunchClicked()
|
||||
{
|
||||
StartPreferredActiveSessionTemplateRun();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandlePreviewRecommendedClicked()
|
||||
{
|
||||
PreviewRecommendedCoachDeck();
|
||||
|
|
@ -4909,6 +5000,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
TEXT("CoachHandoffRecommendationDetail"),
|
||||
8
|
||||
);
|
||||
TemplateLaunchHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachTemplateLaunchHeader"),
|
||||
2
|
||||
);
|
||||
TemplateLaunchStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachTemplateLaunchStatus"),
|
||||
2
|
||||
);
|
||||
TemplateLaunchDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachTemplateLaunchDetail"),
|
||||
8
|
||||
);
|
||||
HandoffHistoryHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
|
|
@ -5175,6 +5284,17 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
StartRecommendedRunLabelRaw
|
||||
);
|
||||
StartRecommendedRunButtonLabel = StartRecommendedRunLabelRaw;
|
||||
|
||||
UTextBlock* StartTemplateLaunchLabelRaw = nullptr;
|
||||
StartTemplateLaunchButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
|
||||
WidgetTree,
|
||||
CoachHandoffActionRow,
|
||||
TEXT("StartTemplateLaunchButton"),
|
||||
TEXT("StartTemplateLaunchButtonLabel"),
|
||||
TEXT("Launch Template"),
|
||||
StartTemplateLaunchLabelRaw
|
||||
);
|
||||
StartTemplateLaunchButtonLabel = StartTemplateLaunchLabelRaw;
|
||||
}
|
||||
UHorizontalBox* HandoffHistoryNavRow = WidgetTree->ConstructWidget<UHorizontalBox>(
|
||||
UHorizontalBox::StaticClass(),
|
||||
|
|
@ -6173,6 +6293,12 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
{
|
||||
StartRecommendedRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleStartRecommendedRunClicked);
|
||||
}
|
||||
if (StartTemplateLaunchButton != nullptr)
|
||||
{
|
||||
StartTemplateLaunchButton->OnClicked.AddDynamic(
|
||||
this,
|
||||
&UHyperTwistCoachDashboardWidget::HandleStartTemplateLaunchClicked);
|
||||
}
|
||||
if (PreviewRecommendedButton != nullptr)
|
||||
{
|
||||
PreviewRecommendedButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePreviewRecommendedClicked);
|
||||
|
|
@ -6620,6 +6746,24 @@ const FHyperTwistCoachBrief* UHyperTwistCoachDashboardWidget::GetActiveGuidanceP
|
|||
}
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingSessionTemplate* UHyperTwistCoachDashboardWidget::FindPreferredDashboardTemplateLaunch() const
|
||||
{
|
||||
const FHyperTwistTrainingSessionTemplate* PreferredTemplate = nullptr;
|
||||
int32 PreferredScore = TNumericLimits<int32>::Lowest();
|
||||
for (const FHyperTwistTrainingSessionTemplate& SessionTemplate : CachedActiveTrainingSessionTemplates)
|
||||
{
|
||||
const int32 TemplateScore =
|
||||
HyperTwistCoachDashboardWidgetInternal::ScoreTemplateForDashboardLaunch(SessionTemplate);
|
||||
if (TemplateScore > PreferredScore)
|
||||
{
|
||||
PreferredTemplate = &SessionTemplate;
|
||||
PreferredScore = TemplateScore;
|
||||
}
|
||||
}
|
||||
|
||||
return PreferredScore > 0 ? PreferredTemplate : nullptr;
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::CaptureActiveGuidanceLaunchContext(
|
||||
const FString& SessionId,
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
|
|
@ -10752,6 +10896,105 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
const bool bDisplayedGuidanceComparisonSingleCase = bHasActiveGuidanceComparison
|
||||
? bActiveGuidanceLaunchSingleCase
|
||||
: (bHasDisplayedRetainedGuidanceComparison ? bRetainedGuidanceComparisonSingleCase : false);
|
||||
const FHyperTwistTrainingSessionTemplate* PreferredTemplateLaunch =
|
||||
FindPreferredDashboardTemplateLaunch();
|
||||
int32 EligibleTemplateLaunchCount = 0;
|
||||
for (const FHyperTwistTrainingSessionTemplate& SessionTemplate : CachedActiveTrainingSessionTemplates)
|
||||
{
|
||||
if (HyperTwistCoachDashboardWidgetInternal::ScoreTemplateForDashboardLaunch(SessionTemplate) > 0)
|
||||
{
|
||||
EligibleTemplateLaunchCount += 1;
|
||||
}
|
||||
}
|
||||
const bool bHasPreferredTemplateLaunch = PreferredTemplateLaunch != nullptr;
|
||||
const bool bPreferredTemplateLaunchUsesReviewLane =
|
||||
bHasPreferredTemplateLaunch
|
||||
&& (PreferredTemplateLaunch->bPreferReviewRun
|
||||
|| PreferredTemplateLaunch->TemplateKind == EHyperTwistTrainingSessionTemplateKind::ReviewPlan
|
||||
|| PreferredTemplateLaunch->TemplateKind == EHyperTwistTrainingSessionTemplateKind::Coach);
|
||||
const FString PreferredTemplateLaunchTitle = bHasPreferredTemplateLaunch
|
||||
? (PreferredTemplateLaunch->Title.IsEmpty()
|
||||
? PreferredTemplateLaunch->TemplateId
|
||||
: PreferredTemplateLaunch->Title)
|
||||
: TEXT("n/a");
|
||||
const FString PreferredTemplateLaunchFocusPreview = bHasPreferredTemplateLaunch
|
||||
? BuildCaseIdPreview(PreferredTemplateLaunch->FocusCaseIds)
|
||||
: TEXT("none");
|
||||
const bool bCanLaunchPreferredTemplate =
|
||||
!bHasLiveAttemptTimer && !bHasActiveRun && bHasPreferredTemplateLaunch;
|
||||
FString TemplateLaunchStatusLine;
|
||||
if (!bHasPreferredTemplateLaunch)
|
||||
{
|
||||
TemplateLaunchStatusLine = FString::Printf(
|
||||
TEXT("Template launch: no active review/recovery template is ready in the dashboard cache. Eligible %d of %d active templates."),
|
||||
EligibleTemplateLaunchCount,
|
||||
CachedActiveTrainingSessionTemplates.Num()
|
||||
);
|
||||
}
|
||||
else if (bHasLiveAttemptTimer)
|
||||
{
|
||||
TemplateLaunchStatusLine = FString::Printf(
|
||||
TEXT("Template launch: %s is ready, but a live attempt is active."),
|
||||
*PreferredTemplateLaunchTitle
|
||||
);
|
||||
}
|
||||
else if (bHasActiveRun)
|
||||
{
|
||||
TemplateLaunchStatusLine = FString::Printf(
|
||||
TEXT("Template launch: %s is ready, but clear or complete the active run first."),
|
||||
*PreferredTemplateLaunchTitle
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
TemplateLaunchStatusLine = FString::Printf(
|
||||
TEXT("Template launch: %s is ready on %s. Eligible %d of %d active templates."),
|
||||
*PreferredTemplateLaunchTitle,
|
||||
PreferredTemplateLaunch->FocusDeckId.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *PreferredTemplateLaunch->FocusDeckId,
|
||||
EligibleTemplateLaunchCount,
|
||||
CachedActiveTrainingSessionTemplates.Num()
|
||||
);
|
||||
}
|
||||
FString TemplateLaunchDetailLine;
|
||||
if (!bHasPreferredTemplateLaunch)
|
||||
{
|
||||
TemplateLaunchDetailLine =
|
||||
TEXT("Template launch detail: this dashboard only surfaces active review-plan, recovery, or coach-review templates from the current session-template cache.");
|
||||
}
|
||||
else
|
||||
{
|
||||
TemplateLaunchDetailLine = FString::Printf(
|
||||
TEXT("Template: %s | id: %s | kind: %s | mode: %s | selection: %s | recommended %d | focus cases: %s | guidance: %s | method: %s | user: %s | source plan: %s | review-backed: %s | short set: %s"),
|
||||
*PreferredTemplateLaunchTitle,
|
||||
PreferredTemplateLaunch->TemplateId.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *PreferredTemplateLaunch->TemplateId,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
|
||||
PreferredTemplateLaunch->TemplateKind),
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
|
||||
PreferredTemplateLaunch->SuggestedMode),
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
|
||||
PreferredTemplateLaunch->SuggestedSelectionPolicy),
|
||||
PreferredTemplateLaunch->RecommendedCaseCount,
|
||||
*PreferredTemplateLaunchFocusPreview,
|
||||
PreferredTemplateLaunch->GuidanceLabel.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *PreferredTemplateLaunch->GuidanceLabel,
|
||||
PreferredTemplateLaunch->MethodSegmentId.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *PreferredTemplateLaunch->MethodSegmentId,
|
||||
PreferredTemplateLaunch->UserId.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *PreferredTemplateLaunch->UserId,
|
||||
PreferredTemplateLaunch->SourceReviewPlanId.IsEmpty()
|
||||
? TEXT("none")
|
||||
: *PreferredTemplateLaunch->SourceReviewPlanId,
|
||||
bPreferredTemplateLaunchUsesReviewLane ? TEXT("yes") : TEXT("no"),
|
||||
PreferredTemplateLaunch->bPreferShortReviewSet ? TEXT("yes") : TEXT("no")
|
||||
);
|
||||
}
|
||||
const bool bCanUseCoachHandoff = !bHasLiveAttemptTimer && !bHasActiveRun;
|
||||
FString CoachHandoffStatusLine;
|
||||
if (bHasLiveAttemptTimer)
|
||||
|
|
@ -14888,6 +15131,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
{
|
||||
CoachHandoffRecommendationDetailTextBlock->SetText(FText::FromString(CoachHandoffRecommendationDetailLine));
|
||||
}
|
||||
if (TemplateLaunchHeaderTextBlock != nullptr)
|
||||
{
|
||||
TemplateLaunchHeaderTextBlock->SetText(FText::FromString(TEXT("[Template Launch]")));
|
||||
}
|
||||
if (TemplateLaunchStatusTextBlock != nullptr)
|
||||
{
|
||||
TemplateLaunchStatusTextBlock->SetText(FText::FromString(TemplateLaunchStatusLine));
|
||||
}
|
||||
if (TemplateLaunchDetailTextBlock != nullptr)
|
||||
{
|
||||
TemplateLaunchDetailTextBlock->SetText(FText::FromString(TemplateLaunchDetailLine));
|
||||
}
|
||||
if (HandoffHistoryHeaderTextBlock != nullptr)
|
||||
{
|
||||
HandoffHistoryHeaderTextBlock->SetText(FText::FromString(TEXT("[Handoff History]")));
|
||||
|
|
@ -15027,6 +15282,32 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
{
|
||||
StartRecommendedRunButton->SetIsEnabled(bCanUseCoachHandoff && bRecommendedReadyForHandoff);
|
||||
}
|
||||
FString TemplateLaunchButtonLabel = TEXT("No Template");
|
||||
if (bHasPreferredTemplateLaunch)
|
||||
{
|
||||
TemplateLaunchButtonLabel =
|
||||
PreferredTemplateLaunchTitle.Len() <= 28
|
||||
? PreferredTemplateLaunchTitle
|
||||
: (bPreferredTemplateLaunchUsesReviewLane
|
||||
? TEXT("Launch Review Template")
|
||||
: TEXT("Launch Recovery Template"));
|
||||
}
|
||||
if (bHasLiveAttemptTimer && bHasPreferredTemplateLaunch)
|
||||
{
|
||||
TemplateLaunchButtonLabel = TEXT("Template Busy");
|
||||
}
|
||||
else if (bHasActiveRun && bHasPreferredTemplateLaunch)
|
||||
{
|
||||
TemplateLaunchButtonLabel = TEXT("Clear Run For Template");
|
||||
}
|
||||
if (StartTemplateLaunchButtonLabel != nullptr)
|
||||
{
|
||||
StartTemplateLaunchButtonLabel->SetText(FText::FromString(TemplateLaunchButtonLabel));
|
||||
}
|
||||
if (StartTemplateLaunchButton != nullptr)
|
||||
{
|
||||
StartTemplateLaunchButton->SetIsEnabled(bCanLaunchPreferredTemplate);
|
||||
}
|
||||
if (PreviewRecommendedButtonLabel != nullptr)
|
||||
{
|
||||
PreviewRecommendedButtonLabel->SetText(FText::FromString(TEXT("Preview Recommended")));
|
||||
|
|
|
|||
|
|
@ -626,6 +626,9 @@ public:
|
|||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
FHyperTwistTrainingDeck PreviewFollowUpCoachDeck();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
FHyperTwistTrainingRunState StartPreferredActiveSessionTemplateRun();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool SelectPreviousGuidancePreviewCase();
|
||||
|
||||
|
|
@ -827,6 +830,15 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> CoachHandoffRecommendationDetailTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> TemplateLaunchHeaderTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> TemplateLaunchStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> TemplateLaunchDetailTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> HandoffHistoryHeaderTextBlock = nullptr;
|
||||
|
||||
|
|
@ -938,6 +950,12 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> StartRecommendedRunButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> StartTemplateLaunchButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> StartTemplateLaunchButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> PreviewRecommendedButton = nullptr;
|
||||
|
||||
|
|
@ -1346,6 +1364,9 @@ protected:
|
|||
UFUNCTION()
|
||||
void HandleStartRecommendedRunClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleStartTemplateLaunchClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandlePreviewRecommendedClicked();
|
||||
|
||||
|
|
@ -1491,6 +1512,7 @@ private:
|
|||
const FHyperTwistTrainingTimingPolicy* FindActiveTimingPolicy() const;
|
||||
const FHyperTwistTrainingDeck* GetActiveGuidancePreviewDeck() const;
|
||||
const FHyperTwistCoachBrief* GetActiveGuidancePreviewBrief() const;
|
||||
const FHyperTwistTrainingSessionTemplate* FindPreferredDashboardTemplateLaunch() const;
|
||||
FString ResolveDashboardHandoffStartActionSourceLabel(
|
||||
EHyperTwistTrainingCoachHandoffKind HandoffKind) const;
|
||||
FHyperTwistTrainingDeck BuildSelectedGuidancePreviewCaseDeck() const;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
# HyperTwist Phase 4 dashboard template launch affordance packet
|
||||
|
||||
Created on `2026-05-06`
|
||||
|
||||
Status:
|
||||
|
||||
- first-party HyperTwist packet
|
||||
- bounded Phase `4` dashboard template launch slice
|
||||
|
||||
## Purpose
|
||||
|
||||
This packet exposes one explicit template-backed launch affordance in the first-party coach dashboard.
|
||||
|
||||
The open tasks are:
|
||||
|
||||
- surface the highest-priority active review/recovery template in the dashboard itself
|
||||
- make the corrected review-template launch lane reachable from the first-party dashboard UX
|
||||
- keep the affordance narrow by reusing existing panel template caches and runtime launch wrappers
|
||||
|
||||
It is not:
|
||||
|
||||
- a dashboard redesign packet
|
||||
- a template browser or editor packet
|
||||
- a broader coach handoff rewrite
|
||||
- a new template derivation packet
|
||||
|
||||
## Scope
|
||||
|
||||
Bounded lane:
|
||||
|
||||
- rank active session templates for dashboard launch priority
|
||||
- prefer active review-plan, recovery, and coach-review templates
|
||||
- add a dedicated dashboard template-launch status/detail section
|
||||
- add one dashboard button that launches the preferred active template through `StartTrainingRunFromTemplate(...)`
|
||||
- keep existing coach handoff, preview, and method-drill flows unchanged
|
||||
|
||||
Out of scope:
|
||||
|
||||
- adding multi-template pagination or selection UI
|
||||
- changing template merge or derivation order in the repository/subsystem
|
||||
- changing review-plan semantics
|
||||
- changing coach queue or guidance decision logic
|
||||
|
||||
## Why this was the right next packet
|
||||
|
||||
The previous packet gave first-party panel and actor consumers parity with the runtime template APIs.
|
||||
|
||||
That still left one practical gap:
|
||||
|
||||
- the dashboard remained the main first-party operator surface
|
||||
- but it still offered no direct session-template launch affordance
|
||||
- so review-preferring templates were technically reachable yet still visually absent from the built-in dashboard workflow
|
||||
|
||||
The next honest move was therefore:
|
||||
|
||||
- keep template logic where it already lived
|
||||
- rank the active template cache for one narrow review/recovery launch choice
|
||||
- and surface that choice directly in the dashboard beside the existing handoff actions
|
||||
|
||||
## What landed
|
||||
|
||||
Primary code changes:
|
||||
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h`
|
||||
- added a Blueprint-callable dashboard action for starting the preferred active template
|
||||
- added dashboard text/button members for the new template-launch surface
|
||||
- added a private helper for selecting the preferred dashboard template launch target
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp`
|
||||
- added narrow dashboard launch scoring for active session templates
|
||||
- added the new dashboard template-launch action and button handler
|
||||
- added a `[Template Launch]` status/detail section
|
||||
- added a direct template-launch button to the coach handoff action row
|
||||
- updates now describe and enable the highest-priority active review/recovery template when available
|
||||
|
||||
## Product effect
|
||||
|
||||
The first-party dashboard now makes the corrected template lane visible:
|
||||
|
||||
- operators can see whether an active review/recovery template is available
|
||||
- the dashboard shows which active template won launch priority and why it is or is not launchable
|
||||
- a direct dashboard button now launches that preferred template through the existing template-backed run path
|
||||
- review-preferring templates now have a visible first-party dashboard entry point instead of living only behind lower-level calls
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- dashboard surfaces a preferred active review/recovery template when one exists
|
||||
- dashboard button launches that preferred template through `StartTrainingRunFromTemplate(...)`
|
||||
- dashboard disables the template action while a live attempt or active run blocks launch
|
||||
- full product build succeeds
|
||||
|
||||
## Validation checklist
|
||||
|
||||
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
|
||||
2. confirm the dashboard shows a `[Template Launch]` section
|
||||
3. confirm the button label and enabled state track the preferred active template
|
||||
4. confirm clicking the button launches the preferred active template-backed run
|
||||
|
||||
That is the packet.
|
||||
Loading…
Add table
Reference in a new issue