Use a 16ms Timer restarted from onContentHeightChanged and
onCountChanged. The timer fires after the polish pass completes,
avoiding the recursive layout loop that the old synchronous
positionViewAtEnd() caused.
Change m_chat from Oneshot to Guarded with the same guard as
m_state (daemon connected + session + agent selected). The fd
stays open continuously; it closes on agent switch or program exit.
Removes the demand-driven start/stop logic from handleEvent() and
submit() — no longer needed since streaming doesn't cause layout
thrash after the earlier fixes.
Previously, every streamed line triggered a full rebuild of the
block's HTML: reparsing all content, re-running syntax highlighting
on every code fence from the start. For a block with n lines this
was O(n) per line = O(n²) total.
Now appendContent() uses incrementalAppend() which:
- Prose: escapes only the new line and appends to the segment HTML
- Code fences: saves KSyntaxHighlighting::State between lines and
only highlights the new line (O(1) per line)
- Fence close: finalizes segment, requests diagrams as before
- Partial line display: uses cached state, no full re-highlight
Full rebuildBlockHtml() is retained only for diagram reply callbacks
where the entire segment needs reconstruction.
Also adds BlockRenderState to ChatBlock for tracking incremental
parse position, highlight state, and accumulated HTML.
The delegate used a ColumnLayout root with parent-dependent width
fallbacks (: 0, : 100) causing two-pass layouts. Combined with
onContentHeightChanged -> positionViewAtEnd(), this created a
feedback loop: height change -> scroll -> refill -> new delegates ->
height change -> ... burning 100% CPU on the main thread.
Fixes:
- Replace onContentHeightChanged with onCountChanged + Qt.callLater
- Replace ColumnLayout root with plain Column
- Bind all child widths to root.width (top-down, no fallbacks)
- Create context Menu lazily on first right-click
- Add reuseItems: true to the ListView
- Standardize bin path to ~/.local/bin
The chat stream (ollie-9p read .../chat) blocks forever waiting for
data. Starting it on agent switch meant stopStreams() would block the
main thread trying to kill a hung process.
Fix: stream chat only while the agent is active.
- submit() starts the chat stream (agent is about to become active)
- Event handler stops chat on idle transition, starts on non-idle
- switchAgent() only starts chat if agent is already non-idle
- NativeStreamer::stop() uses SIGTERM+200ms then SIGKILL+100ms
instead of immediate SIGKILL+1000ms
appendContent was calling rebuildBlockHtml (full syntax highlighting)
on every single line during initial log load. For a 1000-line chat,
that's O(n²) highlighting passes.
Add beginBulkLoad()/endBulkLoad() — skips per-line rebuilds during
bulk, then rebuilds all blocks once at the end. QML load sites now
wrap appendData(log) with bulk mode.
createAgent now returns the new agent ID so the dialog can immediately
switch to it. Previously the first agent created required a GUI restart
to select.
Also removes the Remote field from NewAgentDialog — remote belongs to
the session, not the agent.
Use onContentHeightChanged with a re-entry guard to scroll as content
renders. Works for both initial log load and streaming responses.
- followTail tracks user preference (updated on scroll end)
- scrollingNow prevents recursive positionViewAtEnd calls
Replace timer-based scroll hack with event-driven approach:
- onContentHeightChanged triggers scroll when content grows
- onMovementEnded detects when user scrolled away from bottom
Scrolling is now driven by actual content changes, not arbitrary delays.
Go blocking reads can't be interrupted by closing the fid from another
thread. Revert to subprocess-based streaming using ollie-9p, which can
be killed cleanly.
Native library is still used for all request/response operations.
Only streaming (chat, statewait, eventwait) uses subprocess.
- Disconnect finished signal before waiting in stop() to prevent
onThreadFinished from running after thread is deleted
- Use deleteLater() in onThreadFinished for safe cleanup during restart
- Remove redundant quit() call (QThread::create threads have no event loop)
Replace StreamFsm and NinePConnection (QProcess-based) with NativeStreamer
which uses the new libollie9p streaming API (open/read_fid/close_fid).
All 9P operations now go through the native library:
- Chat, statewait, eventwait streams use threaded NativeStreamer
- Request/response operations use Lib9pClient directly
- No more subprocess spawning for any 9P operation
Remove ninepBin, ollie9pBin, serverAddr, run9p helper functions.
Remove streamfsm.cpp/h, ninepconnection.cpp/h from build.
Convert most operations from spawning plan9port 9p subprocess to using
the persistent Lib9pClient connection. This eliminates process spawn
overhead for common operations like submit, interrupt, kill, pause,
resume, rename, and create.
Streaming operations (chat, statewait) still use subprocess because
they need continuous tailing which the native lib doesn't support.
Reduces code by ~70 lines and removes synchronous QProcess waits.
- 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.