Add native browser operator status surface

This commit is contained in:
axiomlogicnexus 2026-06-19 06:13:53 +00:00
parent 37eb22040c
commit 2392dfd5fa
10 changed files with 651 additions and 19 deletions

View file

@ -18,15 +18,82 @@ namespace HyperTwistBrowserWidgetInternal
{
return FPaths::Combine(FPaths::ProjectDir(), TEXT(".."), TEXT("Content"), TEXT("Browser"), TEXT("index.html"));
}
FString GetShellAuthorityFallback(const UHyperTwistBrowserWidget& BrowserWidget)
{
if (BrowserWidget.bUseBundledBrowserShell)
{
return TEXT("Content/Browser/index.html");
}
return BrowserWidget.InitialUrl.IsEmpty()
? TEXT("pending-shell-selection")
: BrowserWidget.InitialUrl;
}
FString MakeShellAuthorityLine(const FString& ShellAuthority)
{
return FString::Printf(TEXT("shell %s"), *ShellAuthority);
}
FString MakeQueueStatusLine(const int32 CommandCount, const int32 ShellStateCount)
{
return FString::Printf(
TEXT("queue commands %d | shell states %d"),
CommandCount,
ShellStateCount
);
}
FString MakeReadySummaryLine(const FString& RuntimeMode, const int32 AdapterCount)
{
return FString::Printf(TEXT("%s | adapters %d"), *RuntimeMode, AdapterCount);
}
FString MakeLiveSummaryLine(
const FString& RuntimeMode,
const FString& BridgeTrafficStatus,
const int32 AdapterCount
)
{
return FString::Printf(
TEXT("%s | %s | adapters %d"),
*RuntimeMode,
*BridgeTrafficStatus,
AdapterCount
);
}
FString MakeReadyBridgeStatusLine()
{
return TEXT("bridge waiting for live runtime-status envelope");
}
FString MakeWaitingBridgeStatusLine()
{
return TEXT("bridge waiting for runtime-ready");
}
FString MakeLiveBridgeStatusLine(
const int32 CommandReceiveCount,
const int32 ShellStateReceiveCount
)
{
return FString::Printf(
TEXT("bridge commands %d | shell states %d"),
CommandReceiveCount,
ShellStateReceiveCount
);
}
}
void UHyperTwistBrowserWidget::LoadBundledBrowserShell()
{
bUseBundledBrowserShell = true;
InitialUrl = ResolveBundledBrowserShellUrl();
ResetBrowserRuntimeHandshake();
if (BrowserWidget.IsValid())
{
ResetBrowserRuntimeHandshake();
BrowserWidget->LoadURL(InitialUrl);
}
}
@ -35,9 +102,13 @@ void UHyperTwistBrowserWidget::LoadBrowserUrl(const FString& NewUrl)
{
bUseBundledBrowserShell = false;
InitialUrl = NewUrl;
if (BrowserWidget.IsValid() && !NewUrl.IsEmpty())
if (!NewUrl.IsEmpty())
{
ResetBrowserRuntimeHandshake();
}
if (BrowserWidget.IsValid() && !NewUrl.IsEmpty())
{
BrowserWidget->LoadURL(NewUrl);
}
}
@ -120,6 +191,99 @@ bool UHyperTwistBrowserWidget::IsBrowserRuntimeReady() const
return bBrowserRuntimeReady;
}
FHyperTwistBrowserOperatorStatusSurface UHyperTwistBrowserWidget::GetBrowserOperatorStatusSurface() const
{
FHyperTwistBrowserOperatorStatusSurface StatusSurface;
StatusSurface.bBrowserRuntimeReady = bBrowserRuntimeReady;
StatusSurface.PendingCommandCount = PendingCommandJsonQueue.Num();
StatusSurface.PendingShellStateCount = bHasPendingShellStateJson ? 1 : 0;
FHyperTwistBrowserRuntimeReadyPayload RuntimeReadyPayload;
const bool bHasRuntimeReadyPayload =
BrowserBridgeObject != nullptr
&& BrowserBridgeObject->TryGetLastRuntimeReadyPayload(RuntimeReadyPayload);
StatusSurface.bHasRuntimeReadyPayload = bHasRuntimeReadyPayload;
FHyperTwistBrowserRuntimeStatusEnvelope RuntimeStatusEnvelope;
const bool bHasRuntimeStatusEnvelope =
BrowserBridgeObject != nullptr
&& BrowserBridgeObject->TryGetLastBrowserRuntimeStatusEnvelope(RuntimeStatusEnvelope);
StatusSurface.bHasRuntimeStatusEnvelope = bHasRuntimeStatusEnvelope;
if (bHasRuntimeStatusEnvelope)
{
const FHyperTwistBrowserRuntimeStatusSnapshot& RuntimeStatus = RuntimeStatusEnvelope.Status;
StatusSurface.RuntimeStageId = RuntimeStatus.RuntimeStatusId;
StatusSurface.RuntimeMode = RuntimeStatus.RuntimeMode;
StatusSurface.BridgeTrafficStatus = RuntimeStatus.BridgeTrafficStatus;
StatusSurface.AdapterCount = RuntimeStatus.AdapterCount;
StatusSurface.Headline = TEXT("Browser runtime live");
StatusSurface.SummaryLine = HyperTwistBrowserWidgetInternal::MakeLiveSummaryLine(
RuntimeStatus.RuntimeMode,
RuntimeStatus.BridgeTrafficStatus,
RuntimeStatus.AdapterCount
);
StatusSurface.QueueStatusLine = HyperTwistBrowserWidgetInternal::MakeQueueStatusLine(
RuntimeStatus.QueuedCommandCountAtRuntimeReady,
RuntimeStatus.QueuedShellStateCountAtRuntimeReady
);
StatusSurface.BridgeStatusLine = HyperTwistBrowserWidgetInternal::MakeLiveBridgeStatusLine(
RuntimeStatus.CommandReceiveCount,
RuntimeStatus.ShellStateReceiveCount
);
StatusSurface.ShellAuthorityLine = HyperTwistBrowserWidgetInternal::MakeShellAuthorityLine(
RuntimeStatus.ShellAuthority.IsEmpty()
? HyperTwistBrowserWidgetInternal::GetShellAuthorityFallback(*this)
: RuntimeStatus.ShellAuthority
);
return StatusSurface;
}
if (bHasRuntimeReadyPayload || bBrowserRuntimeReady)
{
const FString RuntimeMode =
(bHasRuntimeReadyPayload && !RuntimeReadyPayload.Mode.IsEmpty())
? RuntimeReadyPayload.Mode
: TEXT("ready");
const int32 AdapterCount = bHasRuntimeReadyPayload ? RuntimeReadyPayload.AdapterCount : 0;
StatusSurface.RuntimeStageId = bHasRuntimeReadyPayload
? RuntimeReadyPayload.Status
: TEXT("ready");
StatusSurface.RuntimeMode = RuntimeMode;
StatusSurface.AdapterCount = AdapterCount;
StatusSurface.Headline = TEXT("Browser runtime ready");
StatusSurface.SummaryLine = HyperTwistBrowserWidgetInternal::MakeReadySummaryLine(
RuntimeMode,
AdapterCount
);
StatusSurface.QueueStatusLine = HyperTwistBrowserWidgetInternal::MakeQueueStatusLine(
bHasRuntimeReadyPayload ? RuntimeReadyPayload.QueuedCommandCountAtRuntimeReady : StatusSurface.PendingCommandCount,
bHasRuntimeReadyPayload ? RuntimeReadyPayload.QueuedShellStateCountAtRuntimeReady : StatusSurface.PendingShellStateCount
);
StatusSurface.BridgeStatusLine = HyperTwistBrowserWidgetInternal::MakeReadyBridgeStatusLine();
StatusSurface.ShellAuthorityLine = HyperTwistBrowserWidgetInternal::MakeShellAuthorityLine(
(bHasRuntimeReadyPayload && !RuntimeReadyPayload.ShellAuthority.IsEmpty())
? RuntimeReadyPayload.ShellAuthority
: HyperTwistBrowserWidgetInternal::GetShellAuthorityFallback(*this)
);
return StatusSurface;
}
StatusSurface.RuntimeStageId = TEXT("waiting");
StatusSurface.RuntimeMode = TEXT("bootstrapping");
StatusSurface.Headline = TEXT("Browser runtime waiting");
StatusSurface.SummaryLine = TEXT("awaiting runtime-ready handshake");
StatusSurface.QueueStatusLine = HyperTwistBrowserWidgetInternal::MakeQueueStatusLine(
StatusSurface.PendingCommandCount,
StatusSurface.PendingShellStateCount
);
StatusSurface.BridgeStatusLine = HyperTwistBrowserWidgetInternal::MakeWaitingBridgeStatusLine();
StatusSurface.ShellAuthorityLine = HyperTwistBrowserWidgetInternal::MakeShellAuthorityLine(
HyperTwistBrowserWidgetInternal::GetShellAuthorityFallback(*this)
);
return StatusSurface;
}
bool UHyperTwistBrowserWidget::TryGetLastTilingEnvelope(
FHyperTwistTrainingTilingStateEnvelope& OutStateEnvelope
) const
@ -170,24 +334,13 @@ FString UHyperTwistBrowserWidget::ResolveBundledBrowserShellSourcePath()
TSharedRef<SWidget> UHyperTwistBrowserWidget::RebuildWidget()
{
if (BrowserBridgeObject == nullptr)
if (BrowserBridgeObject != nullptr && RuntimeReadyDelegateHandle.IsValid())
{
BrowserBridgeObject = NewObject<UHyperTwistBrowserBridgeObject>(this, TEXT("HyperTwistBrowserBridge"));
}
else
{
if (RuntimeReadyDelegateHandle.IsValid())
{
BrowserBridgeObject->OnRuntimeReady.Remove(RuntimeReadyDelegateHandle);
RuntimeReadyDelegateHandle.Reset();
}
BrowserBridgeObject->ResetReceivedMessages();
BrowserBridgeObject->OnRuntimeReady.Remove(RuntimeReadyDelegateHandle);
RuntimeReadyDelegateHandle.Reset();
}
RuntimeReadyDelegateHandle = BrowserBridgeObject->OnRuntimeReady.AddUObject(
this,
&UHyperTwistBrowserWidget::HandleBrowserRuntimeReady
);
EnsureBridgeObject();
ResetBrowserRuntimeHandshake();
BrowserWidget =
@ -313,6 +466,27 @@ void UHyperTwistBrowserWidget::QueueShellStateJson(const FString& StateJson)
void UHyperTwistBrowserWidget::ResetBrowserRuntimeHandshake()
{
bBrowserRuntimeReady = false;
if (BrowserBridgeObject != nullptr)
{
BrowserBridgeObject->ResetReceivedMessages();
}
}
void UHyperTwistBrowserWidget::EnsureBridgeObject()
{
if (BrowserBridgeObject == nullptr)
{
BrowserBridgeObject = NewObject<UHyperTwistBrowserBridgeObject>(this, TEXT("HyperTwistBrowserBridge"));
}
if (BrowserBridgeObject != nullptr && !RuntimeReadyDelegateHandle.IsValid())
{
RuntimeReadyDelegateHandle = BrowserBridgeObject->OnRuntimeReady.AddUObject(
this,
&UHyperTwistBrowserWidget::HandleBrowserRuntimeReady
);
}
}
FString UHyperTwistBrowserWidget::BuildCommandDispatchScript(const FString& CommandJson) const
@ -366,9 +540,26 @@ void UHyperTwistBrowserWidget::SetBrowserRuntimeDispatchReadyForTesting(const bo
ExecutedBrowserScriptsForTesting.Reset();
}
void UHyperTwistBrowserWidget::SimulateBrowserEnvelopeForTesting(const FString& EnvelopeJson)
{
EnsureBridgeObject();
if (BrowserBridgeObject != nullptr)
{
BrowserBridgeObject->NotifyEnvelope(EnvelopeJson);
}
}
void UHyperTwistBrowserWidget::SimulateBrowserRuntimeReadyForTesting(const FString& RuntimeReadyJson)
{
HandleBrowserRuntimeReady(RuntimeReadyJson);
EnsureBridgeObject();
if (BrowserBridgeObject != nullptr)
{
BrowserBridgeObject->NotifyRuntimeReady(RuntimeReadyJson);
}
else
{
HandleBrowserRuntimeReady(RuntimeReadyJson);
}
}
const TArray<FString>& UHyperTwistBrowserWidget::GetExecutedBrowserScriptsForTesting() const

View file

@ -8,6 +8,60 @@
class SWebBrowser;
USTRUCT(BlueprintType)
struct FHyperTwistBrowserOperatorStatusSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
FString SummaryLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
FString QueueStatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
FString BridgeStatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
FString ShellAuthorityLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
bool bBrowserRuntimeReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
bool bHasRuntimeReadyPayload = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
bool bHasRuntimeStatusEnvelope = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
FString RuntimeStageId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
FString RuntimeMode;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
FString BridgeTrafficStatus;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
int32 AdapterCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
int32 PendingCommandCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Browser")
int32 PendingShellStateCount = 0;
bool IsStructurallyValid() const
{
return !Headline.IsEmpty() && !SummaryLine.IsEmpty() && !QueueStatusLine.IsEmpty()
&& !BridgeStatusLine.IsEmpty() && !ShellAuthorityLine.IsEmpty();
}
};
UCLASS(BlueprintType)
class UNREALHYPERTWIST_API UHyperTwistBrowserWidget : public UWidget
{
@ -56,6 +110,9 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Browser")
bool IsBrowserRuntimeReady() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Browser")
FHyperTwistBrowserOperatorStatusSurface GetBrowserOperatorStatusSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Browser")
bool TryGetLastTilingEnvelope(
FHyperTwistTrainingTilingStateEnvelope& OutStateEnvelope
@ -95,6 +152,7 @@ private:
void QueueCommandJson(const FString& CommandJson);
void QueueShellStateJson(const FString& StateJson);
void ResetBrowserRuntimeHandshake();
void EnsureBridgeObject();
FString BuildCommandDispatchScript(const FString& CommandJson) const;
FString BuildShellStateDispatchScript(const FString& StateJson) const;
static FString EscapeForJavaScriptSingleQuotedString(const FString& Input);
@ -122,6 +180,7 @@ private:
#if WITH_AUTOMATION_TESTS
public:
void SetBrowserRuntimeDispatchReadyForTesting(bool bInDispatchReady);
void SimulateBrowserEnvelopeForTesting(const FString& EnvelopeJson);
void SimulateBrowserRuntimeReadyForTesting(const FString& RuntimeReadyJson);
const TArray<FString>& GetExecutedBrowserScriptsForTesting() const;

View file

@ -452,6 +452,248 @@ bool FHyperTwistBrowserWidgetBundledShellUrlTest::RunTest(const FString& Paramet
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistBrowserWidgetOperatorStatusSurfaceWaitingTest,
"HyperTwist.Browser.Widget.OperatorStatusSurfaceWaiting",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistBrowserWidgetOperatorStatusSurfaceWaitingTest::RunTest(
const FString& Parameters
)
{
UHyperTwistBrowserWidget* BrowserWidget = NewObject<UHyperTwistBrowserWidget>();
TestNotNull(TEXT("The browser widget must be constructible."), BrowserWidget);
if (BrowserWidget == nullptr)
{
return false;
}
BrowserWidget->DispatchCommandJson(TEXT("{\"command\":\"alpha\"}"));
BrowserWidget->PushShellStateJson(TEXT("{\"phase\":\"8A\"}"));
const FHyperTwistBrowserOperatorStatusSurface StatusSurface =
BrowserWidget->GetBrowserOperatorStatusSurface();
TestTrue(
TEXT("The native browser operator status surface must stay structurally valid while waiting for runtime-ready."),
StatusSurface.IsStructurallyValid()
);
TestEqual(
TEXT("The waiting surface must advertise the waiting headline."),
StatusSurface.Headline,
TEXT("Browser runtime waiting")
);
TestEqual(
TEXT("The waiting surface must preserve the queued command count."),
StatusSurface.PendingCommandCount,
1
);
TestEqual(
TEXT("The waiting surface must preserve the queued shell-state count."),
StatusSurface.PendingShellStateCount,
1
);
TestTrue(
TEXT("The waiting surface must explain that runtime-ready is still pending."),
StatusSurface.BridgeStatusLine.Contains(TEXT("waiting for runtime-ready"))
);
TestTrue(
TEXT("The waiting surface must project queue counts for operator review."),
StatusSurface.QueueStatusLine.Contains(TEXT("commands 1"))
&& StatusSurface.QueueStatusLine.Contains(TEXT("shell states 1"))
);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistBrowserWidgetOperatorStatusSurfaceLiveTest,
"HyperTwist.Browser.Widget.OperatorStatusSurfaceLive",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistBrowserWidgetOperatorStatusSurfaceLiveTest::RunTest(const FString& Parameters)
{
UHyperTwistBrowserWidget* BrowserWidget = NewObject<UHyperTwistBrowserWidget>();
TestNotNull(TEXT("The browser widget must be constructible."), BrowserWidget);
if (BrowserWidget == nullptr)
{
return false;
}
BrowserWidget->SetBrowserRuntimeDispatchReadyForTesting(true);
BrowserWidget->SimulateBrowserRuntimeReadyForTesting(
TEXT("{\"status\":\"ready\",\"runtime\":\"hypertwist-browser-runtime\",")
TEXT("\"mode\":\"bundled-module\",\"adapterCount\":21,")
TEXT("\"shellAuthority\":\"Content/Browser/index.html\",")
TEXT("\"queuedCommandCountAtRuntimeReady\":2,")
TEXT("\"queuedShellStateCountAtRuntimeReady\":1}")
);
const FHyperTwistBrowserOperatorStatusSurface ReadyStatusSurface =
BrowserWidget->GetBrowserOperatorStatusSurface();
TestEqual(
TEXT("The operator surface must first expose the runtime-ready stage before live status arrives."),
ReadyStatusSurface.Headline,
TEXT("Browser runtime ready")
);
TestTrue(
TEXT("The runtime-ready stage must surface retained startup queue carryover from the typed ready payload."),
ReadyStatusSurface.QueueStatusLine.Contains(TEXT("commands 2"))
&& ReadyStatusSurface.QueueStatusLine.Contains(TEXT("shell states 1"))
);
TestTrue(
TEXT("The runtime-ready stage must explain that live runtime-status is still pending."),
ReadyStatusSurface.BridgeStatusLine.Contains(TEXT("waiting for live runtime-status envelope"))
);
BrowserWidget->SimulateBrowserEnvelopeForTesting(
TEXT("{\"type\":\"browser-runtime-status\",\"source\":\"browser-runtime-shell\",")
TEXT("\"status\":{\"runtimeStatusId\":\"runtime-ready\",\"runtimeMode\":\"bundled-module\",")
TEXT("\"bootstrapStartedAtUtc\":\"2026-06-19T04:30:00Z\",")
TEXT("\"runtimeReadyAtUtc\":\"2026-06-19T04:31:00Z\",")
TEXT("\"shellAuthority\":\"Content/Browser/index.html\",")
TEXT("\"adapterCount\":21,")
TEXT("\"queuedCommandCountAtRuntimeReady\":2,")
TEXT("\"queuedShellStateCountAtRuntimeReady\":1,")
TEXT("\"commandReceiveCount\":3,")
TEXT("\"shellStateReceiveCount\":2,")
TEXT("\"lastCommandReceivedAtUtc\":\"2026-06-19T04:31:10Z\",")
TEXT("\"lastShellStateReceivedAtUtc\":\"2026-06-19T04:31:11Z\",")
TEXT("\"bridgeTrafficStatus\":\"shell-state-live\",")
TEXT("\"fallbackReason\":null}}")
);
const FHyperTwistBrowserOperatorStatusSurface StatusSurface =
BrowserWidget->GetBrowserOperatorStatusSurface();
TestTrue(
TEXT("The native browser operator status surface must stay structurally valid once live runtime status is available."),
StatusSurface.IsStructurallyValid()
);
TestEqual(
TEXT("The live surface must advertise the live headline."),
StatusSurface.Headline,
TEXT("Browser runtime live")
);
TestTrue(
TEXT("The live surface must report the runtime-ready and runtime-status payloads as present."),
StatusSurface.bBrowserRuntimeReady
&& StatusSurface.bHasRuntimeReadyPayload
&& StatusSurface.bHasRuntimeStatusEnvelope
);
TestEqual(
TEXT("The live surface must preserve the runtime mode."),
StatusSurface.RuntimeMode,
TEXT("bundled-module")
);
TestEqual(
TEXT("The live surface must preserve the adapter count."),
StatusSurface.AdapterCount,
21
);
TestTrue(
TEXT("The live surface must summarize bridge traffic state."),
StatusSurface.SummaryLine.Contains(TEXT("shell-state-live"))
);
TestTrue(
TEXT("The live surface must surface retained startup queue carryover."),
StatusSurface.QueueStatusLine.Contains(TEXT("commands 2"))
&& StatusSurface.QueueStatusLine.Contains(TEXT("shell states 1"))
);
TestTrue(
TEXT("The live surface must surface the command and shell-state receive counts."),
StatusSurface.BridgeStatusLine.Contains(TEXT("commands 3"))
&& StatusSurface.BridgeStatusLine.Contains(TEXT("shell states 2"))
);
TestTrue(
TEXT("The live surface must preserve the shell authority line."),
StatusSurface.ShellAuthorityLine.Contains(TEXT("Content/Browser/index.html"))
);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistBrowserWidgetOperatorStatusSurfaceResetOnShellChangeTest,
"HyperTwist.Browser.Widget.OperatorStatusSurfaceResetOnShellChange",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistBrowserWidgetOperatorStatusSurfaceResetOnShellChangeTest::RunTest(
const FString& Parameters
)
{
UHyperTwistBrowserWidget* BrowserWidget = NewObject<UHyperTwistBrowserWidget>();
TestNotNull(TEXT("The browser widget must be constructible."), BrowserWidget);
if (BrowserWidget == nullptr)
{
return false;
}
BrowserWidget->SetBrowserRuntimeDispatchReadyForTesting(true);
BrowserWidget->DispatchCommandJson(TEXT("{\"command\":\"alpha\"}"));
BrowserWidget->PushShellStateJson(TEXT("{\"phase\":\"8A\"}"));
BrowserWidget->SimulateBrowserRuntimeReadyForTesting(
TEXT("{\"status\":\"ready\",\"runtime\":\"hypertwist-browser-runtime\",")
TEXT("\"mode\":\"bundled-module\",\"adapterCount\":21,")
TEXT("\"shellAuthority\":\"Content/Browser/index.html\",")
TEXT("\"queuedCommandCountAtRuntimeReady\":1,")
TEXT("\"queuedShellStateCountAtRuntimeReady\":1}")
);
BrowserWidget->SimulateBrowserEnvelopeForTesting(
TEXT("{\"type\":\"browser-runtime-status\",\"source\":\"browser-runtime-shell\",")
TEXT("\"status\":{\"runtimeStatusId\":\"runtime-ready\",\"runtimeMode\":\"bundled-module\",")
TEXT("\"bootstrapStartedAtUtc\":\"2026-06-19T04:30:00Z\",")
TEXT("\"runtimeReadyAtUtc\":\"2026-06-19T04:31:00Z\",")
TEXT("\"shellAuthority\":\"Content/Browser/index.html\",")
TEXT("\"adapterCount\":21,")
TEXT("\"queuedCommandCountAtRuntimeReady\":1,")
TEXT("\"queuedShellStateCountAtRuntimeReady\":1,")
TEXT("\"commandReceiveCount\":3,")
TEXT("\"shellStateReceiveCount\":2,")
TEXT("\"lastCommandReceivedAtUtc\":\"2026-06-19T04:31:10Z\",")
TEXT("\"lastShellStateReceivedAtUtc\":\"2026-06-19T04:31:11Z\",")
TEXT("\"bridgeTrafficStatus\":\"shell-state-live\",")
TEXT("\"fallbackReason\":null}}")
);
const FHyperTwistBrowserOperatorStatusSurface LiveStatusSurface =
BrowserWidget->GetBrowserOperatorStatusSurface();
TestEqual(
TEXT("The setup seam must reach the live operator surface before the reset path is exercised."),
LiveStatusSurface.Headline,
TEXT("Browser runtime live")
);
BrowserWidget->LoadBrowserUrl(TEXT("https://example.invalid/hypertwist"));
const FHyperTwistBrowserOperatorStatusSurface ResetStatusSurface =
BrowserWidget->GetBrowserOperatorStatusSurface();
TestEqual(
TEXT("Changing shell authority must reset the operator surface back to waiting."),
ResetStatusSurface.Headline,
TEXT("Browser runtime waiting")
);
TestFalse(
TEXT("Changing shell authority must clear retained runtime-ready and runtime-status ownership."),
ResetStatusSurface.bBrowserRuntimeReady
|| ResetStatusSurface.bHasRuntimeReadyPayload
|| ResetStatusSurface.bHasRuntimeStatusEnvelope
);
TestTrue(
TEXT("Changing shell authority must switch the shell authority line to the newly requested shell."),
ResetStatusSurface.ShellAuthorityLine.Contains(TEXT("https://example.invalid/hypertwist"))
);
TestTrue(
TEXT("Changing shell authority must keep the waiting explanation intact after clearing stale live state."),
ResetStatusSurface.BridgeStatusLine.Contains(TEXT("waiting for runtime-ready"))
);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistBrowserWidgetAuthoritativeShellArtifactsTest,
"HyperTwist.Browser.Widget.AuthoritativeShellArtifacts",

View file

@ -0,0 +1,111 @@
# HyperTwist Phase 8A browser native operator-status surface implementation packet
Created on `2026-06-19`
## Status
- first-party HyperTwist packet
- bounded first-party `Phase 8A` continuation slice
## Purpose
This packet hardens the already-landed embedded browser shipping lane at the
native/operator-facing status surface that sits above the browser bridge.
The landed slice is:
- add a typed first-party `FHyperTwistBrowserOperatorStatusSurface` projection
in `UHyperTwistBrowserWidget`
- consume the already-landed typed `hypertwist-runtime-ready` and
`browser-runtime-status` ownership seams to produce native waiting, ready,
and live operator status stages
- surface queue carryover, bridge receive counts, runtime mode, adapter count,
and shell authority through a compact Unreal-owned status contract
- clear retained runtime-ready and runtime-status ownership whenever shell
authority changes so stale live state cannot survive a shell reload
- widen focused browser automation with waiting, live, and reset coverage on
the primary reverse-SSH Windows lane
It is not:
- a topology widening into the optional full-browser client branch
- a new browser transport vocabulary packet
- a native `MagicTile` renderer-port packet
## Current authority basis
This implementation packet stands on:
- `docs/ops/HYPERTWIST_COMPREHENSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md`
- `docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md`
- `docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md`
- `docs/v6_5_deep_manual_pack/HyperTwist/ARCHITECTURE.md`
- `docs/arch/HYPERTWIST_PHASE8A_BROWSER_RUNTIME_STATUS_NATIVE_CAPTURE_IMPLEMENTATION_PACKET_2026-06-19.md`
- `docs/arch/HYPERTWIST_PHASE8A_BROWSER_RUNTIME_READY_NATIVE_CAPTURE_IMPLEMENTATION_PACKET_2026-06-19.md`
## Landed scope
Current code now hardens the live embedded-browser lane through:
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistBrowser/HyperTwistBrowserWidget.h`
typed native `FHyperTwistBrowserOperatorStatusSurface` ownership
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBrowser/HyperTwistBrowserWidget.cpp`
native waiting, ready, and live status projection from the retained typed
browser-runtime caches plus explicit cache clearing on shell-authority change
- focused browser automation in
`UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp`
for the new:
- `Widget.OperatorStatusSurfaceWaiting`
- `Widget.OperatorStatusSurfaceLive`
- `Widget.OperatorStatusSurfaceResetOnShellChange`
## Why this hardening mattered
The browser shell already had a compact runtime-health status surface, and
Unreal already had typed native ownership for the adjacent ready and live
runtime packets.
What still remained weaker was the operator-facing native projection of those
facts. Without this slice, the host still lacked a compact Unreal-owned status
surface for waiting, ready, and live stages, and shell changes could leave
stale retained runtime facts visible after reload.
This packet closes that gap without widening topology.
## Validation
Local validation:
- `git diff --check`
- result on `2026-06-19`: passed
Windows build validation:
- `Build.bat UnrealHyperTwistEditor Win64 Development -Project='C:\HyperTwist_worktrees\phase10validate\UnrealHyperTwist\UnrealHyperTwist.uproject' -WaitMutex -NoHotReloadFromIDE -NoUba`
- result on `2026-06-19`: `Result: Succeeded` with UnrealBuildTool
`Total execution time: 59.53 seconds` on isolated worktree
`C:\HyperTwist_worktrees\phase10validate`
Focused browser validation:
- `Automation RunTests HyperTwist.Browser`
- result on `2026-06-19`: report `index.json` exported to
`Saved\AutomationReports\Browser-NativeOperatorStatusSurface-Reset-Verify`
with `12` `HyperTwist.Browser.*` tests succeeded, `0` failed, including:
- `Bridge.RuntimeReadyPayloadDecode`
- `Bridge.RuntimeStatusEnvelopeDecode`
- `Widget.OperatorStatusSurfaceWaiting`
- `Widget.OperatorStatusSurfaceLive`
- `Widget.OperatorStatusSurfaceResetOnShellChange`
## Product effect
This packet keeps the live embedded browser/CEF shipping lane intact, but it
upgrades the native operator posture:
- Unreal now has a compact typed browser runtime operator surface instead of
depending on raw JSON or browser-only status rendering
- the same typed authority now drives both the browser-side runtime status
presentation and the native-side operator summary
- changing shell authority now clears retained runtime ownership before the
next handshake, so stale ready/live state does not survive reloads

View file

@ -99,3 +99,10 @@ upgrades the original handshake seam:
without reparsing ad hoc strings
- queue flush behavior remains unchanged while the authority seam underneath it
becomes more explicit
Follow-on continuation:
- `docs/arch/HYPERTWIST_PHASE8A_BROWSER_NATIVE_OPERATOR_STATUS_SURFACE_IMPLEMENTATION_PACKET_2026-06-19.md`
now records the next hardening slice that consumes both the typed
runtime-ready and runtime-status seams from native code and clears stale
retained runtime ownership on shell-authority change

View file

@ -429,6 +429,8 @@ Closure read:
- Validation evidence on `2026-06-19`: the same primary reverse-SSH `localhost:22022` lane rebuilt isolated worktree `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded` and UnrealBuildTool `Total execution time: 111.69 seconds`, then `Automation RunTests HyperTwist.Browser` found `8` `HyperTwist.Browser.*` tests and passed all `8` at `Saved\AutomationReports\Browser-RuntimeStatusNativeCapture-Verify`
- Runtime-ready continuation on `2026-06-19`: current code now closes the adjacent handshake gap as well by decoding first-party `hypertwist-runtime-ready` payloads into typed Unreal-owned structs, retaining the last valid runtime-ready payload through `UHyperTwistBrowserBridgeObject` and `UHyperTwistBrowserWidget`, and keeping the existing browser-ready queue-flush behavior intact
- Validation evidence on `2026-06-19`: the same primary reverse-SSH `localhost:22022` lane rebuilt isolated worktree `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded` and UnrealBuildTool `Total execution time: 88.20 seconds`, then `Automation RunTests HyperTwist.Browser` found `9` `HyperTwist.Browser.*` tests and passed all `9` at `Saved\AutomationReports\Browser-RuntimeReadyPayload-Verify`
- Native operator-surface continuation on `2026-06-19`: current code now projects a compact native/operator-facing browser runtime status surface from the typed Unreal-owned runtime-ready and runtime-status caches, exposing waiting/ready/live stages, queue carryover, bridge receive counts, runtime mode, adapter count, and shell authority while clearing retained runtime ownership whenever shell authority changes so stale live state cannot survive a browser reload
- Validation evidence on `2026-06-19`: the same primary reverse-SSH `localhost:22022` lane rebuilt isolated worktree `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded` and UnrealBuildTool `Total execution time: 59.53 seconds`, then `Automation RunTests HyperTwist.Browser` exported `Saved\AutomationReports\Browser-NativeOperatorStatusSurface-Reset-Verify\index.json` with `12` `HyperTwist.Browser.*` tests succeeded and `0` failed, including `Widget.OperatorStatusSurfaceWaiting`, `Widget.OperatorStatusSurfaceLive`, and `Widget.OperatorStatusSurfaceResetOnShellChange`
### 8B — State Synchronization
- [x] Bridge UE cube state to browser via embedded Unreal bridge object plus JavaScript `postMessage` fallback

View file

@ -141,6 +141,10 @@ Use these as the current governing docs:
`hypertwist-runtime-ready` capture seam through the browser bridge/widget,
keeping the existing queue-flush behavior intact while lifting the original
runtime-ready handshake out of raw JSON-only handling
- that same shipping/browser posture now also has a compact native/operator-facing
waiting/ready/live status surface built from those typed ready/status seams,
and changing shell authority now clears retained runtime ownership so stale
live state does not survive a reload
- `MagicTile` `Phase 7C` is now landed separately through the bundled tiling native-behavior
proof contract/probe seam, runtime-library proof helpers, and focused Windows validation on
isolated worktree `C:\HyperTwist_worktrees\phase10validate`

View file

@ -111,6 +111,12 @@ native browser bridge now also decodes and retains the typed first-party
authority seam is no longer raw JSON only even though the existing queue flush
behavior remains unchanged.
That same shipping lane now also projects a compact native/operator-facing
waiting, ready, and live status surface from those retained typed caches,
including queue carryover, bridge receive counts, runtime mode, adapter count,
and shell authority, and clears retained runtime ownership whenever shell
authority changes so stale live state cannot survive a shell reload.
The optional full-browser client path now exists only as a spec-defined
first-party branch:

View file

@ -144,7 +144,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice.
| First-party provider-neutral custom-endpoint runtime routing | Implemented now | landed first-party `Phase 6R-AI` | First-party per-session provider-profile endpoint selection, runtime routing, fallback-health reflection, and bounded provider-backed transport-failure posture are now live above the landed provider-profile/BYOK and provider-routing seams. Provider-specific overlays, payment execution, provider-portal ownership, and payload shipping remain deferred. |
| Analytics/reporting surfaces | Implemented now | landed analytics/reporting packets | Reporting is real, but bounded to accepted retained slices. |
| Rewritten training analytics and report reference grounding | Implemented now | `apache/echarts` retained permissive lane + first-party current code | Current live `Training Analytics` reference side includes four rewritten first-party targets grounded in retained `apache/echarts`: session outcome and progress reporting, analytics data-view and export, timing-trend history and overview interaction, and the optional richer explainer or sidecar boundary. This does not displace the landed `Phase 3R-C` first-party analytics/reporting owner or elevate `ecomfe/echarts-gl` and `ecomfe/zrender` beyond support-only sidecars. |
| Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, and the current Phase 1 browser-runtime landing now includes a first-party authoritative `Content/Browser/index.html` shell, bundled-runtime upgrade path, committed clean-checkout plain-JS fallback runtime, embedded `UHyperTwistBrowserWidget` bridge, the `2026-06-19` runtime-ready queue hardening that retains outbound Unreal shell traffic until the browser runtime is ready, a same-day first-party `Browser Runtime Status` surface with shared boot-state ownership across bundled and fallback shells, a follow-on typed Unreal-side `browser-runtime-status` capture seam that retains the last valid runtime snapshot across unrelated later envelope traffic until explicit reset, and a second follow-on typed Unreal-side `hypertwist-runtime-ready` capture seam that raises the original handshake payload out of raw JSON-only handling without changing queue flush behavior. This still is not proof of unlimited browser-shell parity. |
| Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, and the current Phase 1 browser-runtime landing now includes a first-party authoritative `Content/Browser/index.html` shell, bundled-runtime upgrade path, committed clean-checkout plain-JS fallback runtime, embedded `UHyperTwistBrowserWidget` bridge, the `2026-06-19` runtime-ready queue hardening that retains outbound Unreal shell traffic until the browser runtime is ready, a same-day first-party `Browser Runtime Status` surface with shared boot-state ownership across bundled and fallback shells, a follow-on typed Unreal-side `browser-runtime-status` capture seam that retains the last valid runtime snapshot across unrelated later envelope traffic until explicit reset, a second follow-on typed Unreal-side `hypertwist-runtime-ready` capture seam that raises the original handshake payload out of raw JSON-only handling without changing queue flush behavior, and a third same-day native/operator-facing status surface that consumes those typed seams inside Unreal while clearing retained runtime ownership on shell-authority change. This still is not proof of unlimited browser-shell parity. |
| Optional full-browser client path above Unreal backend authority | Deep-source grounded retained | landed first-party `Phase 8B` preparation packet + spec/evidence continuation | This branch remains a spec-only first-party option: a browser-owned rendering and input shell may sit above explicit Unreal backend seams while Unreal remains authoritative for puzzle, training, timer, replay, and persistence state. The current continuation now fixes the allowed seam families to catalog, session, action, state, continuity push, replay, persistence status, and runtime health, while keeping offline-first, public-hosted, and native renderer widening posture deferred. The landed embedded browser/CEF shell remains the current shipping posture, and this branch stays separate from any native `MagicTile` behavior or renderer widening. |
| Rewritten browser spatial scene and renderer reference grounding | Implemented now | `mrdoob/three.js` retained permissive lane + first-party current code | Current live `Browser 3D and XR Support` reference side includes one rewritten first-party target grounded in retained `mrdoob/three.js`: the browser spatial scene and renderer contract. This does not displace the landed `Phase 3R-F` first-party browser spatial owner trio or absorb the adjacent `react-three-fiber` renderer-bridge and `xr` session slices. |
| Rewritten React-side browser renderer and event-bridge grounding | Implemented now | `pmndrs/react-three-fiber` retained permissive lane + first-party current code | Current live `Browser 3D and XR Support` reference side includes one rewritten first-party target grounded in retained `react-three-fiber`: the React scene renderer and event bridge. This does not displace the landed `Phase 3R-F` first-party browser spatial owner trio or absorb the adjacent `three.js` scene substrate and `xr` session slices. |

View file

@ -135,6 +135,16 @@ Current consolidated milestone snapshot:
of raw JSON-only handling, with refreshed primary-lane `localhost:22022`
validation at `9` green `HyperTwist.Browser.*` tests including the new
`Bridge.RuntimeReadyPayloadDecode`
- that same embedded-browser lane then gained a compact native/operator-facing
waiting/ready/live status surface on `2026-06-19`, consuming the typed
runtime-ready and runtime-status seams inside Unreal, surfacing queue
carryover plus bridge receive counts, and clearing retained runtime
ownership whenever shell authority changes so stale live state cannot
survive a reload, with refreshed primary-lane `localhost:22022` validation
at `12` green `HyperTwist.Browser.*` tests including
`Widget.OperatorStatusSurfaceWaiting`,
`Widget.OperatorStatusSurfaceLive`, and
`Widget.OperatorStatusSurfaceResetOnShellChange`
- classic-cube `Phase 9A` replay recording is now closed through first-party
runtime capture, `.json` replay persistence, local playback reconstruction,
schema-light replay normalization across save/load/viewer import, and live