Repository navigation
WebView reload/refresh wipes all chat content (DOM never repopulated) #39
Description
Describe the bug
Switching between light and dark themes at runtime has no effect on the browser-hosted chat output surface. The chat output retains the colours of whichever theme was active when the WebView first loaded.
If the underlying WebView reloads after the initial page load — for example due to an OS/renderer restart, a crash-recovery cycle, or any navigation event that isn't triggered by reassigning HtmlShell — all chat content disappears and the page remains blank.
Root cause
ControllableWebViewControl queues outbound messages in pendingMessages only while isShellLoaded == false. The flag is cleared only when HtmlShell is reassigned.
AgentChatOutputControl.AttachOutputModel sets HtmlShell and immediately constructs a ChatOutputHtmlModel. The model constructor (ApplyInitialTransform) emits all initial DOM insert operations, which land in pendingMessages (because isShellLoaded is still false) and are flushed when navigation completes — correct for the first load.
On a spontaneous reload (without HtmlShell being reassigned):
isShellLoadedis alreadytrue— flag is never cleared.OnNavigationCompletedfires, findspendingMessagesempty, runs startup scripts.Readyfires →OnBrowserReadyposts only theThemecommand.- The DOM reverts to the bare shell; the live
ChatOutputHtmlModelnever replays.
Options considered
Option A — Move model creation to OnBrowserReady ✅ recommended
OnNavigationCompleted already fires Ready on every navigation completion — first load and spontaneous reload alike. AttachOutputModel would set HtmlShell (triggering navigation) without creating the model. OnBrowserReady would:
- Post the theme command (so CSS variables are set before DOM ops arrive).
- Dispose any existing
outputModel. - Construct a fresh
ChatOutputHtmlModelforsubscribedViewModel.
Because Ready fires after isShellLoaded has been set to true, every message from ApplyInitialTransform is delivered directly — no queuing needed. The handler fires identically for the first load and for any subsequent reload, so both cases share the same code path.
HeadlessControllableBrowser must be updated to raise Ready synchronously when HtmlShell is assigned so that headless tests can exercise the new model-creation path without a real WebView.
Pros: Single code path for first-load and reload. Clean invariant: model exists iff browser is ready. No duplicate logic.
Cons: Minor restructure of AttachOutputModel. pendingMessages becomes vestigial for initial content (but still useful as a safety net for any message sent between HtmlShell assignment and Ready).
Why not use a JavaScript "ready" message?
A JS-initiated approach — having the page post { type: "ready" } via DOMContentLoaded, with the host recreating the model on that message — would also work, but is redundant. The C# Ready event already fires at exactly the right moment (after OnNavigationCompleted, so the page is ready to receive PostMessageToJavaScript calls). Adding a JS-to-host round-trip would introduce a new message type and JS in the shell without fixing anything that the existing Ready event doesn't already cover.
Why not prohibit refresh (block F5 / Ctrl+R)?
Blocking keyboard shortcuts via JavaScript would prevent user-triggered reloads but would not handle spontaneous reloads caused by WebView2 crashes, OS-level renderer restarts, or process suspension/resume. The fix must handle the reload case regardless of cause; prohibiting refresh is not a substitute.
Option B — Add Replay() to ChatOutputHtmlModel
Add a method that re-emits all currently tracked history and running-item models back to the sink. OnBrowserReady would call outputModel?.Replay() in addition to posting the theme.
Pros: Stable object graph; subscriptions and idSequence are preserved across reloads.
Cons: Duplicates the insertion logic from the transformers' OnInsert; the IsInserted flag on each model would need resetting; the running-item Activate() call would need replaying. More moving parts and a second code path that must stay in sync with the primary insertion path.
Recommended fix
Option A. Changes expected in:
Phantom.Workspaces.Agent.Gui/Controls/AgentChatOutputControl.axaml.cs— move model creation fromAttachOutputModeltoOnBrowserReadyPhantom.Workspaces.Agent.Gui.Tests/HeadlessControllableBrowser.cs— raiseReadysynchronously onHtmlShellset
Activity
Fix #39: recreate chat output model on every browser Ready event
JoshuaRowePhantom commentedon Jun 30, 2026
Verification passed
Checked: Commit 784331b (features branch) - AgentChatOutputControl.axaml.cs, HeadlessControllableBrowser.cs, and the new AgentChatOutputControlTests.cs (26 test methods).
Result: Implementation found, all verification criteria satisfied, tests present and green.
Implementation
Tests covering described behaviour
No disabled tests, no unresolved TODOs, no timing-dependent synchronisation.