Record dashboard template launch provenance
This commit is contained in:
parent
5be3abea59
commit
b2ca2957fe
3 changed files with 584 additions and 31 deletions
|
|
@ -2806,6 +2806,7 @@ void UHyperTwistCoachDashboardWidget::RefreshCoachDashboardView()
|
|||
SyncSelectedAttempt();
|
||||
SyncSelectedGuidanceDecisionHistoryEntry();
|
||||
SyncSelectedHandoffHistoryEntry();
|
||||
SyncSelectedTemplateLaunchHistoryEntry();
|
||||
SyncSelectedQueueSuppressionHistoryEntry();
|
||||
SyncSelectedClosureRecoveryHistoryEntry();
|
||||
SyncSelectedMethodDrillFollowUpHistoryEntry();
|
||||
|
|
@ -3511,21 +3512,26 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartPreferredActiv
|
|||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
const FString ResolvedUserId = PreferredTemplate->UserId.IsEmpty()
|
||||
const FHyperTwistTrainingSessionTemplate LaunchedTemplate = *PreferredTemplate;
|
||||
const FString ResolvedUserId = LaunchedTemplate.UserId.IsEmpty()
|
||||
? DefaultUserId
|
||||
: PreferredTemplate->UserId;
|
||||
: LaunchedTemplate.UserId;
|
||||
const FString LaunchSessionId = FString::Printf(
|
||||
TEXT("%s_template_%s_%s"),
|
||||
*DefaultSessionId,
|
||||
*PreferredTemplate->TemplateId,
|
||||
*LaunchedTemplate.TemplateId,
|
||||
*FGuid::NewGuid().ToString(EGuidFormats::Digits)
|
||||
);
|
||||
LastStepResult = FHyperTwistTrainingRunStepResult();
|
||||
const FHyperTwistTrainingRunState RunState = StartTrainingRunFromTemplate(
|
||||
PreferredTemplate->TemplateId,
|
||||
LaunchedTemplate.TemplateId,
|
||||
ResolvedUserId,
|
||||
LaunchSessionId
|
||||
);
|
||||
if (RunState.Session.IsStructurallyValid())
|
||||
{
|
||||
CaptureActiveTemplateLaunchContext(RunState.Session.TrainingSessionId, LaunchedTemplate);
|
||||
}
|
||||
SyncRetainedRunRecap();
|
||||
SyncSelectedQueueEntry();
|
||||
SyncSelectedAttempt();
|
||||
|
|
@ -3689,6 +3695,9 @@ void UHyperTwistCoachDashboardWidget::ClearRetainedRunRecap()
|
|||
RetainedRunRecapUserId.Reset();
|
||||
RetainedRunRecapSessionId.Reset();
|
||||
RetainedRunRecapMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
bHasRetainedTemplateLaunch = false;
|
||||
RetainedTemplateLaunchSessionId.Reset();
|
||||
RetainedTemplateLaunchTemplate = FHyperTwistTrainingSessionTemplate();
|
||||
bHasRetainedGuidanceComparison = false;
|
||||
RetainedGuidanceComparisonSessionId.Reset();
|
||||
RetainedGuidanceComparisonLane = EHyperTwistCoachDashboardGuidancePreviewLane::None;
|
||||
|
|
@ -4057,6 +4066,47 @@ bool UHyperTwistCoachDashboardWidget::SelectNextHandoffHistoryEntry()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::SelectPreviousTemplateLaunchHistoryEntry()
|
||||
{
|
||||
if (TemplateLaunchHistoryEntries.Num() <= 0)
|
||||
{
|
||||
SelectedTemplateLaunchHistoryIndex = INDEX_NONE;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SelectedTemplateLaunchHistoryIndex == INDEX_NONE)
|
||||
{
|
||||
SyncSelectedTemplateLaunchHistoryEntry();
|
||||
return SelectedTemplateLaunchHistoryIndex != INDEX_NONE;
|
||||
}
|
||||
|
||||
SelectedTemplateLaunchHistoryIndex =
|
||||
(SelectedTemplateLaunchHistoryIndex - 1 + TemplateLaunchHistoryEntries.Num())
|
||||
% TemplateLaunchHistoryEntries.Num();
|
||||
UpdateDashboardPresentation();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::SelectNextTemplateLaunchHistoryEntry()
|
||||
{
|
||||
if (TemplateLaunchHistoryEntries.Num() <= 0)
|
||||
{
|
||||
SelectedTemplateLaunchHistoryIndex = INDEX_NONE;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SelectedTemplateLaunchHistoryIndex == INDEX_NONE)
|
||||
{
|
||||
SyncSelectedTemplateLaunchHistoryEntry();
|
||||
return SelectedTemplateLaunchHistoryIndex != INDEX_NONE;
|
||||
}
|
||||
|
||||
SelectedTemplateLaunchHistoryIndex =
|
||||
(SelectedTemplateLaunchHistoryIndex + 1) % TemplateLaunchHistoryEntries.Num();
|
||||
UpdateDashboardPresentation();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistCoachDashboardWidget::SelectPreviousQueueSuppressionHistoryEntry()
|
||||
{
|
||||
if (QueueSuppressionHistoryEntries.Num() <= 0)
|
||||
|
|
@ -4613,6 +4663,16 @@ void UHyperTwistCoachDashboardWidget::HandleNextHandoffHistoryClicked()
|
|||
SelectNextHandoffHistoryEntry();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandlePreviousTemplateLaunchHistoryClicked()
|
||||
{
|
||||
SelectPreviousTemplateLaunchHistoryEntry();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandleNextTemplateLaunchHistoryClicked()
|
||||
{
|
||||
SelectNextTemplateLaunchHistoryEntry();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::HandlePreviousQueueSuppressionClicked()
|
||||
{
|
||||
SelectPreviousQueueSuppressionHistoryEntry();
|
||||
|
|
@ -5018,6 +5078,58 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
TEXT("CoachTemplateLaunchDetail"),
|
||||
8
|
||||
);
|
||||
TemplateLaunchHistoryHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachTemplateLaunchHistoryHeader"),
|
||||
2
|
||||
);
|
||||
TemplateLaunchHistoryStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachTemplateLaunchHistoryStatus"),
|
||||
2
|
||||
);
|
||||
TemplateLaunchHistoryDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
TEXT("CoachTemplateLaunchHistoryDetail"),
|
||||
8
|
||||
);
|
||||
UHorizontalBox* TemplateLaunchHistoryNavRow = WidgetTree->ConstructWidget<UHorizontalBox>(
|
||||
UHorizontalBox::StaticClass(),
|
||||
TEXT("CoachDashboardTemplateLaunchHistoryNavRow")
|
||||
);
|
||||
if (TemplateLaunchHistoryNavRow != nullptr)
|
||||
{
|
||||
if (UVerticalBoxSlot* TemplateLaunchHistoryNavRowSlot =
|
||||
RootLayout->AddChildToVerticalBox(TemplateLaunchHistoryNavRow))
|
||||
{
|
||||
TemplateLaunchHistoryNavRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
UTextBlock* PreviousTemplateLaunchHistoryLabelRaw = nullptr;
|
||||
PreviousTemplateLaunchHistoryButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
|
||||
WidgetTree,
|
||||
TemplateLaunchHistoryNavRow,
|
||||
TEXT("PreviousTemplateLaunchHistoryButton"),
|
||||
TEXT("PreviousTemplateLaunchHistoryButtonLabel"),
|
||||
TEXT("Prev Template Launch"),
|
||||
PreviousTemplateLaunchHistoryLabelRaw
|
||||
);
|
||||
PreviousTemplateLaunchHistoryButtonLabel = PreviousTemplateLaunchHistoryLabelRaw;
|
||||
|
||||
UTextBlock* NextTemplateLaunchHistoryLabelRaw = nullptr;
|
||||
NextTemplateLaunchHistoryButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
|
||||
WidgetTree,
|
||||
TemplateLaunchHistoryNavRow,
|
||||
TEXT("NextTemplateLaunchHistoryButton"),
|
||||
TEXT("NextTemplateLaunchHistoryButtonLabel"),
|
||||
TEXT("Next Template Launch"),
|
||||
NextTemplateLaunchHistoryLabelRaw
|
||||
);
|
||||
NextTemplateLaunchHistoryButtonLabel = NextTemplateLaunchHistoryLabelRaw;
|
||||
}
|
||||
HandoffHistoryHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
|
||||
WidgetTree,
|
||||
RootLayout,
|
||||
|
|
@ -6277,6 +6389,20 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
|
|||
{
|
||||
NextHandoffHistoryButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleNextHandoffHistoryClicked);
|
||||
}
|
||||
if (PreviousTemplateLaunchHistoryButton != nullptr)
|
||||
{
|
||||
PreviousTemplateLaunchHistoryButton->OnClicked.AddDynamic(
|
||||
this,
|
||||
&UHyperTwistCoachDashboardWidget::HandlePreviousTemplateLaunchHistoryClicked
|
||||
);
|
||||
}
|
||||
if (NextTemplateLaunchHistoryButton != nullptr)
|
||||
{
|
||||
NextTemplateLaunchHistoryButton->OnClicked.AddDynamic(
|
||||
this,
|
||||
&UHyperTwistCoachDashboardWidget::HandleNextTemplateLaunchHistoryClicked
|
||||
);
|
||||
}
|
||||
if (DismissRecapButton != nullptr)
|
||||
{
|
||||
DismissRecapButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleDismissRecapClicked);
|
||||
|
|
@ -6537,6 +6663,15 @@ void UHyperTwistCoachDashboardWidget::SyncRetainedRunRecap()
|
|||
RetainedRunRecapUserId = CachedRunState.Session.UserId;
|
||||
RetainedRunRecapSessionId = CachedRunState.Session.TrainingSessionId;
|
||||
RetainedRunRecapMode = CachedRunState.Session.Mode;
|
||||
if (!ActiveTemplateLaunchSessionId.IsEmpty()
|
||||
&& ActiveTemplateLaunchSessionId == RetainedRunRecapSessionId
|
||||
&& ActiveTemplateLaunchTemplate.IsStructurallyValid())
|
||||
{
|
||||
bHasRetainedTemplateLaunch = true;
|
||||
RetainedTemplateLaunchSessionId = ActiveTemplateLaunchSessionId;
|
||||
RetainedTemplateLaunchTemplate = ActiveTemplateLaunchTemplate;
|
||||
RecordTemplateLaunchHistoryEntry(RetainedRunRecapSummary);
|
||||
}
|
||||
if (!ActiveGuidanceLaunchSessionId.IsEmpty()
|
||||
&& ActiveGuidanceLaunchSessionId == RetainedRunRecapSessionId
|
||||
&& ActiveGuidanceLaunchLane != EHyperTwistCoachDashboardGuidancePreviewLane::None)
|
||||
|
|
@ -6611,6 +6746,21 @@ void UHyperTwistCoachDashboardWidget::SyncSelectedHandoffHistoryEntry()
|
|||
}
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::SyncSelectedTemplateLaunchHistoryEntry()
|
||||
{
|
||||
if (TemplateLaunchHistoryEntries.Num() <= 0)
|
||||
{
|
||||
SelectedTemplateLaunchHistoryIndex = INDEX_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedTemplateLaunchHistoryIndex < 0
|
||||
|| SelectedTemplateLaunchHistoryIndex >= TemplateLaunchHistoryEntries.Num())
|
||||
{
|
||||
SelectedTemplateLaunchHistoryIndex = TemplateLaunchHistoryEntries.Num() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::SyncSelectedQueueSuppressionHistoryEntry()
|
||||
{
|
||||
if (QueueSuppressionHistoryEntries.Num() <= 0)
|
||||
|
|
@ -6783,6 +6933,15 @@ void UHyperTwistCoachDashboardWidget::CaptureActiveGuidanceLaunchContext(
|
|||
}
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::CaptureActiveTemplateLaunchContext(
|
||||
const FString& SessionId,
|
||||
const FHyperTwistTrainingSessionTemplate& SessionTemplate
|
||||
)
|
||||
{
|
||||
ActiveTemplateLaunchSessionId = SessionId;
|
||||
ActiveTemplateLaunchTemplate = SessionTemplate;
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::CaptureActiveMethodDrillFollowUpLaunchContext(
|
||||
const FString& SessionId
|
||||
)
|
||||
|
|
@ -9699,6 +9858,65 @@ void UHyperTwistCoachDashboardWidget::RecordClosureRecoverySnapshot()
|
|||
SelectedClosureRecoveryIndex = ClosureRecoveryHistoryEntries.Num() - 1;
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::RecordTemplateLaunchHistoryEntry(
|
||||
const FHyperTwistTrainingSessionSummary& CompletedRunRecap
|
||||
)
|
||||
{
|
||||
if (CompletedRunRecap.TrainingSessionId.IsEmpty()
|
||||
|| !ActiveTemplateLaunchTemplate.IsStructurallyValid()
|
||||
|| ActiveTemplateLaunchSessionId != CompletedRunRecap.TrainingSessionId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FHyperTwistCoachDashboardTemplateLaunchHistoryEntry Entry;
|
||||
Entry.RecordedAtUtc = FDateTime::UtcNow().ToIso8601();
|
||||
Entry.SourceSessionId = CompletedRunRecap.TrainingSessionId;
|
||||
Entry.TemplateId = ActiveTemplateLaunchTemplate.TemplateId;
|
||||
Entry.TemplateTitle = ActiveTemplateLaunchTemplate.Title;
|
||||
Entry.UserId = ActiveTemplateLaunchTemplate.UserId;
|
||||
Entry.FocusDeckId = ActiveTemplateLaunchTemplate.FocusDeckId;
|
||||
Entry.SourceReviewPlanId = ActiveTemplateLaunchTemplate.SourceReviewPlanId;
|
||||
Entry.TemplateKind = ActiveTemplateLaunchTemplate.TemplateKind;
|
||||
Entry.SessionState = CompletedRunRecap.SessionState;
|
||||
Entry.SuggestedMode = ActiveTemplateLaunchTemplate.SuggestedMode;
|
||||
Entry.SuggestedSelectionPolicy = ActiveTemplateLaunchTemplate.SuggestedSelectionPolicy;
|
||||
Entry.RecommendedCaseCount = ActiveTemplateLaunchTemplate.RecommendedCaseCount;
|
||||
Entry.FocusCaseCount = ActiveTemplateLaunchTemplate.FocusCaseIds.Num();
|
||||
Entry.CompletedAttemptCount = CompletedRunRecap.CompletedAttemptCount;
|
||||
Entry.TotalMistakes = CompletedRunRecap.TotalMistakes;
|
||||
Entry.SuccessRate = CompletedRunRecap.SuccessRate;
|
||||
Entry.CompletedAtUtc = CompletedRunRecap.LastCompletedAtUtc;
|
||||
Entry.bPreferReviewRun = ActiveTemplateLaunchTemplate.bPreferReviewRun;
|
||||
Entry.bCoachReview = ActiveTemplateLaunchTemplate.bCoachReview;
|
||||
Entry.bPreferShortReviewSet = ActiveTemplateLaunchTemplate.bPreferShortReviewSet;
|
||||
if (!Entry.IsStructurallyValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool bAlreadyRecorded = TemplateLaunchHistoryEntries.ContainsByPredicate(
|
||||
[&Entry](const FHyperTwistCoachDashboardTemplateLaunchHistoryEntry& Candidate)
|
||||
{
|
||||
return Candidate.SourceSessionId == Entry.SourceSessionId;
|
||||
}
|
||||
);
|
||||
if (bAlreadyRecorded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TemplateLaunchHistoryEntries.Add(Entry);
|
||||
constexpr int32 MaxRetainedTemplateLaunchHistoryEntries = 16;
|
||||
if (TemplateLaunchHistoryEntries.Num() > MaxRetainedTemplateLaunchHistoryEntries)
|
||||
{
|
||||
const int32 ExcessCount =
|
||||
TemplateLaunchHistoryEntries.Num() - MaxRetainedTemplateLaunchHistoryEntries;
|
||||
TemplateLaunchHistoryEntries.RemoveAt(0, ExcessCount, EAllowShrinking::No);
|
||||
}
|
||||
SelectedTemplateLaunchHistoryIndex = TemplateLaunchHistoryEntries.Num() - 1;
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::RecordMethodDrillFollowUpHistoryEntry(
|
||||
const FHyperTwistTrainingSessionSummary& CompletedRunRecap
|
||||
)
|
||||
|
|
@ -10860,6 +11078,28 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
const FString RunRecapSourceLabel = bHasCurrentRunRecap
|
||||
? TEXT("current run")
|
||||
: TEXT("last completed run");
|
||||
const bool bHasActiveTemplateLaunchForDisplayedRun =
|
||||
bHasCurrentRunRecap
|
||||
&& DisplayedRunRecap != nullptr
|
||||
&& !ActiveTemplateLaunchSessionId.IsEmpty()
|
||||
&& DisplayedRunRecap->TrainingSessionId == ActiveTemplateLaunchSessionId
|
||||
&& ActiveTemplateLaunchTemplate.IsStructurallyValid();
|
||||
const bool bHasDisplayedRetainedTemplateLaunch =
|
||||
!bHasCurrentRunRecap
|
||||
&& bHasRetainedTemplateLaunch
|
||||
&& DisplayedRunRecap != nullptr
|
||||
&& !RetainedTemplateLaunchSessionId.IsEmpty()
|
||||
&& DisplayedRunRecap->TrainingSessionId == RetainedTemplateLaunchSessionId
|
||||
&& RetainedTemplateLaunchTemplate.IsStructurallyValid();
|
||||
const FHyperTwistTrainingSessionTemplate* DisplayedTemplateLaunch =
|
||||
bHasActiveTemplateLaunchForDisplayedRun
|
||||
? &ActiveTemplateLaunchTemplate
|
||||
: (bHasDisplayedRetainedTemplateLaunch ? &RetainedTemplateLaunchTemplate : nullptr);
|
||||
const bool bDisplayedTemplateLaunchUsesReviewLane =
|
||||
DisplayedTemplateLaunch != nullptr
|
||||
&& (DisplayedTemplateLaunch->bPreferReviewRun
|
||||
|| DisplayedTemplateLaunch->TemplateKind == EHyperTwistTrainingSessionTemplateKind::ReviewPlan
|
||||
|| DisplayedTemplateLaunch->TemplateKind == EHyperTwistTrainingSessionTemplateKind::Coach);
|
||||
const bool bCanRepeatDisplayedRun = !bHasLiveAttemptTimer
|
||||
&& RetainedRunRecapDeck.IsStructurallyValid()
|
||||
&& (!bHasActiveRun
|
||||
|
|
@ -10995,6 +11235,58 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
PreferredTemplateLaunch->bPreferShortReviewSet ? TEXT("yes") : TEXT("no")
|
||||
);
|
||||
}
|
||||
const FHyperTwistCoachDashboardTemplateLaunchHistoryEntry* SelectedTemplateLaunchHistoryEntry =
|
||||
SelectedTemplateLaunchHistoryIndex >= 0
|
||||
&& SelectedTemplateLaunchHistoryIndex < TemplateLaunchHistoryEntries.Num()
|
||||
? &TemplateLaunchHistoryEntries[SelectedTemplateLaunchHistoryIndex]
|
||||
: nullptr;
|
||||
const FString TemplateLaunchHistoryStatusLine =
|
||||
SelectedTemplateLaunchHistoryEntry != nullptr
|
||||
? FString::Printf(
|
||||
TEXT("Template launch history: %d entries | selected %d/%d | Template: %s | State: %s | Completed: %s"),
|
||||
TemplateLaunchHistoryEntries.Num(),
|
||||
SelectedTemplateLaunchHistoryIndex + 1,
|
||||
TemplateLaunchHistoryEntries.Num(),
|
||||
SelectedTemplateLaunchHistoryEntry->TemplateId.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *SelectedTemplateLaunchHistoryEntry->TemplateId,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
|
||||
SelectedTemplateLaunchHistoryEntry->SessionState),
|
||||
SelectedTemplateLaunchHistoryEntry->CompletedAtUtc.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *SelectedTemplateLaunchHistoryEntry->CompletedAtUtc)
|
||||
: TEXT("Template launch history: no completed template-backed runs retained yet.");
|
||||
const FString TemplateLaunchHistoryDetailLine =
|
||||
SelectedTemplateLaunchHistoryEntry != nullptr
|
||||
? FString::Printf(
|
||||
TEXT("Title: %s | kind: %s | mode: %s | selection: %s | deck: %s | user: %s | source plan: %s | recommended %d | focus %d | attempts %d | success %.2f | mistakes %d | review-backed: %s | coach-review: %s | short set: %s"),
|
||||
SelectedTemplateLaunchHistoryEntry->TemplateTitle.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *SelectedTemplateLaunchHistoryEntry->TemplateTitle,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
|
||||
SelectedTemplateLaunchHistoryEntry->TemplateKind),
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
|
||||
SelectedTemplateLaunchHistoryEntry->SuggestedMode),
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
|
||||
SelectedTemplateLaunchHistoryEntry->SuggestedSelectionPolicy),
|
||||
SelectedTemplateLaunchHistoryEntry->FocusDeckId.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *SelectedTemplateLaunchHistoryEntry->FocusDeckId,
|
||||
SelectedTemplateLaunchHistoryEntry->UserId.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *SelectedTemplateLaunchHistoryEntry->UserId,
|
||||
SelectedTemplateLaunchHistoryEntry->SourceReviewPlanId.IsEmpty()
|
||||
? TEXT("none")
|
||||
: *SelectedTemplateLaunchHistoryEntry->SourceReviewPlanId,
|
||||
SelectedTemplateLaunchHistoryEntry->RecommendedCaseCount,
|
||||
SelectedTemplateLaunchHistoryEntry->FocusCaseCount,
|
||||
SelectedTemplateLaunchHistoryEntry->CompletedAttemptCount,
|
||||
SelectedTemplateLaunchHistoryEntry->SuccessRate,
|
||||
SelectedTemplateLaunchHistoryEntry->TotalMistakes,
|
||||
SelectedTemplateLaunchHistoryEntry->bPreferReviewRun ? TEXT("yes") : TEXT("no"),
|
||||
SelectedTemplateLaunchHistoryEntry->bCoachReview ? TEXT("yes") : TEXT("no"),
|
||||
SelectedTemplateLaunchHistoryEntry->bPreferShortReviewSet ? TEXT("yes") : TEXT("no"))
|
||||
: TEXT("Template launch history detail: completed runs started through the dashboard template lane will accumulate here with template id, template kind, and source review-plan provenance.");
|
||||
const bool bCanUseCoachHandoff = !bHasLiveAttemptTimer && !bHasActiveRun;
|
||||
FString CoachHandoffStatusLine;
|
||||
if (bHasLiveAttemptTimer)
|
||||
|
|
@ -14958,16 +15250,35 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
{
|
||||
if (bHasDisplayedRunRecap)
|
||||
{
|
||||
RunRecapStatusTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Source: %s | Session: %s | Deck: %s | State: %s | Completed at: %s"),
|
||||
*RunRecapSourceLabel,
|
||||
*DisplayedRunRecap->TrainingSessionId,
|
||||
DisplayedRunRecap->DeckId.IsEmpty() ? TEXT("n/a") : *DisplayedRunRecap->DeckId,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedRunRecap->SessionState),
|
||||
DisplayedRunRecap->LastCompletedAtUtc.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *DisplayedRunRecap->LastCompletedAtUtc
|
||||
)));
|
||||
RunRecapStatusTextBlock->SetText(FText::FromString(
|
||||
DisplayedTemplateLaunch != nullptr
|
||||
? FString::Printf(
|
||||
TEXT("Source: %s | Session: %s | Deck: %s | State: %s | Completed at: %s | Template: %s (%s) | Source plan: %s"),
|
||||
*RunRecapSourceLabel,
|
||||
*DisplayedRunRecap->TrainingSessionId,
|
||||
DisplayedRunRecap->DeckId.IsEmpty() ? TEXT("n/a") : *DisplayedRunRecap->DeckId,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedRunRecap->SessionState),
|
||||
DisplayedRunRecap->LastCompletedAtUtc.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *DisplayedRunRecap->LastCompletedAtUtc,
|
||||
DisplayedTemplateLaunch->TemplateId.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *DisplayedTemplateLaunch->TemplateId,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
|
||||
DisplayedTemplateLaunch->TemplateKind),
|
||||
DisplayedTemplateLaunch->SourceReviewPlanId.IsEmpty()
|
||||
? TEXT("none")
|
||||
: *DisplayedTemplateLaunch->SourceReviewPlanId)
|
||||
: FString::Printf(
|
||||
TEXT("Source: %s | Session: %s | Deck: %s | State: %s | Completed at: %s"),
|
||||
*RunRecapSourceLabel,
|
||||
*DisplayedRunRecap->TrainingSessionId,
|
||||
DisplayedRunRecap->DeckId.IsEmpty() ? TEXT("n/a") : *DisplayedRunRecap->DeckId,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedRunRecap->SessionState),
|
||||
DisplayedRunRecap->LastCompletedAtUtc.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *DisplayedRunRecap->LastCompletedAtUtc)
|
||||
));
|
||||
}
|
||||
else if (bHasActiveRun)
|
||||
{
|
||||
|
|
@ -14986,23 +15297,51 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
{
|
||||
if (bHasDisplayedRunRecap)
|
||||
{
|
||||
RunRecapDetailTextBlock->SetText(FText::FromString(FString::Printf(
|
||||
TEXT("Attempts %d/%d | Success %d | Failure %d | Timeout %d | DNF %d | Aborted %d | Success rate %.2f | Best %d ms | Avg %d ms | Avg raw %d ms | Avg inspection %d ms | Mistakes %d | Last case %s"),
|
||||
DisplayedRunRecap->CompletedAttemptCount,
|
||||
DisplayedRunRecap->AttemptCount,
|
||||
DisplayedRunRecap->SuccessCount,
|
||||
DisplayedRunRecap->FailureCount,
|
||||
DisplayedRunRecap->TimeoutCount,
|
||||
DisplayedRunRecap->DNFCount,
|
||||
DisplayedRunRecap->AbortedCount,
|
||||
DisplayedRunRecap->SuccessRate,
|
||||
DisplayedRunRecap->BestTotalTimeMs,
|
||||
DisplayedRunRecap->AverageTotalTimeMs,
|
||||
DisplayedRunRecap->AverageRawSolveTimeMs,
|
||||
DisplayedRunRecap->AverageInspectionTimeMs,
|
||||
DisplayedRunRecap->TotalMistakes,
|
||||
DisplayedRunRecap->LastCaseId.IsEmpty() ? TEXT("n/a") : *DisplayedRunRecap->LastCaseId
|
||||
)));
|
||||
RunRecapDetailTextBlock->SetText(FText::FromString(
|
||||
DisplayedTemplateLaunch != nullptr
|
||||
? FString::Printf(
|
||||
TEXT("Attempts %d/%d | Success %d | Failure %d | Timeout %d | DNF %d | Aborted %d | Success rate %.2f | Best %d ms | Avg %d ms | Avg raw %d ms | Avg inspection %d ms | Mistakes %d | Last case %s | Template title %s | Template mode %s | Template selection %s | Recommended %d | Focus %d | Review-backed %s"),
|
||||
DisplayedRunRecap->CompletedAttemptCount,
|
||||
DisplayedRunRecap->AttemptCount,
|
||||
DisplayedRunRecap->SuccessCount,
|
||||
DisplayedRunRecap->FailureCount,
|
||||
DisplayedRunRecap->TimeoutCount,
|
||||
DisplayedRunRecap->DNFCount,
|
||||
DisplayedRunRecap->AbortedCount,
|
||||
DisplayedRunRecap->SuccessRate,
|
||||
DisplayedRunRecap->BestTotalTimeMs,
|
||||
DisplayedRunRecap->AverageTotalTimeMs,
|
||||
DisplayedRunRecap->AverageRawSolveTimeMs,
|
||||
DisplayedRunRecap->AverageInspectionTimeMs,
|
||||
DisplayedRunRecap->TotalMistakes,
|
||||
DisplayedRunRecap->LastCaseId.IsEmpty() ? TEXT("n/a") : *DisplayedRunRecap->LastCaseId,
|
||||
DisplayedTemplateLaunch->Title.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *DisplayedTemplateLaunch->Title,
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
|
||||
DisplayedTemplateLaunch->SuggestedMode),
|
||||
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
|
||||
DisplayedTemplateLaunch->SuggestedSelectionPolicy),
|
||||
DisplayedTemplateLaunch->RecommendedCaseCount,
|
||||
DisplayedTemplateLaunch->FocusCaseIds.Num(),
|
||||
bDisplayedTemplateLaunchUsesReviewLane ? TEXT("yes") : TEXT("no"))
|
||||
: FString::Printf(
|
||||
TEXT("Attempts %d/%d | Success %d | Failure %d | Timeout %d | DNF %d | Aborted %d | Success rate %.2f | Best %d ms | Avg %d ms | Avg raw %d ms | Avg inspection %d ms | Mistakes %d | Last case %s"),
|
||||
DisplayedRunRecap->CompletedAttemptCount,
|
||||
DisplayedRunRecap->AttemptCount,
|
||||
DisplayedRunRecap->SuccessCount,
|
||||
DisplayedRunRecap->FailureCount,
|
||||
DisplayedRunRecap->TimeoutCount,
|
||||
DisplayedRunRecap->DNFCount,
|
||||
DisplayedRunRecap->AbortedCount,
|
||||
DisplayedRunRecap->SuccessRate,
|
||||
DisplayedRunRecap->BestTotalTimeMs,
|
||||
DisplayedRunRecap->AverageTotalTimeMs,
|
||||
DisplayedRunRecap->AverageRawSolveTimeMs,
|
||||
DisplayedRunRecap->AverageInspectionTimeMs,
|
||||
DisplayedRunRecap->TotalMistakes,
|
||||
DisplayedRunRecap->LastCaseId.IsEmpty() ? TEXT("n/a") : *DisplayedRunRecap->LastCaseId)
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -15143,6 +15482,39 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
{
|
||||
TemplateLaunchDetailTextBlock->SetText(FText::FromString(TemplateLaunchDetailLine));
|
||||
}
|
||||
if (TemplateLaunchHistoryHeaderTextBlock != nullptr)
|
||||
{
|
||||
TemplateLaunchHistoryHeaderTextBlock->SetText(FText::FromString(
|
||||
TEXT("[Template Launch History]")));
|
||||
}
|
||||
if (TemplateLaunchHistoryStatusTextBlock != nullptr)
|
||||
{
|
||||
TemplateLaunchHistoryStatusTextBlock->SetText(FText::FromString(
|
||||
TemplateLaunchHistoryStatusLine));
|
||||
}
|
||||
if (TemplateLaunchHistoryDetailTextBlock != nullptr)
|
||||
{
|
||||
TemplateLaunchHistoryDetailTextBlock->SetText(FText::FromString(
|
||||
TemplateLaunchHistoryDetailLine));
|
||||
}
|
||||
if (PreviousTemplateLaunchHistoryButtonLabel != nullptr)
|
||||
{
|
||||
PreviousTemplateLaunchHistoryButtonLabel->SetText(FText::FromString(
|
||||
TEXT("Prev Template Launch")));
|
||||
}
|
||||
if (PreviousTemplateLaunchHistoryButton != nullptr)
|
||||
{
|
||||
PreviousTemplateLaunchHistoryButton->SetIsEnabled(TemplateLaunchHistoryEntries.Num() > 1);
|
||||
}
|
||||
if (NextTemplateLaunchHistoryButtonLabel != nullptr)
|
||||
{
|
||||
NextTemplateLaunchHistoryButtonLabel->SetText(FText::FromString(
|
||||
TEXT("Next Template Launch")));
|
||||
}
|
||||
if (NextTemplateLaunchHistoryButton != nullptr)
|
||||
{
|
||||
NextTemplateLaunchHistoryButton->SetIsEnabled(TemplateLaunchHistoryEntries.Num() > 1);
|
||||
}
|
||||
if (HandoffHistoryHeaderTextBlock != nullptr)
|
||||
{
|
||||
HandoffHistoryHeaderTextBlock->SetText(FText::FromString(TEXT("[Handoff History]")));
|
||||
|
|
|
|||
|
|
@ -372,6 +372,40 @@ struct FHyperTwistCoachDashboardHandoffHistoryEntry
|
|||
}
|
||||
};
|
||||
|
||||
struct FHyperTwistCoachDashboardTemplateLaunchHistoryEntry
|
||||
{
|
||||
FString RecordedAtUtc;
|
||||
FString SourceSessionId;
|
||||
FString TemplateId;
|
||||
FString TemplateTitle;
|
||||
FString UserId;
|
||||
FString FocusDeckId;
|
||||
FString SourceReviewPlanId;
|
||||
EHyperTwistTrainingSessionTemplateKind TemplateKind =
|
||||
EHyperTwistTrainingSessionTemplateKind::Recovery;
|
||||
EHyperTwistTrainingSessionState SessionState =
|
||||
EHyperTwistTrainingSessionState::Created;
|
||||
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy =
|
||||
EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
int32 RecommendedCaseCount = 0;
|
||||
int32 FocusCaseCount = 0;
|
||||
int32 CompletedAttemptCount = 0;
|
||||
int32 TotalMistakes = 0;
|
||||
float SuccessRate = 0.0f;
|
||||
FString CompletedAtUtc;
|
||||
bool bPreferReviewRun = false;
|
||||
bool bCoachReview = false;
|
||||
bool bPreferShortReviewSet = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !RecordedAtUtc.IsEmpty()
|
||||
&& !SourceSessionId.IsEmpty()
|
||||
&& !TemplateId.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct FHyperTwistCoachDashboardMethodDrillFollowUpHistoryEntry
|
||||
{
|
||||
FString RecordedAtUtc;
|
||||
|
|
@ -560,6 +594,9 @@ public:
|
|||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
int32 SelectedHandoffHistoryIndex = INDEX_NONE;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
int32 SelectedTemplateLaunchHistoryIndex = INDEX_NONE;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
int32 SelectedQueueSuppressionIndex = INDEX_NONE;
|
||||
|
||||
|
|
@ -671,6 +708,12 @@ public:
|
|||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool SelectNextHandoffHistoryEntry();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool SelectPreviousTemplateLaunchHistoryEntry();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool SelectNextTemplateLaunchHistoryEntry();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
|
||||
bool SelectPreviousQueueSuppressionHistoryEntry();
|
||||
|
||||
|
|
@ -839,6 +882,15 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> TemplateLaunchDetailTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> TemplateLaunchHistoryHeaderTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> TemplateLaunchHistoryStatusTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> TemplateLaunchHistoryDetailTextBlock = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> HandoffHistoryHeaderTextBlock = nullptr;
|
||||
|
||||
|
|
@ -860,6 +912,18 @@ protected:
|
|||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> NextHandoffHistoryButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> PreviousTemplateLaunchHistoryButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> PreviousTemplateLaunchHistoryButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UButton> NextTemplateLaunchHistoryButton = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> NextTemplateLaunchHistoryButtonLabel = nullptr;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextBlock> GuidanceReviewHeaderTextBlock = nullptr;
|
||||
|
||||
|
|
@ -1409,6 +1473,12 @@ protected:
|
|||
UFUNCTION()
|
||||
void HandleNextHandoffHistoryClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandlePreviousTemplateLaunchHistoryClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleNextTemplateLaunchHistoryClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandlePreviousQueueSuppressionClicked();
|
||||
|
||||
|
|
@ -1495,6 +1565,7 @@ private:
|
|||
void SyncSelectedGuidancePreviewCase();
|
||||
void SyncSelectedGuidanceDecisionHistoryEntry();
|
||||
void SyncSelectedHandoffHistoryEntry();
|
||||
void SyncSelectedTemplateLaunchHistoryEntry();
|
||||
void SyncSelectedQueueSuppressionHistoryEntry();
|
||||
void SyncSelectedClosureRecoveryHistoryEntry();
|
||||
void SyncSelectedMethodDrillFollowUpHistoryEntry();
|
||||
|
|
@ -1578,6 +1649,7 @@ private:
|
|||
);
|
||||
void RecordQueueSuppressionSnapshot();
|
||||
void RecordClosureRecoverySnapshot();
|
||||
void RecordTemplateLaunchHistoryEntry(const FHyperTwistTrainingSessionSummary& CompletedRunRecap);
|
||||
void RecordMethodDrillFollowUpHistoryEntry(const FHyperTwistTrainingSessionSummary& CompletedRunRecap);
|
||||
void RecordMethodDrillRecoveryMemoryHistorySnapshot(
|
||||
const FHyperTwistCoachDashboardQueueRecoveryWeighting& QueueRecoveryWeighting,
|
||||
|
|
@ -1603,6 +1675,10 @@ private:
|
|||
bool bSingleCaseLaunch,
|
||||
const FString& SelectedCaseId
|
||||
);
|
||||
void CaptureActiveTemplateLaunchContext(
|
||||
const FString& SessionId,
|
||||
const FHyperTwistTrainingSessionTemplate& SessionTemplate
|
||||
);
|
||||
void CaptureActiveMethodDrillFollowUpLaunchContext(const FString& SessionId);
|
||||
bool IsLiveAttemptInspectionPhase() const;
|
||||
int32 GetMaxManualSplitCaptureCount() const;
|
||||
|
|
@ -1619,6 +1695,11 @@ private:
|
|||
FString RetainedRunRecapUserId;
|
||||
FString RetainedRunRecapSessionId;
|
||||
EHyperTwistTrainingDeliveryMode RetainedRunRecapMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
FString ActiveTemplateLaunchSessionId;
|
||||
FHyperTwistTrainingSessionTemplate ActiveTemplateLaunchTemplate;
|
||||
bool bHasRetainedTemplateLaunch = false;
|
||||
FString RetainedTemplateLaunchSessionId;
|
||||
FHyperTwistTrainingSessionTemplate RetainedTemplateLaunchTemplate;
|
||||
FString ActiveGuidanceLaunchSessionId;
|
||||
EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidanceLaunchLane =
|
||||
EHyperTwistCoachDashboardGuidancePreviewLane::None;
|
||||
|
|
@ -1642,6 +1723,7 @@ private:
|
|||
FString RetainedGuidanceComparisonSelectedCaseId;
|
||||
TArray<FHyperTwistCoachDashboardDecisionHistoryEntry> GuidanceDecisionHistoryEntries;
|
||||
TArray<FHyperTwistCoachDashboardHandoffHistoryEntry> HandoffHistoryEntries;
|
||||
TArray<FHyperTwistCoachDashboardTemplateLaunchHistoryEntry> TemplateLaunchHistoryEntries;
|
||||
TArray<FHyperTwistCoachDashboardMethodDrillFollowUpHistoryEntry> MethodDrillFollowUpHistoryEntries;
|
||||
TArray<FHyperTwistCoachDashboardMethodDrillRecoveryMemoryHistoryEntry>
|
||||
MethodDrillRecoveryMemoryHistoryEntries;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
# HyperTwist Phase 4 template launch provenance packet
|
||||
|
||||
Created on `2026-05-06`
|
||||
|
||||
Status:
|
||||
|
||||
- first-party HyperTwist packet
|
||||
- bounded Phase `4` dashboard template provenance slice
|
||||
|
||||
## Purpose
|
||||
|
||||
This packet captures and surfaces template-launch provenance for completed runs started through the dashboard template lane.
|
||||
|
||||
The open tasks were:
|
||||
|
||||
- remember which active session template launched a completed dashboard run
|
||||
- retain the template `TemplateId`, `TemplateKind`, and `SourceReviewPlanId` alongside the run recap
|
||||
- add a first-party dashboard history surface so repeated template launches can be audited
|
||||
|
||||
It is not:
|
||||
|
||||
- a repository schema migration packet
|
||||
- a template browser or template editing packet
|
||||
- a new run-restart or replay semantics packet
|
||||
- a broader dashboard history redesign
|
||||
|
||||
## Scope
|
||||
|
||||
Bounded lane:
|
||||
|
||||
- capture active template launch context when `StartPreferredActiveSessionTemplateRun()` succeeds
|
||||
- carry that context through `SyncRetainedRunRecap()` when the run completes or aborts
|
||||
- record retained template-launch history snapshots with run outcome fields
|
||||
- append template provenance to the run recap display when the displayed recap came from a template-backed launch
|
||||
- add a dedicated `[Template Launch History]` section with simple previous/next navigation
|
||||
|
||||
Out of scope:
|
||||
|
||||
- changing repository persistence for run summaries
|
||||
- altering template selection priority
|
||||
- changing method-drill follow-up weighting logic
|
||||
- changing coach handoff recommendation rules
|
||||
|
||||
## Why this was the right next packet
|
||||
|
||||
The previous packet made the template-backed launch lane visible and clickable from the first-party dashboard.
|
||||
|
||||
That still left one practical gap:
|
||||
|
||||
- once a template-backed run finished, the recap no longer explained which template produced it
|
||||
- repeated template launches had no retained audit trail on the dashboard itself
|
||||
- review-preferring template outcomes could not be compared from the same first-party surface that launched them
|
||||
|
||||
The next honest move was therefore:
|
||||
|
||||
- capture template provenance at launch time
|
||||
- retain it only through the dashboard recap/history path
|
||||
- and keep the change narrowly scoped to first-party dashboard state and presentation
|
||||
|
||||
## What landed
|
||||
|
||||
Primary code changes:
|
||||
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h`
|
||||
- added retained template-launch history entry shape and selected-history index
|
||||
- added active/retained template launch context members
|
||||
- added dashboard text/button members and handlers for a new template-launch history section
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp`
|
||||
- captures template launch context when the preferred dashboard template run starts
|
||||
- records template launch provenance when the matching run recap completes or aborts
|
||||
- retains up to sixteen template launch history snapshots
|
||||
- appends template provenance into the displayed run recap when applicable
|
||||
- adds a `[Template Launch History]` status/detail section with previous/next navigation
|
||||
|
||||
## Product effect
|
||||
|
||||
The first-party dashboard now keeps template-backed run provenance attached to the run outcome:
|
||||
|
||||
- run recap shows the template id, template kind, and source review-plan id for matching template launches
|
||||
- the dashboard retains a small history of completed template-backed runs and their outcome summaries
|
||||
- operators can page through recent template launches without leaving the dashboard
|
||||
- review-preferring template runs are now auditable from launch through recap on the same first-party surface
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- successful dashboard template launches capture template provenance
|
||||
- completed or aborted template-backed runs retain that provenance in recap state
|
||||
- dashboard shows template provenance in run recap when the displayed run came from a template launch
|
||||
- dashboard exposes retained template-launch history with previous/next navigation
|
||||
- full product build succeeds
|
||||
|
||||
## Validation checklist
|
||||
|
||||
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
|
||||
2. launch a run through the dashboard template button
|
||||
3. complete or abort the run and confirm run recap shows template provenance
|
||||
4. confirm the dashboard `[Template Launch History]` section records the completed run
|
||||
|
||||
That is the packet.
|
||||
Loading…
Add table
Reference in a new issue