Signals were emitted before m_agentId was set, causing QML's
onActiveSessionIdChanged handler to call readLog() with an empty
agent path. Now: discover agent → emit signals → start streams.
- KRunner: rewritten to use plan9port 9p command for all operations
(list sessions, kill, one-shot generate). No D-Bus dependency.
- Tray: removed. Was a thin D-Bus status wrapper, not useful enough
to justify maintaining.
- Plasmoid: removed. GUI is standalone now.
- Kate: temporarily disabled in CMake. Header partially converted to
9P (D-Bus includes removed, QProcess/9P helpers declared). Full
conversion is next.
- All ninepBin() helpers use $PLAN9/bin/9p (not hardcoded path).
- CMake: removed Qt5::DBus, KF5::Notifications, KF5::Plasma deps.
Only remaining link deps: Qt5::Core, Qt5::Widgets, Qt5::Quick,
Qt5::QuickControls2, KF5::Runner.
Kill the entire D-Bus rendering pipeline. The GUI now uses plan9port's
9p command as a subprocess for all server communication:
- Chat streaming: persistent 'read chat' process, readyReadStandardOutput
delivers tokens directly to QML (no polling, no signals, no offsets)
- State: 'read statewait' process, restarts on each state change
- Session list: 'read session/idx' one-shot
- Submit/stop/kill: 'write prompt/ctl' one-shot
The QML is stripped to fundamentals: session list + monospace streaming
text (last 8KB tail) + input field. No ChatBlockModel, no ThemeManager,
no OllieDBusClient, no syntax highlighting, no markdown rendering.
This eliminates the multi-second GUI freezes caused by:
- QTextDocument relayout on 200KB+ accumulated text
- loadEarlier cascade creating hundreds of delegates
- KSyntaxHighlighting::Repository blocking init
- Offset mismatch triggering synchronous D-Bus calls
Features will be re-added incrementally on this proven foundation.
D-Bus retained only for plasmoid/tray/KRunner (desktop integration).
Allow specifying a remote target (e.g. user@host) when creating a
session from the GUI. Empty means local execution. The value is passed
through to the D-Bus CreateSession method's remote parameter.
Reactions were removed from the core runtime. Remove the dead QML code:
- 3x setReactions/getReactions dbus calls
- blockReaction property on delegate loader
- Reaction badge label and reaction buttons row
When the new-session dialog opens, the backend combo defaults to index 0
but onCurrentTextChanged doesn't fire for the initial value. Explicitly
call listModelsByBackend for the default backend so the model dropdown
is populated immediately.
Child MouseArea inside Flickable interferes with drag detection even
when only accepting RightButton. Move it to a sibling overlay with
propagateComposedEvents so left-drag reaches the Flickable natively.
Remove manual pan logic. Let Flickable handle drag-to-pan natively.
MouseArea only accepts RightButton (close) and wheel (zoom), so it
no longer steals left-click events from the Flickable.
Left-click drag was unreliable for panning. Use Meta (Super/Win key)
+ left-click-drag instead. Flickable interactive mode disabled;
panning is handled manually via MouseArea.
The mermaid container has been replaced with a proper mermaid.ink
server. Use /img/ endpoint (returns JPEG) instead of /svg/ since
Qt's SVG renderer can't handle CSS class-based styles. Falls back
to mmdc CLI with PNG output if server is unavailable.
Qt's SVG renderer doesn't support CSS class-based styling that mermaid
generates (<style> blocks with class selectors). This caused text and
connectors to be invisible — they had no fill/stroke applied.
Fix: output PNG from mmdc for mermaid diagrams. Qt renders PNG perfectly.
PlantUML and Draw.io continue using SVG (they use inline styles).
Text and connectors were invisible because mmdc rendered with a dark
theme (white text/edges) on the white diagram background. Explicitly
pass -t default to force dark text on light background.
- Detect mermaid/plantuml/drawio/mxgraph code fences as 'diagram' sub-blocks
- Render via local servers (PlantUML :8080, Mermaid :8081, draw.io :8082)
- Fall back to CLI tools (plantuml, mmdc, drawio) if servers unavailable
- Cache rendered SVGs in ~/.cache/*/diagrams/ keyed by content hash
- Show rendered image with white background (dark theme friendly)
- Collapsible source toggle (▶/▼) below rendered diagram
- Middle-click to invalidate cache and re-render
- Left-click opens fullscreen pannable/zoomable viewer
- Fullscreen: scroll-wheel zoom, drag to pan, Esc/right-click/dbl-click to close
- Both kf5 and kf6 builds
In Qt5 Repeater delegates, 'property var block: model' doesn't
propagate dataChanged updates for individual roles. Add a reactive
'blockReaction' string property bound to model.reaction directly,
which updates when the model emits dataChanged.
Same fixes as kf5:
- Remove hover-based sloppy focus that stole focus from buttons
- Add Ctrl+L to focus prompt
- Add emoji reaction buttons on assistant message hover
Reaction buttons (👍👎🚀💩🤔) appear on hover over assistant chat
bubbles. Clicking one calls the React D-Bus method which records
the feedback in session history without triggering a model turn.
Also adds react() to OllieDBusClient.
The hover-based sloppy focus timer was stealing focus from buttons
via paneRoot.focus=true every 100ms of mouse movement, making
buttons very hard to click.
Replace with a click-to-focus MouseArea that only updates the
focusedPaneIndex on actual clicks, with mouse.accepted=false
so clicks propagate to the target widget normally.
The sloppy focus timer was calling panePrompt.forceActiveFocus()
on mouse movement, stealing focus from button clicks. Remove the
auto-focus; add Ctrl+L shortcut to focus the prompt input instead.
Adds a filterText property to ChatBlockModel that filters all
parsed blocks by case-insensitive substring match on content
or tool/call name. Toggle the filter bar with Ctrl+F; Escape
or the close button clears the filter and hides the bar.
Shows match count while filtering.
Optional name field in the dialog. If provided, the session is
renamed immediately after creation via RenameSession D-Bus call.
Left blank, the auto-generated ID is used as before.
Use palette.mid at 60% opacity when active (scrolling), fully
transparent when idle, palette.mid on hover, palette.highlight
when pressed. 150ms color animation for smooth transitions.
- Add ScrollBar.vertical to the chat Flickable (both KF5/KF6)
- Remove interactive:false so native flick gestures work
- Trigger paneModel.loadEarlier() when scrolled near top
- Preserve scroll position when prepending older blocks
- Reduce column width by 12px for scrollbar clearance
- Expose GuiActivator as "activator" context property
- Add Ctrl+N shortcut to open a new window (both KF5 and KF6)
- Add per-session draft text persistence to main-kf5.qml
(KF6 ChatPane.qml already had this from previous commit)
When switching between sessions, the draft text in the prompt
is saved and restored. Each pane maintains a _drafts map keyed
by session ID, so switching away and back preserves whatever
was typed.
Running ollie-gui a second time now opens a new window in the
existing process (via NewWindow D-Bus method) instead of just
raising the existing window. The single-instance guard remains.
Each window gets its own ChatBlockModel via QML instantiation
(ChatPane.qml), so windows can display independent sessions.
Replace simple folder picker with a full dialog. Adds listBackends()
and listModels() to OllieDBusClient. createSession() now accepts
backend and model parameters.
Adds errorOccurred signal to OllieDBusClient, emitted when
CreateSession (or other async calls) fail. GUI shows a modal
dialog; plasmoid shows a passive notification.
- YouTube videos show hqdefault.jpg thumbnail
- All cards have minimum 200px height
- Play button opens in browser (fixed parent chain)
- Use id-based refs instead of fragile parent.parent chains
Detect video URLs (YouTube, Vimeo, Dailymotion, Twitch, direct media)
in chat responses. Direct .mp4/.webm/.ogv files play inline via
QtMultimedia Video. Other video sites show a card with play button
that opens in the system browser.
Adds Qt6::Multimedia dependency.
Replace full-pane MouseArea sloppy focus with HoverHandler which
does not interfere with click event delivery. Also increase timer
to 300ms and guard against redundant forceActiveFocus() calls.
Incremental parsing caused phantom blocks and disappearing content.
The 150ms coalescing on the daemon side is sufficient for perf.
updateVisibleWindow() extraction kept for clean separation.
When the tail contains only one header (common case during streaming),
just update the last block content directly. Fall back to full reparse
only when a genuinely new block boundary appears.
reparseIncremental() only re-parses from the last block boundary
instead of the entire chat text. Fixes UI lag and typing blockage
during long streaming responses.
Also extracted updateVisibleWindow() for reuse.
- OllieDBusClient: detachProcess, listDetached, signalDetached,
getDetachedOutput, dismissDetached methods + D-Bus signal handlers
- ChatPane.qml: Detach button (visible during calling: state) +
badge showing detached process count (click opens Process Manager)
- main.qml + main-kf5.qml: Process Manager window with process list,
live output tail, Term/Kill/Dismiss buttons per process
Full split-pane implementation:
- Ctrl+\ splits focused pane (BSP tree layout, alternating h/v)
- Ctrl+W closes focused pane (collapses parent split)
- Sloppy focus: pane focuses when mouse stops moving (100ms)
- Drag header to another pane to swap sessions
- Multi-line TextArea prompt with emoji shortcode completion
- Each pane has own ChatBlockModel and D-Bus signal filtering
- Auto-relayout on window resize
Major refactor: extracted chat UI into ChatPane.qml (self-contained
with own ChatBlockModel, D-Bus signal filtering, prompt input).
Main content area is now a SplitView with dynamic pane creation.
Auto-layout: splits horizontal if window wider than tall.
Sidebar click updates the focused pane session.
New OllieDBusClient methods: getChat, getConfig, getState (per-session).
New signal: chatAppended (emitted for all sessions, not just active).
Circular node layout showing all sessions with peer relationship edges.
Nodes are color-coded by state (green=idle, blue=thinking, orange=calling),
pulse when active. Click a node to select that session.
Toggle between list and graph view in sidebar toolbar.
Adds peerList() to OllieDBusClient.
- Add configurable close-to-tray (Settings > System Tray)
- QSystemTrayIcon with show/hide toggle and quit action
- closeToTray property persisted in theme.conf
- Window hides instead of quitting when enabled
- Fix session rename being interrupted by state changes
- onStateChanged no longer calls refreshSessions()
- State updates in-place via sessionStateUpdated signal
- Delegates track state locally, preserving rename TextField
- Replace Qt.callLater scroll debounce with Timer (16ms) to coalesce
rapid contentHeight changes and eliminate scroll flickering
- Add execute button (play icon) on code blocks for scripted languages
(bash, python, perl, ruby, etc.) — launches in terminal
- Fix assistant bubble width: always use 95% of available width
- Add minimum width (280px) to code fence blocks
- Move emoji popup into prompt RowLayout so it appears near input
- Disable wheel scroll inertia (direct contentY) while keeping flick
inertia intact
- Apply all fixes to both KF5 and KF6 QML files
- Tool blocks: expanded via sub-blocks (same as assistant) so embedded
diffs and code fences render properly with coloring
- Call blocks: always JSON, use formattedContent (pretty-print + highlight)
- Prose sub-blocks use Text.MarkdownText for native backtick/bold/link support
Tool results often contain markdown with code fences and diffs.
Using formattedContent treated the whole block as raw text.
Now tool blocks expand into parsed sub-blocks (prose, code, diff)
just like assistant blocks, so embedded diffs get colored.
HTML RichText collapses multiple spaces, destroying JSON indentation.
Wrap all highlighted output in <pre> tags and use real newlines instead
of <br>. pre-wrap allows wrapping at container width.
QSyntaxHighlighter applies colors to the layout only (not accessible
via QTextBlock iteration). Replace with a custom AbstractHighlighter
subclass (HtmlBuilder) that builds HTML directly in applyFormat().
This actually produces colored output.
QML scope issues made JS-based formatting unreliable. New approach:
- FormattedContentRole in the model computes pretty-printed HTML in C++
- Call blocks: always prettyJson()
- Tool blocks: detect JSON/diff and format, otherwise escaped HTML
- QML just binds text: block.formattedContent || ""
The onVisibleChanged approach failed because block.blockContent could
not resolve through the Loader scope. Now using direct binding:
- call blocks: text bound to chatModel.prettyJson(block.blockContent)
- tool blocks: detect JSON/diff, format accordingly
- prettyJson() uses QJsonDocument for proper indentation + KSyntaxHighlighting
- Only evaluated when visible (lazy)
ListView.positionViewAtEnd() causes freezes by forcing instantiation
of all delegates to measure total height. Flickable+Column avoids this
- contentHeight is computed directly from Column.implicitHeight
- Scroll to bottom is just contentY = contentHeight - height
- No virtualization needed with only 20 blocks
- Eliminates all positionViewAtEnd and _scrolling guard complexity
- Code blocks show plain text immediately, highlight via Timer after
150ms (prevents blocking the render thread during initial load)
- Reduced initial blocks from 40 to 20
- Detect append vs full reset in setRawText for better session switch
- Model shows last 40 blocks initially, positioned at end
- Scrolling near top triggers loadEarlier() which prepends 30 blocks
- Scroll position maintained after prepend via contentY adjustment
- All blocks still parsed (for searching later) but only tail rendered
- Reduced initial render cost significantly
With 300KB+ chat logs producing hundreds of blocks, instantiating all
delegates was causing unresponsiveness. Cap the model at the last 80
blocks and set cacheBuffer: 200 to limit off-screen rendering.
- Cache parseSubBlocks per block (only reparse when content changes)
- Cache highlightedHtml results (code+language key)
- Prevents expensive KSyntaxHighlighting and regex on every render
- Also fixes scroll loop with _scrolling guard
- ChatBlockModel::reparse() now does incremental diffs instead of
beginResetModel which destroys all delegates and resets scroll
- ListView scroll uses _scrolling guard to prevent infinite loop
from positionViewAtEnd triggering contentHeightChanged
- Loader delegate: set explicit height from item.implicitHeight so
ListView can calculate correct contentHeight for scrolling
- Prose sub-blocks: escape HTML, convert newlines to <br> before
linkifying paths (RichText ignores bare newlines)
Replace flat TextArea chat view with structured ListView:
- ChatBlockModel parses raw chat text into typed blocks (user,
assistant, reasoning, call, tool, error, info)
- Sub-blocks parsed from markdown: prose, fenced code, diffs
- Code blocks rendered with KSyntaxHighlighting (auto light/dark theme)
- Diff blocks with red/green line coloring
- Copy button on code and diff blocks
- File paths linkified and clickable (opens with xdg-open)
- Reasoning/call/tool blocks collapsible (click to expand)
- User blocks highlighted with subtle accent background
- Error blocks in red
- Scroll pinning preserved (auto-scroll when at bottom)
Replace single-line TextField/QLineEdit with multi-line TextArea/QPlainTextEdit.
Enter sends, Shift+Enter and Ctrl+J insert newlines. Max height capped so the
input area does not take over the panel.
Scan ~/.local/share/color-schemes/ in addition to /usr/share/color-schemes/
so user-installed themes (e.g. Catppuccin) are discovered. Mode is now any
scheme name rather than just system/light/dark. Settings dialog uses a
ComboBox listing all available schemes plus "System".
- Kate: add always-visible stop button next to prompt input
- All front-ends: sort sessions alphabetically by ID
- All front-ends: only auto-scroll chat if at bottom, show
floating go-to-bottom button when scrolled away
- CMakeLists: set KDE_INSTALL_PLUGINDIR to QT6_INSTALL_PLUGINS
so plugins install to lib64/qt6/plugins/ (where Kate looks)
- Remove placeholderText from QML palette block (not available in Qt5)
- Use theme.placeholderTextColor directly instead of palette.placeholderText
- Add Qt version guards for QFontDatabase static vs instance API
- Add Qt version guard for QVariant::typeId vs QVariant::type
- Link KF5::ConfigCore for ollie-gui KF5 build
Installs ollie.svg to hicolor scalable apps and org.ollie.gui.desktop
to /usr/share/applications. Sets desktopFileName so Plasma can match
the running window to the .desktop entry when pinned.
Session list with create/kill/rename, chat view, prompt input,
folder dialogs for cwd. Reuses OllieDBusClient from plasmoid plugin.
Supports both Qt5 (KF5) and Qt6 (KF6) builds.