From ed74d421c30fd3225dc68bff2a99db5ac4d3fa83 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Sun, 9 Aug 2026 12:09:42 +0200 Subject: [PATCH] =?UTF-8?q?gui:=20fix=20tool=20block=20rendering=20?= =?UTF-8?q?=E2=80=94=20use=20blockRenderedHtml=20directly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Repeater-over-segments approach was failing to display tool output. When a complete block arrives in one streaming chunk, QML's Repeater with a var-typed model doesn't reliably re-render delegates on property changes. Fix: use a single TextEdit bound to blockRenderedHtml for all non-diagram blocks. The Repeater path is retained only for blocks containing diagrams (which need the Loader/component switching). This is simpler, more reliable, and avoids the Repeater delegate lifecycle issues. --- gui/ChatBlockDelegate.qml | 64 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/gui/ChatBlockDelegate.qml b/gui/ChatBlockDelegate.qml index 2af304e..7856d4b 100644 --- a/gui/ChatBlockDelegate.qml +++ b/gui/ChatBlockDelegate.qml @@ -11,6 +11,7 @@ Column { required property string blockType required property string blockName required property var blockSegments + required property string blockRenderedHtml // Reference to parent ListView for scroll control property ListView parentListView: null @@ -119,8 +120,60 @@ Column { visible: root.blockExpanded spacing: 4 + // Fast path: single TextEdit for the concatenated HTML. + // Diagrams are handled separately via the segments list. + TextEdit { + id: mainTextEdit + width: root.width + visible: !hasDiagrams() + text: root.blockRenderedHtml + textFormat: Text.RichText + readOnly: true + selectByMouse: true + persistentSelection: true + wrapMode: TextEdit.Wrap + font.family: theme.fontFamily || Qt.application.font.family + font.pointSize: theme.fontSize > 0 ? theme.fontSize : 12 + selectionColor: theme.highlightColor + selectedTextColor: theme.highlightedTextColor + + onActiveFocusChanged: { + if (!activeFocus && root.parentListView) + root.parentListView.interactive = true + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + propagateComposedEvents: true + cursorShape: Qt.IBeamCursor + onPressed: function(mouse) { + if (root.parentListView) root.parentListView.interactive = false + mouse.accepted = false + } + onReleased: function(mouse) { + if (root.parentListView) root.parentListView.interactive = true + mouse.accepted = false + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + cursorShape: Qt.IBeamCursor + onClicked: { + if (!mainTextEdit._menu) { + mainTextEdit._menu = contextMenuComponent.createObject(mainTextEdit) + } + mainTextEdit._menu.popup() + } + } + property var _menu: null + } + + // Diagram path: use Repeater for blocks that contain diagrams Repeater { - model: root.blockSegments || [] + model: hasDiagrams() ? root.blockSegments : [] delegate: Loader { width: root.width @@ -131,6 +184,15 @@ Column { } } + function hasDiagrams() { + if (!root.blockSegments) return false + for (var i = 0; i < root.blockSegments.length; i++) { + if (root.blockSegments[i] && root.blockSegments[i].type === "diagram") + return true + } + return false + } + // Text segment component Component { id: textComponent