- Add Interrupt block type to ChatBlock enum
- Detect <system-user-interruption> and </system-user-interruption>
tags as block openers/closers in the parser
- Render interrupt blocks with bright yellow header
(#3d2e00 dark, #fff8e1 light)
- Show ⚠ User Interrupt header label
Right-click now stores the target session ID in a root-level
property (killTargetSessionId) before popping the menu, avoiding
unreliable QML parent chain traversal that could resolve to the
wrong delegate.
- Add killSession(sessionId) Q_INVOKABLE to Ollie9pClient
- Accept right button in session tree MouseArea
- Context menu with Kill option on right-click
- MenuItem resolves session ID via parent chain
Replace the single-bracket INI-like format with triple-bracket blocks
with explicit [[[end]]] terminators. The parser tracks m_inBlock to
ignore content between blocks, making the format robust against
accidental header-like content in tool output or LLM responses.
- Go sender: FormatEvent + NewEventHandler + replayMessagesToLog
- C++ parser: new regex for [[[type]]], new [[[end]]] handler, m_inBlock gate
- QML: update hardcoded [info] string
- Remove 300ms click delay (timer-based single/double-click disambiguation)
- Use native QML onDoubleClicked for rename instead of timer hack
- Capture model index as delegate property to fix rename targeting all nodes
- Remove dead rename code that was orphaned by the timer removal
- Clean up Escape shortcut handler
buildSessionTree() was clearing the tree and setting expanded=true
only for the active session, collapsing all other sessions whenever
an agent in a different session was selected.
Now snapshot expanded state before clearing and restore it, so manually
expanded sessions stay open across agent switches.
- New session dialog: Backend and Model now use proper ComboBoxes with
editable=true so users can type or select from dropdown
- Backend picker and model picker dialogs removed (no longer needed)
- Model list populated reactively via Connections on backend selection
- Fix model caching: m_rootModels[be].toStringList() returns a copy;
extract to local QStringList, append, store back into map
- Session creation uses correct path 'session/new' instead of 'new'
- Chat blocks are now collapsible: user/assistant/tool expanded by default,
reasoning/call/error/info/default collapsed
- Fix chat history on session switch: readLogForSession() reads from the
explicit session/agent path instead of relying on active state
- Fix delegate layout: use plain ColumnLayout instead of Item-wrapped
ColumnLayout so ListView calculates implicit heights correctly
- Keys.onEscapePressed doesn't fire when child items have focus
- Shortcut element provides proper global key handling
- Added console.log debugging to track ESC activation and activeState
- C++: Use <pre style="white-space:pre-wrap;"> instead of <div> with <br>
so raw newlines are preserved through to the HTML renderer
- QML: Use TextEdit (preserves whitespace) instead of Label (collapses it)
for rendering blockRenderedHtml
- Backend: TextField with '...' button opens ListView of available backends
- Model: TextField with '...' button opens ListView filtered by selected backend
- Auto-populate model placeholder when backend text changes
- Model picker also works on manual click for re-selection
- Update onAccepted to use field.text instead of combo.currentText
- Remove import of QtQuick.Dialogs 1.15 (not available on Gentoo)
- Replace FolderDialog with FileDialog { selectFolder: true }
- FileDialog is part of QtQuick.Controls 2.x which is already imported
- Add C++ methods: loadRootBackends(), loadRootAgents(), getAvailableModels(), createSession()
- Add Q_PROPERTYs for root data state (backends, agents, models)
- Add '+' button to sessions toolbar
- Create New Session Dialog with:
- Directory field + Browse button (FolderDialog)
- Name field (optional, auto-generated if empty)
- Backend combo (populated from /backends)
- Model combo (filtered by selected backend)
- Agent combo (populated from /agents)
- Remote field (user@host:port)
- Root data loaded lazily on startup
- OK creates session via 'write new' 9P call and refreshes list
- Guard delegate against undefined properties during initial render
- Use || 0 fallbacks for optional model properties
- Remove broken ScrollBar.vertical.value assignment (not assignable)
- Fix Keys.onReturnPressed to use explicit function(event) parameter
- Call buildSessionTree() on Component.onCompleted
- Add getAgents() to Ollie9pClient for listing agents per session
- Add switchAgent() for switching sessions+agents atomically
- Add activeAgentId property and activeAgentIdChanged signal
- Replace flat ListView with tree-aware model (session → agents)
- Session nodes expandable/collapsible with ▶/▼ indicator
- Agent nodes indented, clickable to activate that agent
- Active agent highlighted, active session auto-expanded
- State dots on both session and agent rows
- TextArea grows from 5 rows up to 200px as content increases
- Auto-scrolls to bottom on text change
- Uses Math.min(contentHeight, max) to avoid unnecessary space
- Add fence state machine (NoFence → InFence) to ChatBlockModel
- On closing ```, render via KSyntaxHighlighting or inline diff colorizer
- Store rendered HTML in ChatBlock.renderedHtml, emit dataChanged on RenderedHtmlRole
- QML shows RichText Label when renderedHtml is populated, falls back to PlainText
- Add KF6::SyntaxHighlighting dependency for ollie-gui target
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).
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.
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.
- 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)
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.
- 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.
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.
- 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".