From 0ee5ec7d0037f2c30b4b0c7ef51bf370b96b5d16 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:16:10 +0000 Subject: [PATCH 1/2] fix(ui): make ConversationInputSurface load state monotonic The ConversationInputSurface was relying solely on `Visibility` without an `x:Load` optimization like other major surfaces in ChatView. This could lead to either unexpected pre-loading or loss of layout state when a blocking overlay temporarily hides the content. This adds `ShouldLoadConversationInputSurface` to the `ChatConversationSurfaceProjection` record struct, coordinates it monotonically in `ChatConversationSurfaceProjectionCoordinator`, exposes it in `ChatViewModel`, and binds it via `x:Load` in `ChatView.xaml`. --- .../Presentation/Views/Chat/ChatView.xaml | 1 + global.json | 2 +- .../ViewModels/Chat/ChatViewModel.cs | 3 ++ .../ChatConversationSurfaceProjection.cs | 1 + ...onversationSurfaceProjectionCoordinator.cs | 3 ++ ...sationSurfaceProjectionCoordinatorTests.cs | 47 +++++++++++++++++++ .../Ui/XamlComplianceTests.cs | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/SalmonEgg/SalmonEgg/Presentation/Views/Chat/ChatView.xaml b/SalmonEgg/SalmonEgg/Presentation/Views/Chat/ChatView.xaml index 797aa35d..3f7739c2 100644 --- a/SalmonEgg/SalmonEgg/Presentation/Views/Chat/ChatView.xaml +++ b/SalmonEgg/SalmonEgg/Presentation/Views/Chat/ChatView.xaml @@ -394,6 +394,7 @@ ResolveConversationSurfaceProjection().ShouldShowConversationInputSurface; + public bool ShouldLoadConversationInputSurface + => ResolveConversationSurfaceProjection().ShouldLoadConversationInputSurface; + public bool ShouldShowBlockingLoadingMask => ResolveConversationSurfaceProjection().ShouldShowBlockingLoadingMask; diff --git a/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjection.cs b/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjection.cs index 9b675525..2fabd019 100644 --- a/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjection.cs +++ b/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjection.cs @@ -11,6 +11,7 @@ internal readonly record struct ChatConversationSurfaceProjection( bool ShouldShowTranscriptSurface, bool ShouldLoadTranscriptSurface, bool ShouldShowConversationInputSurface, + bool ShouldLoadConversationInputSurface, bool ShouldShowBlockingLoadingMask, bool ShouldShowLoadingOverlayStatusPill, bool ShouldShowLoadingOverlayPresenter, diff --git a/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjectionCoordinator.cs b/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjectionCoordinator.cs index 287317cd..1c72b133 100644 --- a/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjectionCoordinator.cs +++ b/src/SalmonEgg.Presentation.Core/ViewModels/Chat/Overlay/ChatConversationSurfaceProjectionCoordinator.cs @@ -6,6 +6,7 @@ internal sealed class ChatConversationSurfaceProjectionCoordinator { private bool _hasLoadedActiveConversationRoot; private bool _hasLoadedTranscriptSurface; + private bool _hasLoadedConversationInputSurface; public ChatConversationSurfaceProjection Project(ChatConversationSurfaceStateInput input) { @@ -13,6 +14,7 @@ public ChatConversationSurfaceProjection Project(ChatConversationSurfaceStateInp _hasLoadedActiveConversationRoot |= state.ShouldShowActiveConversationRoot; _hasLoadedTranscriptSurface |= state.ShouldShowTranscriptSurface; + _hasLoadedConversationInputSurface |= state.ShouldShowConversationInputSurface; return new ChatConversationSurfaceProjection( state.IsActivationOverlayVisible, @@ -23,6 +25,7 @@ public ChatConversationSurfaceProjection Project(ChatConversationSurfaceStateInp state.ShouldShowTranscriptSurface, _hasLoadedTranscriptSurface, state.ShouldShowConversationInputSurface, + _hasLoadedConversationInputSurface, state.ShouldShowBlockingLoadingMask, state.ShouldShowLoadingOverlayStatusPill, state.ShouldShowLoadingOverlayPresenter, diff --git a/tests/SalmonEgg.Presentation.Core.Tests/Chat/Overlay/ChatConversationSurfaceProjectionCoordinatorTests.cs b/tests/SalmonEgg.Presentation.Core.Tests/Chat/Overlay/ChatConversationSurfaceProjectionCoordinatorTests.cs index b1bc7652..cb109306 100644 --- a/tests/SalmonEgg.Presentation.Core.Tests/Chat/Overlay/ChatConversationSurfaceProjectionCoordinatorTests.cs +++ b/tests/SalmonEgg.Presentation.Core.Tests/Chat/Overlay/ChatConversationSurfaceProjectionCoordinatorTests.cs @@ -99,4 +99,51 @@ public void Project_WhenTranscriptSurfaceWasShown_KeepsLoadStateWhenLaterHidden( Assert.False(hidden.ShouldShowTranscriptSurface); Assert.True(hidden.ShouldLoadTranscriptSurface); } + + [Fact] + public void Project_WhenConversationInputSurfaceWasShown_KeepsLoadStateWhenLaterHidden() + { + var coordinator = new ChatConversationSurfaceProjectionCoordinator(); + + var visible = coordinator.Project(new ChatConversationSurfaceStateInput( + IsSessionActive: true, + CurrentSessionId: "conv-1", + MessageHistoryCount: 0, + VisibleTranscriptConversationId: null, + IsChatShellVisibleForRemoteUi: true, + IsConnecting: false, + IsInitializing: false, + IsHydrating: false, + IsLayoutLoading: false, + IsSessionSwitching: false, + SessionSwitchOverlayConversationId: null, + SessionSwitchPreviewConversationId: null, + ConnectionLifecycleOverlayConversationId: null, + HistoryOverlayConversationId: null, + PendingShellActivationConversationId: null, + HydrationLoadedMessageCount: 0)); + + var hidden = coordinator.Project(new ChatConversationSurfaceStateInput( + IsSessionActive: true, + CurrentSessionId: "conv-1", + MessageHistoryCount: 0, + VisibleTranscriptConversationId: null, + IsChatShellVisibleForRemoteUi: true, + IsConnecting: false, + IsInitializing: false, + IsHydrating: false, + IsLayoutLoading: false, + IsSessionSwitching: true, + SessionSwitchOverlayConversationId: "conv-2", + SessionSwitchPreviewConversationId: null, + ConnectionLifecycleOverlayConversationId: null, + HistoryOverlayConversationId: null, + PendingShellActivationConversationId: null, + HydrationLoadedMessageCount: 0)); + + Assert.True(visible.ShouldShowConversationInputSurface); + Assert.True(visible.ShouldLoadConversationInputSurface); + Assert.False(hidden.ShouldShowConversationInputSurface); + Assert.True(hidden.ShouldLoadConversationInputSurface); + } } diff --git a/tests/SalmonEgg.Presentation.Core.Tests/Ui/XamlComplianceTests.cs b/tests/SalmonEgg.Presentation.Core.Tests/Ui/XamlComplianceTests.cs index de66063d..ea359af5 100644 --- a/tests/SalmonEgg.Presentation.Core.Tests/Ui/XamlComplianceTests.cs +++ b/tests/SalmonEgg.Presentation.Core.Tests/Ui/XamlComplianceTests.cs @@ -1403,6 +1403,7 @@ public void ChatView_UsesDeferredTranscriptLoadingWithoutWholePageLifecycleHack( Assert.Contains("x:Name=\"ActiveConversationRoot\"", xaml); Assert.Contains("x:Load=\"{x:Bind ViewModel.ShouldLoadActiveConversationRoot, Mode=OneWay}\"", xaml); Assert.Contains("x:Load=\"{x:Bind ViewModel.ShouldLoadTranscriptSurface, Mode=OneWay}\"", xaml); + Assert.Contains("x:Load=\"{x:Bind ViewModel.ShouldLoadConversationInputSurface, Mode=OneWay}\"", xaml); Assert.Contains("Unloaded=\"OnMessagesListUnloaded\"", xaml); Assert.Contains("private void OnMessagesListUnloaded", codeBehind); Assert.DoesNotContain("PointerPressed=\"OnMessagesListPointerPressed\"", xaml, StringComparison.Ordinal); From fd711950e31723995388359a8c89324609ed2bec Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:39:25 +0000 Subject: [PATCH 2/2] fix(ui): revert accidental deletion in XamlComplianceTests