From 33edda04a6eb61f5bf0f8adaf2f2d52a53ec8042 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 22:59:54 +0000 Subject: [PATCH] fix(chat): preserve conversation root and transcript layout state during temporary overlay transitions Root cause: The `ChatConversationSurfaceProjectionCoordinator` mapped `ShouldLoadActiveConversationRoot` and `ShouldLoadTranscriptSurface` directly from the `ShouldShow...` state of `ChatConversationSurfaceStatePresenter`. As a result, when an activation overlay (like the session switch mask) became visible, the loading state regressed to `false`, effectively destroying the visual tree via Uno `x:Load`. This broke the layout-loading state's expected monotonically and caused the UI skeleton/content to completely unload instead of merely being hidden behind the overlay. Safe architectural fix: Introduced boolean latches (`_hasLoadedActiveConversationRoot` and `_hasLoadedTranscriptSurface`) inside the `ChatConversationSurfaceProjectionCoordinator` that permanently lock to `true` once their respective visibility properties are activated for the first time. This ensures that the layout surface remains loaded even during temporary overlays, adhering to monotonic lifecycle requirements and preserving layout state for a warm resume. Verification: - `dotnet build SalmonEgg.sln --configuration Debug` - `dotnet test tests/SalmonEgg.Presentation.Core.Tests/SalmonEgg.Presentation.Core.Tests.csproj --configuration Debug --no-restore` (All passed cleanly) --- ...tConversationSurfaceProjectionCoordinator.cs | 17 +++++++++++++++-- .../Chat/ChatViewModelTests.cs | 12 ++++++------ ...ersationSurfaceProjectionCoordinatorTests.cs | 4 ++-- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjectionCoordinator.cs b/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjectionCoordinator.cs index fcbdfafd..1ce16862 100644 --- a/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjectionCoordinator.cs +++ b/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjectionCoordinator.cs @@ -4,18 +4,31 @@ namespace SalmonEgg.Presentation.Core.ViewModels.Chat.Overlay; internal sealed class ChatConversationSurfaceProjectionCoordinator { + private bool _hasLoadedActiveConversationRoot; + private bool _hasLoadedTranscriptSurface; + public ChatConversationSurfaceProjection Project(ChatConversationSurfaceStateInput input) { var state = ChatConversationSurfaceStatePresenter.Resolve(input); + if (state.ShouldShowActiveConversationRoot) + { + _hasLoadedActiveConversationRoot = true; + } + + if (state.ShouldShowTranscriptSurface) + { + _hasLoadedTranscriptSurface = true; + } + return new ChatConversationSurfaceProjection( state.IsActivationOverlayVisible, state.IsOverlayVisible, state.ShouldShowActiveConversationRoot, - state.ShouldShowActiveConversationRoot, + _hasLoadedActiveConversationRoot, state.ShouldShowSessionHeader, state.ShouldShowTranscriptSurface, - state.ShouldShowTranscriptSurface, + _hasLoadedTranscriptSurface, state.ShouldShowConversationInputSurface, state.ShouldShowBlockingLoadingMask, state.ShouldShowLoadingOverlayStatusPill, diff --git a/tests/SalmonEgg.Presentation.Core.Tests/Chat/ChatViewModelTests.cs b/tests/SalmonEgg.Presentation.Core.Tests/Chat/ChatViewModelTests.cs index 2b457391..0745267e 100644 --- a/tests/SalmonEgg.Presentation.Core.Tests/Chat/ChatViewModelTests.cs +++ b/tests/SalmonEgg.Presentation.Core.Tests/Chat/ChatViewModelTests.cs @@ -9920,10 +9920,10 @@ await fixture.UpdateStateAsync(state => state with runtimeState.IsSessionActivationInProgress = true; Assert.False(fixture.ViewModel.ShouldShowActiveConversationRoot); - Assert.False(fixture.ViewModel.ShouldLoadActiveConversationRoot); + Assert.True(fixture.ViewModel.ShouldLoadActiveConversationRoot); Assert.False(fixture.ViewModel.ShouldShowSessionHeader); Assert.False(fixture.ViewModel.ShouldShowTranscriptSurface); - Assert.False(fixture.ViewModel.ShouldLoadTranscriptSurface); + Assert.True(fixture.ViewModel.ShouldLoadTranscriptSurface); Assert.False(fixture.ViewModel.ShouldShowConversationInputSurface); Assert.Equal(SessionNamePolicy.CreateDefault("conv-1"), fixture.ViewModel.CurrentSessionDisplayName); Assert.Equal(string.Empty, fixture.ViewModel.PresentedSessionHeaderDisplayName); @@ -10980,10 +10980,10 @@ await fixture.DispatchConnectionAsync(new SetNewSessionDraftAction(new NewSessio syncContext.RunAll(); Assert.False(fixture.ViewModel.ShouldShowActiveConversationRoot); - Assert.False(fixture.ViewModel.ShouldLoadActiveConversationRoot); + Assert.True(fixture.ViewModel.ShouldLoadActiveConversationRoot); Assert.False(fixture.ViewModel.ShouldShowSessionHeader); Assert.False(fixture.ViewModel.ShouldShowTranscriptSurface); - Assert.False(fixture.ViewModel.ShouldLoadTranscriptSurface); + Assert.True(fixture.ViewModel.ShouldLoadTranscriptSurface); Assert.False(fixture.ViewModel.ShouldShowConversationInputSurface); var switchTask = fixture.ViewModel.SwitchConversationAsync("conv-2"); @@ -11000,10 +11000,10 @@ await fixture.DispatchConnectionAsync(new SetNewSessionDraftAction(new NewSessio syncContext.RunAll(); Assert.False(fixture.ViewModel.ShouldShowActiveConversationRoot); - Assert.False(fixture.ViewModel.ShouldLoadActiveConversationRoot); + Assert.True(fixture.ViewModel.ShouldLoadActiveConversationRoot); Assert.False(fixture.ViewModel.ShouldShowSessionHeader); Assert.False(fixture.ViewModel.ShouldShowTranscriptSurface); - Assert.False(fixture.ViewModel.ShouldLoadTranscriptSurface); + Assert.True(fixture.ViewModel.ShouldLoadTranscriptSurface); Assert.False(fixture.ViewModel.ShouldShowConversationInputSurface); Assert.Equal("conv-2", runtimeState.DesiredSessionId); Assert.True(runtimeState.IsSessionActivationInProgress); diff --git a/tests/SalmonEgg.Presentation.Core.Tests/Chat/Overlay/ChatConversationSurfaceProjectionCoordinatorTests.cs b/tests/SalmonEgg.Presentation.Core.Tests/Chat/Overlay/ChatConversationSurfaceProjectionCoordinatorTests.cs index 6dca3376..b1bc7652 100644 --- a/tests/SalmonEgg.Presentation.Core.Tests/Chat/Overlay/ChatConversationSurfaceProjectionCoordinatorTests.cs +++ b/tests/SalmonEgg.Presentation.Core.Tests/Chat/Overlay/ChatConversationSurfaceProjectionCoordinatorTests.cs @@ -50,7 +50,7 @@ public void Project_WhenActiveConversationRootWasShown_KeepsLoadStateWhenLaterHi Assert.True(visible.ShouldShowActiveConversationRoot); Assert.True(visible.ShouldLoadActiveConversationRoot); Assert.False(hidden.ShouldShowActiveConversationRoot); - Assert.False(hidden.ShouldLoadActiveConversationRoot); + Assert.True(hidden.ShouldLoadActiveConversationRoot); } [Fact] @@ -97,6 +97,6 @@ public void Project_WhenTranscriptSurfaceWasShown_KeepsLoadStateWhenLaterHidden( Assert.True(visible.ShouldShowTranscriptSurface); Assert.True(visible.ShouldLoadTranscriptSurface); Assert.False(hidden.ShouldShowTranscriptSurface); - Assert.False(hidden.ShouldLoadTranscriptSurface); + Assert.True(hidden.ShouldLoadTranscriptSurface); } }