Expose dashboard run recap status and detail surfaces

This commit is contained in:
axiomlogicnexus 2026-05-10 15:38:49 +02:00
parent 7e28d89f50
commit 00c8daf216
5 changed files with 643 additions and 107 deletions

View file

@ -12318,6 +12318,307 @@ UHyperTwistCoachDashboardWidget::GetDisplayedActiveRunStatusInspectSurface() con
return Surface;
}
FHyperTwistTrainingCoachDashboardRunRecapStatusInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedRunRecapStatusInspectSurface() const
{
FHyperTwistTrainingCoachDashboardRunRecapStatusInspectSurface Surface;
Surface.Headline = TEXT("Run recap status");
const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid()
&& CachedRunState.ActiveDeck.IsStructurallyValid();
const bool bHasCurrentRunRecap = bHasActiveRun
&& (CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed
|| CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted);
const FHyperTwistTrainingSessionSummary* DisplayedRunRecap = bHasCurrentRunRecap
? &CachedRunSummary
: (bHasRetainedRunRecap ? &RetainedRunRecapSummary : nullptr);
const FHyperTwistTrainingDeck* DisplayedRunRecapDeck = bHasCurrentRunRecap
? &CachedRunState.ActiveDeck
: (bHasRetainedRunRecap ? &RetainedRunRecapDeck : nullptr);
const EHyperTwistTrainingDeliveryMode DisplayedRunRecapMode = bHasCurrentRunRecap
? CachedRunState.Session.Mode
: RetainedRunRecapMode;
const bool bHasDisplayedRunRecap = DisplayedRunRecap != nullptr
&& !DisplayedRunRecap->TrainingSessionId.IsEmpty();
const FString RunRecapSourceLabel = bHasDisplayedRunRecap
? (bHasCurrentRunRecap ? TEXT("current run") : TEXT("last completed run"))
: (bHasActiveRun ? TEXT("active run") : TEXT("none yet"));
const bool bHasActiveTemplateLaunchForDisplayedRun =
bHasCurrentRunRecap
&& DisplayedRunRecap != nullptr
&& !ActiveTemplateLaunchSessionId.IsEmpty()
&& DisplayedRunRecap->TrainingSessionId == ActiveTemplateLaunchSessionId
&& ActiveTemplateLaunchProvenance.IsStructurallyValid();
const bool bHasDisplayedRetainedTemplateLaunch =
!bHasCurrentRunRecap
&& bHasRetainedTemplateLaunch
&& DisplayedRunRecap != nullptr
&& !RetainedTemplateLaunchSessionId.IsEmpty()
&& DisplayedRunRecap->TrainingSessionId == RetainedTemplateLaunchSessionId
&& RetainedTemplateLaunchProvenance.IsStructurallyValid();
const FHyperTwistTrainingTemplateLaunchProvenance* DisplayedTemplateLaunch =
bHasActiveTemplateLaunchForDisplayedRun
? &ActiveTemplateLaunchProvenance
: (bHasDisplayedRetainedTemplateLaunch ? &RetainedTemplateLaunchProvenance : nullptr);
const FString DisplayedDeckLabel =
DisplayedRunRecapDeck != nullptr
? (DisplayedRunRecapDeck->Title.IsEmpty()
? (DisplayedRunRecapDeck->DeckId.IsEmpty()
? TEXT("n/a")
: DisplayedRunRecapDeck->DeckId)
: DisplayedRunRecapDeck->Title)
: (bHasDisplayedRunRecap && !DisplayedRunRecap->DeckId.IsEmpty()
? DisplayedRunRecap->DeckId
: TEXT("n/a"));
Surface.SourceLabel = RunRecapSourceLabel;
Surface.TrainingSessionId = bHasDisplayedRunRecap
? DisplayedRunRecap->TrainingSessionId
: TEXT("n/a");
Surface.DeckId = bHasDisplayedRunRecap && !DisplayedRunRecap->DeckId.IsEmpty()
? DisplayedRunRecap->DeckId
: TEXT("n/a");
Surface.DeckLabel = DisplayedDeckLabel;
Surface.SessionStateLabel = bHasDisplayedRunRecap
? HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedRunRecap->SessionState)
: TEXT("n/a");
Surface.DeliveryModeLabel = bHasDisplayedRunRecap
? HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedRunRecapMode)
: TEXT("n/a");
Surface.CompletedAtUtc = bHasDisplayedRunRecap && !DisplayedRunRecap->LastCompletedAtUtc.IsEmpty()
? DisplayedRunRecap->LastCompletedAtUtc
: TEXT("n/a");
Surface.TemplateId = DisplayedTemplateLaunch != nullptr && !DisplayedTemplateLaunch->TemplateId.IsEmpty()
? DisplayedTemplateLaunch->TemplateId
: TEXT("n/a");
Surface.TemplateKindLabel = DisplayedTemplateLaunch != nullptr
? HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedTemplateLaunch->TemplateKind)
: TEXT("n/a");
Surface.TemplateSourcePlanId =
DisplayedTemplateLaunch != nullptr && !DisplayedTemplateLaunch->SourceReviewPlanId.IsEmpty()
? DisplayedTemplateLaunch->SourceReviewPlanId
: TEXT("none");
Surface.bHasDisplayedRunRecap = bHasDisplayedRunRecap;
Surface.bHasCurrentRunRecap = bHasCurrentRunRecap;
Surface.bHasRetainedRunRecap = !bHasCurrentRunRecap && bHasRetainedRunRecap;
Surface.bHasTemplateLaunch = DisplayedTemplateLaunch != nullptr;
Surface.bTemplateUsesReviewLane =
DisplayedTemplateLaunch != nullptr
&& (DisplayedTemplateLaunch->bPreferReviewRun
|| DisplayedTemplateLaunch->TemplateKind == EHyperTwistTrainingSessionTemplateKind::ReviewPlan
|| DisplayedTemplateLaunch->TemplateKind == EHyperTwistTrainingSessionTemplateKind::Coach);
Surface.bHasActiveRun = bHasActiveRun;
Surface.StatusLine = !DisplayedRunRecapStatusLine.IsEmpty()
? DisplayedRunRecapStatusLine
: (bHasDisplayedRunRecap
? (DisplayedTemplateLaunch != nullptr
? FString::Printf(
TEXT("Source: %s | Session: %s | Deck: %s | State: %s | Completed at: %s | Template: %s (%s) | Source plan: %s"),
*Surface.SourceLabel,
*Surface.TrainingSessionId,
*Surface.DeckId,
*Surface.SessionStateLabel,
*Surface.CompletedAtUtc,
*Surface.TemplateId,
*Surface.TemplateKindLabel,
*Surface.TemplateSourcePlanId)
: FString::Printf(
TEXT("Source: %s | Session: %s | Deck: %s | State: %s | Completed at: %s"),
*Surface.SourceLabel,
*Surface.TrainingSessionId,
*Surface.DeckId,
*Surface.SessionStateLabel,
*Surface.CompletedAtUtc))
: (bHasActiveRun
? TEXT("Source: active run | recap will stabilize here when the run completes or aborts.")
: TEXT("Source: none yet | complete a run to retain a session recap on this dashboard.")));
return Surface;
}
FHyperTwistTrainingCoachDashboardRunRecapDetailInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedRunRecapDetailInspectSurface() const
{
FHyperTwistTrainingCoachDashboardRunRecapDetailInspectSurface Surface;
Surface.Headline = TEXT("Run recap detail");
const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid()
&& CachedRunState.ActiveDeck.IsStructurallyValid();
const bool bHasCurrentRunRecap = bHasActiveRun
&& (CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed
|| CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted);
const FHyperTwistTrainingSessionSummary* DisplayedRunRecap = bHasCurrentRunRecap
? &CachedRunSummary
: (bHasRetainedRunRecap ? &RetainedRunRecapSummary : nullptr);
const bool bHasDisplayedRunRecap = DisplayedRunRecap != nullptr
&& !DisplayedRunRecap->TrainingSessionId.IsEmpty();
const FString RunRecapSourceLabel = bHasDisplayedRunRecap
? (bHasCurrentRunRecap ? TEXT("current run") : TEXT("last completed run"))
: (bHasActiveRun ? TEXT("active run") : TEXT("none yet"));
const bool bHasActiveTemplateLaunchForDisplayedRun =
bHasCurrentRunRecap
&& DisplayedRunRecap != nullptr
&& !ActiveTemplateLaunchSessionId.IsEmpty()
&& DisplayedRunRecap->TrainingSessionId == ActiveTemplateLaunchSessionId
&& ActiveTemplateLaunchProvenance.IsStructurallyValid();
const bool bHasDisplayedRetainedTemplateLaunch =
!bHasCurrentRunRecap
&& bHasRetainedTemplateLaunch
&& DisplayedRunRecap != nullptr
&& !RetainedTemplateLaunchSessionId.IsEmpty()
&& DisplayedRunRecap->TrainingSessionId == RetainedTemplateLaunchSessionId
&& RetainedTemplateLaunchProvenance.IsStructurallyValid();
const FHyperTwistTrainingTemplateLaunchProvenance* DisplayedTemplateLaunch =
bHasActiveTemplateLaunchForDisplayedRun
? &ActiveTemplateLaunchProvenance
: (bHasDisplayedRetainedTemplateLaunch ? &RetainedTemplateLaunchProvenance : nullptr);
const FString DisplayedTemplateLaunchUserId = DisplayedTemplateLaunch != nullptr
? (DisplayedTemplateLaunch->UserId.IsEmpty()
? (bHasCurrentRunRecap ? CachedRunState.Session.UserId : RetainedRunRecapUserId)
: DisplayedTemplateLaunch->UserId)
: FString();
const FHyperTwistTrainingTemplateLaunchScopedStats* DisplayedTemplateLaunchStats =
DisplayedTemplateLaunch != nullptr
? FindCachedTemplateLaunchScopedStats(
DisplayedTemplateLaunch->TemplateId,
DisplayedTemplateLaunchUserId)
: nullptr;
const FString DisplayedTemplateLaunchSelectionDetail =
HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchSelectionDetailFragment(
DisplayedTemplateLaunch != nullptr
&& DisplayedTemplateLaunch->SelectionProvenance.IsStructurallyValid()
? &DisplayedTemplateLaunch->SelectionProvenance
: nullptr
);
const bool bDisplayedTemplateLaunchUsesReviewLane =
DisplayedTemplateLaunch != nullptr
&& (DisplayedTemplateLaunch->bPreferReviewRun
|| DisplayedTemplateLaunch->TemplateKind == EHyperTwistTrainingSessionTemplateKind::ReviewPlan
|| DisplayedTemplateLaunch->TemplateKind == EHyperTwistTrainingSessionTemplateKind::Coach);
const FString TemplateAggregateFragment = DisplayedTemplateLaunch != nullptr
? HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsOutcomeFragment(
DisplayedTemplateLaunchStats)
: TEXT("n/a");
const FString TemplateAnalyticsDetailFragment = DisplayedTemplateLaunch != nullptr
? HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsDetailFragment(
DisplayedTemplateLaunchStats)
: TEXT("n/a");
const FString TemplateTitle = DisplayedTemplateLaunch != nullptr
? (DisplayedTemplateLaunch->Title.IsEmpty()
? (DisplayedTemplateLaunch->TemplateId.IsEmpty()
? TEXT("n/a")
: DisplayedTemplateLaunch->TemplateId)
: DisplayedTemplateLaunch->Title)
: TEXT("n/a");
Surface.SourceLabel = RunRecapSourceLabel;
Surface.LastCaseId = bHasDisplayedRunRecap && !DisplayedRunRecap->LastCaseId.IsEmpty()
? DisplayedRunRecap->LastCaseId
: TEXT("n/a");
Surface.TemplateTitle = TemplateTitle;
Surface.TemplateModeLabel = DisplayedTemplateLaunch != nullptr
? HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(DisplayedTemplateLaunch->SuggestedMode)
: TEXT("n/a");
Surface.TemplateSelectionLabel = DisplayedTemplateLaunch != nullptr
? HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
DisplayedTemplateLaunch->SuggestedSelectionPolicy)
: TEXT("n/a");
Surface.TemplateAggregateFragment = TemplateAggregateFragment;
Surface.TemplateAnalyticsDetailFragment = TemplateAnalyticsDetailFragment;
Surface.TemplateSelectionDetailFragment = DisplayedTemplateLaunchSelectionDetail;
Surface.AttemptCount = bHasDisplayedRunRecap ? DisplayedRunRecap->AttemptCount : 0;
Surface.CompletedAttemptCount = bHasDisplayedRunRecap ? DisplayedRunRecap->CompletedAttemptCount : 0;
Surface.SuccessCount = bHasDisplayedRunRecap ? DisplayedRunRecap->SuccessCount : 0;
Surface.FailureCount = bHasDisplayedRunRecap ? DisplayedRunRecap->FailureCount : 0;
Surface.TimeoutCount = bHasDisplayedRunRecap ? DisplayedRunRecap->TimeoutCount : 0;
Surface.DNFCount = bHasDisplayedRunRecap ? DisplayedRunRecap->DNFCount : 0;
Surface.AbortedCount = bHasDisplayedRunRecap ? DisplayedRunRecap->AbortedCount : 0;
Surface.BestTotalTimeMs = bHasDisplayedRunRecap ? DisplayedRunRecap->BestTotalTimeMs : 0;
Surface.AverageTotalTimeMs = bHasDisplayedRunRecap ? DisplayedRunRecap->AverageTotalTimeMs : 0;
Surface.AverageRawSolveTimeMs = bHasDisplayedRunRecap ? DisplayedRunRecap->AverageRawSolveTimeMs : 0;
Surface.AverageInspectionTimeMs = bHasDisplayedRunRecap
? DisplayedRunRecap->AverageInspectionTimeMs
: 0;
Surface.TotalMistakes = bHasDisplayedRunRecap ? DisplayedRunRecap->TotalMistakes : 0;
Surface.RecommendedCaseCount = DisplayedTemplateLaunch != nullptr
? DisplayedTemplateLaunch->RecommendedCaseCount
: 0;
Surface.FocusCaseCount = DisplayedTemplateLaunch != nullptr
? DisplayedTemplateLaunch->FocusCaseIds.Num()
: 0;
Surface.SuccessRate = bHasDisplayedRunRecap ? DisplayedRunRecap->SuccessRate : 0.0f;
Surface.bHasDisplayedRunRecap = bHasDisplayedRunRecap;
Surface.bHasCurrentRunRecap = bHasCurrentRunRecap;
Surface.bHasTemplateLaunch = DisplayedTemplateLaunch != nullptr;
Surface.bTemplateUsesReviewLane = bDisplayedTemplateLaunchUsesReviewLane;
Surface.bHasTemplateSelectionDetail = !DisplayedTemplateLaunchSelectionDetail.IsEmpty();
Surface.bHasTemplateAnalytics = DisplayedTemplateLaunchStats != nullptr
&& DisplayedTemplateLaunchStats->IsStructurallyValid();
if (!DisplayedRunRecapDetailLine.IsEmpty())
{
Surface.DetailLine = DisplayedRunRecapDetailLine;
return Surface;
}
if (bHasDisplayedRunRecap)
{
Surface.DetailLine =
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 | Template aggregate %s | Template detail %s"),
Surface.CompletedAttemptCount,
Surface.AttemptCount,
Surface.SuccessCount,
Surface.FailureCount,
Surface.TimeoutCount,
Surface.DNFCount,
Surface.AbortedCount,
Surface.SuccessRate,
Surface.BestTotalTimeMs,
Surface.AverageTotalTimeMs,
Surface.AverageRawSolveTimeMs,
Surface.AverageInspectionTimeMs,
Surface.TotalMistakes,
*Surface.LastCaseId,
*Surface.TemplateTitle,
*Surface.TemplateModeLabel,
*Surface.TemplateSelectionLabel,
Surface.RecommendedCaseCount,
Surface.FocusCaseCount,
Surface.bTemplateUsesReviewLane ? TEXT("yes") : TEXT("no"),
*Surface.TemplateAggregateFragment,
*Surface.TemplateAnalyticsDetailFragment)
: 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"),
Surface.CompletedAttemptCount,
Surface.AttemptCount,
Surface.SuccessCount,
Surface.FailureCount,
Surface.TimeoutCount,
Surface.DNFCount,
Surface.AbortedCount,
Surface.SuccessRate,
Surface.BestTotalTimeMs,
Surface.AverageTotalTimeMs,
Surface.AverageRawSolveTimeMs,
Surface.AverageInspectionTimeMs,
Surface.TotalMistakes,
*Surface.LastCaseId);
if (Surface.bHasTemplateSelectionDetail)
{
Surface.DetailLine += TEXT(" | ");
Surface.DetailLine += Surface.TemplateSelectionDetailFragment;
}
}
else
{
Surface.DetailLine =
TEXT("Recap detail: unavailable until a run has been completed or explicitly aborted.");
}
return Surface;
}
FHyperTwistTrainingCoachDashboardActiveAttemptTimerStatusInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedActiveAttemptTimerStatusInspectSurface() const
{
@ -25242,118 +25543,115 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
RunRecapHeaderTextBlock->SetText(FText::FromString(TEXT("[Run Recap]")));
}
if (bHasDisplayedRunRecap)
{
DisplayedRunRecapStatusLine =
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)
{
DisplayedRunRecapStatusLine =
TEXT("Source: active run | recap will stabilize here when the run completes or aborts.");
}
else
{
DisplayedRunRecapStatusLine =
TEXT("Source: none yet | complete a run to retain a session recap on this dashboard.");
}
if (RunRecapStatusTextBlock != nullptr)
{
if (bHasDisplayedRunRecap)
RunRecapStatusTextBlock->SetText(FText::FromString(DisplayedRunRecapStatusLine));
}
if (bHasDisplayedRunRecap)
{
DisplayedRunRecapDetailLine =
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 | Template aggregate %s | Template detail %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"),
*HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsOutcomeFragment(
DisplayedTemplateLaunchStats),
*HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsDetailFragment(
DisplayedTemplateLaunchStats))
: 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);
if (!DisplayedTemplateLaunchSelectionDetail.IsEmpty())
{
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)
{
RunRecapStatusTextBlock->SetText(FText::FromString(
TEXT("Source: active run | recap will stabilize here when the run completes or aborts.")
));
}
else
{
RunRecapStatusTextBlock->SetText(FText::FromString(
TEXT("Source: none yet | complete a run to retain a session recap on this dashboard.")
));
DisplayedRunRecapDetailLine += TEXT(" | ");
DisplayedRunRecapDetailLine += DisplayedTemplateLaunchSelectionDetail;
}
}
else
{
DisplayedRunRecapDetailLine =
TEXT("Recap detail: unavailable until a run has been completed or explicitly aborted.");
}
if (RunRecapDetailTextBlock != nullptr)
{
if (bHasDisplayedRunRecap)
{
FString RunRecapDetailLine =
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 | Template aggregate %s | Template detail %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"),
*HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsOutcomeFragment(
DisplayedTemplateLaunchStats),
*HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsDetailFragment(
DisplayedTemplateLaunchStats))
: 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);
if (!DisplayedTemplateLaunchSelectionDetail.IsEmpty())
{
RunRecapDetailLine += TEXT(" | ");
RunRecapDetailLine += DisplayedTemplateLaunchSelectionDetail;
}
RunRecapDetailTextBlock->SetText(FText::FromString(RunRecapDetailLine));
}
else
{
RunRecapDetailTextBlock->SetText(FText::FromString(
TEXT("Recap detail: unavailable until a run has been completed or explicitly aborted.")
));
}
RunRecapDetailTextBlock->SetText(FText::FromString(DisplayedRunRecapDetailLine));
}
if (GuidanceComparisonHeaderTextBlock != nullptr)
{

View file

@ -875,6 +875,14 @@ public:
FHyperTwistTrainingCoachDashboardActiveRunStatusInspectSurface
GetDisplayedActiveRunStatusInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardRunRecapStatusInspectSurface
GetDisplayedRunRecapStatusInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardRunRecapDetailInspectSurface
GetDisplayedRunRecapDetailInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardActiveAttemptTimerStatusInspectSurface
GetDisplayedActiveAttemptTimerStatusInspectSurface() const;
@ -2014,6 +2022,8 @@ private:
FString DisplayedTransitionStatusLine;
FString DisplayedActiveRunStatusLine;
FString DisplayedActiveRunSummaryLine;
FString DisplayedRunRecapStatusLine;
FString DisplayedRunRecapDetailLine;
FString DisplayedCurrentCasePromptLine;
FString DisplayedCurrentCaseNotationLine;
FString DisplayedAttemptTimeHintLabel;

View file

@ -13300,6 +13300,179 @@ struct FHyperTwistTrainingCoachDashboardActiveRunStatusInspectSurface
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardRunRecapStatusInspectSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString TrainingSessionId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DeckId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DeckLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SessionStateLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DeliveryModeLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CompletedAtUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString TemplateId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString TemplateKindLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString TemplateSourcePlanId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasDisplayedRunRecap = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasCurrentRunRecap = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasRetainedRunRecap = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasTemplateLaunch = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bTemplateUsesReviewLane = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasActiveRun = false;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| bHasDisplayedRunRecap
|| bHasActiveRun;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardRunRecapDetailInspectSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LastCaseId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString TemplateTitle;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString TemplateModeLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString TemplateSelectionLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString TemplateAggregateFragment;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString TemplateAnalyticsDetailFragment;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString TemplateSelectionDetailFragment;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 AttemptCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 CompletedAttemptCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SuccessCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FailureCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 TimeoutCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 DNFCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 AbortedCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 BestTotalTimeMs = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 AverageTotalTimeMs = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 AverageRawSolveTimeMs = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 AverageInspectionTimeMs = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 TotalMistakes = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RecommendedCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FocusCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float SuccessRate = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasDisplayedRunRecap = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasCurrentRunRecap = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasTemplateLaunch = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bTemplateUsesReviewLane = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasTemplateSelectionDetail = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasTemplateAnalytics = false;
bool IsStructurallyValid() const
{
return !DetailLine.IsEmpty()
|| bHasDisplayedRunRecap
|| bHasTemplateLaunch;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardActiveAttemptTimerStatusInspectSurface
{

View file

@ -69,8 +69,8 @@ Current correction call from the source-exposed audit and current execution refr
- the latest landed bounded `Phase 4` packet closes the retained-idle comparison/outcome actionability gap at the end of the recognition-assisted coach-to-review continuity lane, and the final retained-surface confirmation pass now leaves that closure lane functionally complete
- the retained idle widget surface now keeps retained comparison and decision history inspectable without continuing to advertise live launch or guidance-outcome actions once the coach-to-review loop has already collapsed to truthful closed-loop idle
- the now-complete deliberate `UHyperTwistTrainingRuntimeLibrary` world-context consumer lane reaches through active generated-mode selector option owned-execution split-phase current-selection merged ordered roster phase-id lookup, normalized-order lookup, and evaluation-order lookup inspect surfaces, so gameplay/Blueprint callers can inspect one retained merged-roster row directly by phase id, normalized order, or evaluation order without scanning the merged roster array or re-running inspect derivation details directly
- the latest landed bounded `Phase 4` packet pivots deliberately into the next unsurfaced live dashboard presentation band and adds displayed method-drill diagnostics and displayed drill follow-up action inspect surfaces over already-cached method-drill diagnostic, follow-up template, follow-up deck, and retained follow-up-weighting state, so gameplay/Blueprint callers can inspect the exact displayed diagnostics lines and surfaced drill follow-up label without scraping widget text or reopening that derivation path
- the next best clean move is to stay on that same live dashboard consumer lane and expose the dedicated run-recap status/detail inspect surface over the current-or-retained recap presentation band
- the latest landed bounded `Phase 4` packet stays on that same live dashboard consumer lane and adds dedicated run-recap status and run-recap detail inspect surfaces over the current-or-retained recap presentation band, so gameplay/Blueprint callers can inspect the exact displayed recap lines plus the retained session/template context behind them without scraping widget text or reopening recap derivation
- the next best clean move is to stay adjacent in that same live dashboard consumer lane and expose the run-recap repeat/dismiss action control surface over the already-retained recap action labels and enablement posture
- the current execution-discipline rule that broad refactor / monolith-splitting work should not interrupt the active bounded roadmap packet unless structure is actually blocking it
## Donor portfolio phase taxonomy

View file

@ -0,0 +1,55 @@
# HyperTwist Phase 4 dashboard live run-recap status/detail 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 current-or-retained run-recap presentation directly to gameplay and Blueprint callers.
## Why this packet exists
`UHyperTwistCoachDashboardWidget` already displayed:
- the live run-recap status line
- the live run-recap detail line
- the retained current-run versus last-completed-run recap posture behind that band
But that recap band still only surfaced through widget text blocks. Callers still had to scrape UI text to understand which recap source is currently being shown, what completed-session payload is being summarized, and whether template-launch provenance is attached to that recap.
## What changed
- added `FHyperTwistTrainingCoachDashboardRunRecapStatusInspectSurface`
- added `FHyperTwistTrainingCoachDashboardRunRecapDetailInspectSurface`
- added `GetDisplayedRunRecapStatusInspectSurface()`
- added `GetDisplayedRunRecapDetailInspectSurface()`
- retained the exact displayed run-recap status/detail lines inside the widget so the new surfaces report the same live strings the dashboard is already presenting
- kept the packet bounded to `UHyperTwistCoachDashboardWidget` plus shared surface types, with no recap-persistence 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 live run-recap status line
- the exact displayed live run-recap detail line
- whether the recap is coming from the current completed/aborted run or the retained last-completed run
- the retained session summary metrics behind that band
- the attached template-launch provenance and template analytics fragments when that recap came from a template-backed launch
without scraping widget text or reopening run-recap derivation.
## 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 run-recap repeat/dismiss action control surface over the already-retained recap action labels and enablement posture.