- Fix TypeError by using explicit id instead of parent.parent chains
- Remove onContentHeightChanged handler that caused layout thrashing
- Add active guard on Loader to prevent null modelData access
- Simplify blockSegments null checks
- Emit SegmentsRole in dataChanged signals so QML updates properly
- Add onContentHeightChanged to scroll when content loads after session switch
- Add floating scroll-to-bottom button that appears when scrolled up
- Wrap chat ScrollView in Item to allow button overlay
- Detect plantuml/mermaid code fences and render as diagrams
- PlantUML: POST to localhost:8080/svg for SVG output
- Mermaid: GET to localhost:8081/img/ with base64 for JPEG output
(SVG not usable - Qt doesn't support foreignObject)
- Cache rendered diagrams by content hash
- Toggle between source code and rendered image view
- Copy button for source text or image data
- Fall back to source display on render errors
- Add ClipboardHelper for image clipboard support
- Clear diagram cache on session reset
Chat blocks:
- Enable selectByMouse and persistentSelection for text selection
- Add context menu with Copy and Select All
- Disable ListView drag during text selection (preserves wheel scroll)
- I-beam cursor over text content
Prompt input:
- Enable selectByMouse and persistentSelection
- Add context menu with Cut, Copy, Paste, Select All
- Remove ScrollView wrapper (TextArea handles overflow natively)
- I-beam cursor
- Add shouldAutoScroll property to track user intent
- Auto-scroll when near bottom (within 50px threshold)
- Disable auto-scroll when user scrolls up to read history
- Re-enable when user scrolls back to bottom
- Reset to auto-scroll on session/agent switch
- Increase timer interval to 16ms for smoother layout settling
appendData() now emits dataChanged with partial line buffer content
so the UI updates as tokens arrive, not just after complete lines.
This fixes the laggy streaming where text would appear in chunks.
Adds ability to hide/show the session list sidebar:
- Ctrl+B keyboard shortcut
- Toggle button (◀/▶) in the chat header
- Sidebar collapses to zero width when hidden
- Rewrite README.md with accurate 9P-based architecture
- Remove all D-Bus dependencies:
- Update dolphin/ollie-ask to use ollie-9p rdwr generate
- Update kate/ollie_ghost.cpp to use ollie-9p rdwr complete
- Remove QDBusInterface from ollie_ghost.h
- Fix OllieGhostProvider constructor in ollie_kate.cpp
- Remove D-Bus comments from gui/main.cpp and kate/ollie_kate.cpp
- Remove QtDBus from CMakeLists.txt (KF5 and KF6)
- Delete D-Bus test scripts test-e2e.sh and test-peers.sh
- Clarify no system tray component exists
- Update build/install instructions with just commands
- Add 2.5 second heartbeat timer using Lib9pClient to read 'help'
- Heartbeat detects daemon death reliably (vs unreliable process exit codes)
- On disconnect: clear sessions, stop streams, reset active selection
- On reconnect: refresh sessions, restart agent streams
Replaces flaky NinePConnection::disconnected signal which depended on
eventwait process exit behavior.
- Add colored dot to session rows showing tool server RPC status:
green=connected, yellow=paused+connected, gray=paused, red=broken
- Remove vestigial agent.connected from model (was never displayed)
- Session-level connected status is the correct granularity
Non-selected agents show idle (green). This matches the working
top indicator approach until we find a proper solution for tracking
all agent states reactively in QML.
- Add agentStateRevision property that increments on any state change
- QML binds to revision to trigger re-evaluation of agentState() calls
- All agent dots now show correct live state, not just active agent
- Update parentPaused on agents during incremental session updates
- Set parentPaused and parentExpanded when inserting new agents
- Update session paused property during updates
- Use explicit === true check for parentPaused
- Agent dots in tree are gray when parent session is paused
- Top state indicator is gray when viewing agent in paused session
- Top state label shows 'inactive' instead of agent state when paused
Previously disconnected was only emitted when entering Disconnected state,
not when transitioning Connected->RetryWait. This caused Lib9pClient to
keep a stale handle when olliesrv restarted, preventing reconnection.
- Add createSession(name, remote) with New Session dialog
- Remove agent connectivity code (redundant with session connectivity)
- Refactor NinePConnection as formal state machine with explicit events
- Fix silent restart to not cycle through Connecting state
Removes the activeState !== 'idle' check before sending the stop
command. The server-side Interrupt() is safe to call on an idle
agent (returns false, no-op).
This fixes an intermittent issue where pressing ESC wouldn't stop
the agent because the GUI's cached activeState was stale/out-of-sync
with the server's actual state.
- onAgentStateChanged: update specific agent in model instead of version bump
- State dot: use model.state directly instead of ollie.agentState() callback
- Remove ollie.isSessionPaused() call (was O(n) iteration per agent)
- Remove unused agentStateVersion property
Before: 1 state change → re-evaluate ALL agent dots → N * ollie.agentState() calls
After: 1 state change → setProperty on 1 model item → 1 binding update
- Check if event is for active agent FIRST using QStringView comparison
- Active agent: compare payload directly against m_activeState (no hash lookup)
- Non-active: use iterator find() to avoid double hash lookup
For the most common case (active agent state change), this reduces
the hot path to: QStringView compare → QString convert → direct compare
- Use QStringView to avoid string allocations during parsing
- Use QLatin1String for comparisons (faster than QString)
- Manual indexOf parsing instead of split('.') allocation
- Early return for non-session events
Reduces allocations in the agent.state hot path from ~7 QStrings
to 4 (only when state actually changes and needs to be stored).
Consolidate refreshSessions() to do everything in one iteration:
- Parse session/idx data
- Update m_agentConnected cache
- Track active session/agent existence
- Emit connection state change
Eliminates separate reconcileAgentConnections() call and second loop
to check active session/agent. O(n) instead of O(3n).
- computeConnectionColor() in QML instead of ollie.sessionConnectionColor()
(avoids O(n²) iteration through sessions list)
- Use agent.state from session/idx data directly instead of ollie.agentState()
(avoids hash lookup per agent per update)
All data now flows from session/idx through m_sessions to QML with no
callbacks into C++ during the update loop.
Agent connection state is now derived from session.connected in session/idx,
updated via refreshSessions(). No longer spawns N processes for N agents.
- Remove m_agentConnections hash and per-agent NinePConnection instances
- Remove readAgentRecords() (dead code - agents come from session/idx)
- reconcileAgentConnections() now just updates m_agentConnected from m_sessions
This eliminates one ollie-9p process per agent, leaving only:
- 1 daemon eventwait stream (for all events)
- 1 chat stream (for active agent only)
- 1 state stream (for active agent only)
Replace subprocess spawning (run9p) with native 9P connection for hot paths:
- refreshSessions() - called on every sessionsChanged event
- loadRootBackends() - called on daemon connect
- loadRootAgents() - called on daemon connect
Lib9pClient wraps the Go shared library with a Qt-friendly C++ API.
Connection persists across operations, eliminating ~2-5ms per-call
process spawn overhead.
Requires libollie9p.so from parent repo's lib9p/ package.
Instead of clearing and rebuilding the entire session tree model on
every sessionsChanged signal, diff old vs new and:
- Update existing session/agent properties in place
- Remove deleted sessions/agents
- Append new sessions/agents
This avoids O(n) model rebuilds that cause ListView to re-render all
delegates, improving responsiveness with many sessions.
- Remove console.log statements from hot paths
- Add sessionExpandedCache for O(1) expanded state lookup
- Replace O(n) parentSessionExpanded() loop with cache lookup
- Preserve expanded state across session tree rebuilds
- Parse new session/idx format with all data in one read:
name, id, paused, connected, cwd, backend, model, agents
- Handle delta events from eventwait instead of full refresh:
- session.{sid}.agent.{aid}.state -> update cache, emit signal
- Structural changes (new/kill/pause/resume) -> full refresh
- Remove per-agent statewait streams (eliminated N processes)
- Single eventwait stream handles all events via pubsub
This eliminates O(sessions * agents) synchronous reads per refresh,
replacing them with one read + incremental delta updates.
- Add Pause/Resume toggle to session context menu
- Gray agent dots when parent session is paused
- Disable input field with 'Session paused' placeholder when paused
- Preserve focus on prompt input during tree rebuilds
- Add per-agent statewait streams to track execution state
- Agent dots show: green=idle, orange=calling, blue=thinking
- Session dots show RPC connection health (unchanged)
- Use version counter pattern to force QML binding re-evaluation
- Green: connected and not paused (healthy)
- Gray: paused (intentional)
- Red: not paused but disconnected (crashed/broken)
- Yellow: paused but still connected (zombie)
- Fetch paused status from session/{id}/paused file
- Gray dot indicator for paused sessions (green otherwise)
- Session names now show agent count: 'ollie (2)'
- Fixed rename to use baseName for sessions (without count suffix)
commitRename now checks the return value from renameSession/renameAgent.
On failure, resets displayName to the old value and shows an error dialog
explaining the rename failed (e.g., name already in use).
refreshSessions now checks if the active session/agent still exist after
updating the list. If the agent was killed, clears m_agentId and stops
streams. The statewait guard will then return false, stopping the loop.