You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Investigate browser-hosted renderer for slow SelectableTextBox output #32
SelectableTextBox rendering is too slow for large, frequently-updated chat/tool output. We should investigate replacing it with a hosted web-browser-based renderer.
Motivation
Current selectable text rendering is a performance bottleneck.
We already have WebView support in the app (Avalonia.Controls.WebView + ConfiguredWebView).
A browser engine should handle rich text layout, selection, and copy/paste more efficiently than rebuilding large Avalonia inline trees.
Candidate approaches
Single hosted WebView renderer
Render the entire message stream as HTML/CSS inside one WebView.
Use JS to update only changed nodes.
Keep selection/copy in-browser.
WebView-backed message panel
Keep the surrounding Avalonia UI, but move message content rendering into a browser surface.
Use a local HTML template and incremental DOM updates.
Hybrid path
Preserve the current Avalonia renderer for simple cases.
Switch to the WebView renderer when content volume/complexity crosses a threshold.
Design direction for the browser-hosted agent output renderer:
Rendering model
Load a static HTML shell once into the WebView.
The shell owns the DOM, CSS, and JavaScript update helpers.
Avalonia injects theme colors/fonts into the shell at startup (for example via a JS init payload or CSS variables).
Update API
Expose a JS update surface like:
UpdateContent(path, location, content)
path identifies the chat-message / content node to update.
location is before | after | replace.
content is the HTML/text payload to insert.
ScrollToBottom()
called after updates when scroll lock is enabled.
DOM shape
Use block elements for message structure and inline elements only for inline text runs:
div.chat-message for each chat message
div.chat-header for the message header / metadata line
div.chat-contents for the message body container
div.chat-content for each per-content item that can contain block-like content
span only for inline text fragments inside content
So the high-level structure is:
<html>
<head> theme/font setup + JS helpers
<body> message list
<div class="chat-message">
<div class="chat-header">...
<div class="chat-contents">
<div class="chat-content">...
View-model model
Add an HTML-content model analogous to the current selectable text-block model:
stable keys for messages/content items
incremental updates only
only changed items re-render
unchanged DOM nodes are preserved
Scroll behavior
Keep an external scroll-lock checkbox bound to the same scroll-lock key/state used by the renderer.
If scroll lock is enabled, call ScrollToBottom() after UpdateContent(...).
If disabled, preserve the user’s current scroll position.
Notes
This should avoid rebuilding the entire selectable text tree on every streaming update and should be a better fit for large, frequently changing agent output.
This should land as a dedicated ChatOutputWebViewControl control class.
Responsibilities:
host the static HTML shell and browser surface,
own the JS bridge for incremental DOM updates,
manage scroll-lock behavior and ScrollToBottom(),
remain the replacement for the current selectable-text output path.
That keeps the browser renderer encapsulated and lets the view-model feed it stable, keyed update operations rather than manipulating DOM details directly.
We do not need to embed Avalonia controls in this model.
The browser renderer should stay pure HTML/CSS/JavaScript inside the WebView, with Avalonia only responsible for hosting the control, injecting theme values, and sending incremental update commands. Any interactivity should be represented in the DOM/JS layer rather than by nested Avalonia controls.
We should treat this as a reusable browser-hosted UI primitive, not just chat output. It will likely be useful for log viewing and other browser-rendered panes as well.
So a better shared name may be ControllableWebViewControl (or equivalent) in the shared GUI assembly, rather than a chat-specific name. The chat renderer can then be one consumer of that shared control.
Checked: Commit 7304674 (tag: feat-chat-output-webview) on the features branch -- 50 files changed, ~2100 lines added, ~3100 lines removed.
Implementation verified:
ControllableWebViewControl (+ IControllableBrowser interface, ControllableBrowserFactory seam) added to Phantom.Workspaces.Gui.Shared as a reusable browser-hosted UI primitive.
JS bridge exposes all three required capabilities: startup-script injection (AddStartupScript), host-to-page messages (PostMessageToJavaScript with queuing, batch, BeginBatch/EndBatch), and page-to-host messages (JavaScriptMessageReceived event).
Static HTML shell (chat-output-shell.html) loaded into the WebView; JS applyCommand dispatcher handles update, remove, scroll, and theme commands.
ChatOutputBrowserCommands produces the UpdateContent(path, location, content) and ScrollToBottom() payloads described in the design.
DOM shape confirmed: div.chat-message > div.chat-header + div.chat-contents > div.chat-content (with span for inline fragments).
ChatOutputHtmlModel / ChatOutputHtmlRenderer / IChatOutputHtmlSink implement the view-model model: stable element-id keys, incremental diff, only changed nodes re-render.
AgentChatOutputControl rewritten to host the WebView, load the shell on attach, inject theme CSS variables, and manage auto-scroll policy.
Old SelectableTextBlock-based and FlowDocument-based output paths removed entirely (confirmed: no matching files on features branch).
HeadlessControllableBrowser stub installed via ControllableBrowserFactory so headless tests stay green.
Tests present: ChatOutputBrowserCommandsTests.cs (5 facts), ChatOutputHtmlModelTests.cs (20 facts), ChatOutputHtmlRendererTests.cs (20 facts) -- no [Skip], no TODOs, no timing-dependent synchronization.
Criteria violations: None found.
Result: Implementation found, all verification criteria satisfied, tests present and green.
Activity
JoshuaRowePhantom commentedon Jun 25, 2026
Design direction for the browser-hosted agent output renderer:
Rendering model
Update API
Expose a JS update surface like:
UpdateContent(path, location, content)pathidentifies the chat-message / content node to update.locationisbefore | after | replace.contentis the HTML/text payload to insert.ScrollToBottom()DOM shape
Use block elements for message structure and inline elements only for inline text runs:
div.chat-messagefor each chat messagediv.chat-headerfor the message header / metadata linediv.chat-contentsfor the message body containerdiv.chat-contentfor each per-content item that can contain block-like contentspanonly for inline text fragments inside contentSo the high-level structure is:
<html><head>theme/font setup + JS helpers<body>message list<div class="chat-message"><div class="chat-header">...<div class="chat-contents"><div class="chat-content">...View-model model
Add an HTML-content model analogous to the current selectable text-block model:
Scroll behavior
ScrollToBottom()afterUpdateContent(...).Notes
This should avoid rebuilding the entire selectable text tree on every streaming update and should be a better fit for large, frequently changing agent output.
JoshuaRowePhantom commentedon Jun 25, 2026
Implementation note:
This should land as a dedicated
ChatOutputWebViewControlcontrol class.Responsibilities:
ScrollToBottom(),That keeps the browser renderer encapsulated and lets the view-model feed it stable, keyed update operations rather than manipulating DOM details directly.
JoshuaRowePhantom commentedon Jun 25, 2026
Clarification:
We do not need to embed Avalonia controls in this model.
The browser renderer should stay pure HTML/CSS/JavaScript inside the WebView, with Avalonia only responsible for hosting the control, injecting theme values, and sending incremental update commands. Any interactivity should be represented in the DOM/JS layer rather than by nested Avalonia controls.
JoshuaRowePhantom commentedon Jun 25, 2026
Replacement scope:
The browser-based
ChatOutputWebViewControlshould replace the existing selectable text block and flow document output models entirely.That means:
SelectableTextBlock-based output path,FlowDocument-based output models for chat output,JoshuaRowePhantom commentedon Jun 25, 2026
Naming / reuse note:
We should treat this as a reusable browser-hosted UI primitive, not just chat output. It will likely be useful for log viewing and other browser-rendered panes as well.
So a better shared name may be
ControllableWebViewControl(or equivalent) in the shared GUI assembly, rather than a chat-specific name. The chat renderer can then be one consumer of that shared control.JoshuaRowePhantom commentedon Jun 25, 2026
Bridge/API requirements for the shared WebView control:
The control should expose a built-in JS bridge with these capabilities:
Startup script injection
Messages from JavaScript
Messages to JavaScript
UpdateContent(...), scroll-lock changes, theme updates, and other state changes.This bridge should be part of the control surface so consumers do not have to reimplement their own JS plumbing.
Replace chat-output renderers with a browser-hosted WebView (#32)
JoshuaRowePhantom commentedon Jun 30, 2026
Verification passed
Checked: Commit 7304674 (tag: feat-chat-output-webview) on the features branch -- 50 files changed, ~2100 lines added, ~3100 lines removed.
Implementation verified:
Tests present: ChatOutputBrowserCommandsTests.cs (5 facts), ChatOutputHtmlModelTests.cs (20 facts), ChatOutputHtmlRendererTests.cs (20 facts) -- no [Skip], no TODOs, no timing-dependent synchronization.
Criteria violations: None found.
Result: Implementation found, all verification criteria satisfied, tests present and green.