Files
oikos/plans/done/2026-08-04-chat-window-overhaul.md
dtoro 1aaedf498a
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled
v0.20.0: thinking blocks, chat windows overhaul, scroll fix
Backend:
- Add isThinking flag to agentEvent for text before tool calls
- Separate thinking from response text in runChatTurn and continue.go
- Persist thinking in a dedicated field in message content

Frontend:
- Add thinking field to MessageContent, ChatMessage, ChatTextEvent types
- Create ThinkingBlock.svelte — collapsible block with brain icon
- SSE handler moves text_delta content to thinking on isThinking flag
- Render thinking block between tools and response in ChatThread
- Fix chat window scroll reset on focus change (stable windowKeys order)
- Remove redundant #key id wrapper in WindowLayer
- Enlarge sidebar rail (24→32 default, 40→60 max)
- Remove glyph from sidebar, square graph at top
- Replace AgentTrace/ToolCallCard/UnifiedTimeline with TurnTrace/ToolLine
2026-08-04 22:42:53 +02:00

11 KiB
Raw Blame History

2026-08-04 — Chat interaction overhaul: inline progressive stream (Claude Code style)

Status: Planned — not started. (Refocused from the earlier feature-heavy draft; backend features deferred — see "Deferred".)

Goal

Streamline agent interactions — thinking, plan, tool usage, responses — into one linear progressive inline stream per turn (the Claude Code / Cline / Roo pattern), instead of the current split where the transcript shows a collapsed trace and the real live activity lives in a separate rail timeline. The right rail becomes graph-only (and auto-zooms to fit all entities).

Locked decisions (operator interview)

Decision Choice
Live activity layout Inline stream (Claude Code) — one progressive column per turn; rail keeps ONLY the Scope graph; Activity timeline tab removed
Tool-call detail Per-tool progressive lines — each tool its own compact live line (spinner → one-line result summary), expandable to raw
Feature phases Defer — edit/resubmit, @mentions, attachments are later phases; this plan is interaction-focused + graph auto-zoom

Diagnosis (grounded in current code)

  • The transcript (ChatThreadAgentTrace) collapses a whole turn's tool calls into one line ("Proposed plan" / "N tool calls"), raw-JSON detail on expand. Not progressive; you can't see what's happening without expanding.
  • The actual live plan + tool timeline lives in the right rail (TaskContextPanelUnifiedTimeline): newest-first backbone + tool stubs. So "what is the agent doing" is in a second place — a cognitive split.
  • UnifiedTimeline is imported only by TaskContextPanel (grep confirms), so removing the Activity pane is self-contained.
  • The activityLog store stays required: it feeds inline labels (toolActivityLabel), live run output (toolsWithLive), and the mascot (mascot/stimuli.ts). Only the timeline view is removed.
  • Tool events already arrive separately (tool_use then tool_result in chat.ts), and the activity log already carries humanized labels + per-tool stepSeq attribution. So progressive per-tool lines + step grouping are a presentation change, not a data/model change.
  • run results are free-form text (e.g. "run on lxc:caddy: ERROR exit status 1") → one-line result summaries are best-effort text parsing, no backend.

Design

D1 — One progressive inline stream per turn

Replace AgentTrace (one collapsed blob per turn) with a new TurnTrace.svelte rendered inline for each assistant turn, top-to-bottom:

  1. Live plan checklist (only on the most-recent/running turn — see D3).
  2. Tool lines grouped by plan step (D2), then orphan tools (no step).
  3. Streamed text answer (existing markdown-body prose-chat), with the blinking cursor while streaming (existing).
  4. A compact "Thinking" line while working and before any output: reuses the existing indicatorLabel (running step → tool → "Agent is thinking…"). Fades once text/tools arrive; reappears between steps.

D2 — Per-tool progressive lines (the Claude-Code signature)

One ToolLine.svelte per tool call (replaces ToolCallCard's row style):

  • Left: state icon — spinner while tool_use-only, ✓ on result, ✗ on error.
  • Label: existing toolActivityLabel(tool) (humanized action).
  • One-line result summary on completion — new toolResultSummary(tool) in activity.ts (see plumbing). E.g.:
    • runexit 0 · <first line> (parse "exit status N" / "ERROR")
    • get_entityhost:hubris (healthy); get_health_summaryhealthy X · degraded Y · down Z
    • list_entities/list_lxcsN entities; get_relationsN relations
    • search_knowledgeN results; upsert_knowledgerecorded document:…
    • update_plan_stepstep <seq> → <status>; propose_planN steps
    • default → first non-empty line of stringified result (≤80ch); done if empty
  • Live run output: while streaming, the line auto-expands a pinned-tail mini pane (reuse the liveOutput path from toolsWithLive).
  • Click → expand raw args/result (border-driven <pre>, cyberspace-square).
  • Border-driven, no rounded/shadow (per border_driven_language).

D3 — Live plan checklist (TodoWrite-style)

On the running/last turn, render the current-generation planSteps (already generation-aware via workspace.ts) as a checklist: pending = hollow, running = spinner + highlight, done = ✓, failed = ✗, blocked = pause. Steps check off live as plan.step.* events land. This is the unified timeline's plan view, moved inline and scoped to the active turn. Past turns render only their tool lines + text (the plan is session-level; the running turn carries its current state, mirroring how TodoWrite re-displays state each turn). On a terminal task state (done/failed), the checklist collapses to one line: Plan complete — N steps / Plan failed — step K.

D4 — Rail → graph only

TaskContextPanel: remove the Activity pane and the UnifiedTimeline import; the panel becomes the Scope graph full-height (keep the collapsible "Scope" header + the nowTouching strip). The graph is now the rail's entire job, so auto-fit (D6) matters more. activityLog* stores remain imported only where the inline stream/mascot need them.

D5 — Cyberspace cohesion of the stream

Apply alongside the rewrite so the new inline view is on-system from day one:

  • Transcript → terminal log rows (square, full-width, YOU/NOMOS role-tags, hairline divide-y separators; no bubbles, no soft shadow). Delete .user-msg { box-shadow }.
  • Tool lines + expanded <pre>: border-driven, square, opaque.
  • Composer: opaque bg-background, square (remove rounded-2xl/bg-card/50).
  • Rewrite the stale "Art Nouveau" <style> comments → "cyberspace/terminal".
  • Per central_css_override: drive surface styling centrally in app.css where it's a primitive concern; no ad-hoc rounded-*/shadow-*/backdrop-blur.

D6 — Graph auto-fit + drag-pan (SessionGraph.svelte) (carried over)

  • Wrap nodes+links in <g transform="translate(tx,ty) scale(s)">; fit the bbox of all nodes (radius + label + padding) into cw/ch; cap s ∈ [0.2, 2.5].
  • Re-fit on: mount, node-set change, container resize, sim-settle (alpha > 0.05), background double-click. Not every tick (fights pan). A userPanned flag pauses auto-follow after a manual pan until next membership/resize/double-click.
  • Background drag = pan (tx/ty); node drag converts screen→graph via the inverse transform before setting fx/fy. Dot-grid stays in screen space.
  • Keep: open-on-click, touched pulse, health-diff label, selection ring. Respect scrollIntoView pitfall (transform, not scroll).

Phased task list (each independently shippable; all frontend)

  • P1 — Inline progressive stream. TurnTrace.svelte + ToolLine.svelte; wire into ChatThread per turn; "Thinking" line; tool→step grouping via activity-log stepSeq matched by tool id; keep toolsWithLive for run.
  • P2 — Live plan checklist. Inline current-gen planSteps on the running turn; collapse-to-summary at terminal state.
  • P3 — Rail → graph only. Strip Activity pane + UnifiedTimeline from TaskContextPanel; verify no other importers (grep: only TaskContextPanel).
  • P4 — Cyberspace cohesion. Terminal log rows; remove rounded/shadow/ translucency; square composer; centralize in app.css; fix stale comments.
  • P5 — Graph auto-fit + drag-pan. D6.
  • Polish (small, frontend-only): per-message/tool copy; scroll-to- bottom button (uses container.scrollTo, never scrollIntoView).

Plumbing specifics (grounded, no backend)

  • New toolResultSummary(t: ToolCallResult): string in activity.ts, beside toolActivityLabel. Per-name switch (D2 list), graceful fallback.
  • Tool→step grouping: build id → stepSeq from the activity log once per turn; tools with no step render as orphans.
  • Reuse: planSteps (generation-aware), indicatorLabel, toolsWithLive, toolActivityLabel, liveOutput streaming path.

Constraints honored (saved decisions)

  • design_system.central_css_override, border_driven_language: square, hairline, opaque, focus-by-color, no soft shadows/glows.
  • chat_thread.pane_layout: dynamic status (Thinking line, live checklist) lives in the message Pane, never the input Pane.
  • wmkit.scrollintoview_reflow_pitfall: container.scrollTo for scroll-to- bottom; transform (not scroll) for graph pan.

Risks

  • Removing the rail timeline loses the "overview" view. Mitigation: the inline checklist + per-turn tool lines carry the same info progressively; the graph still shows fleet scope. If operators miss the overview, a collapsed "full timeline" can return as a toggle (follow-up).
  • Auto-fit vs manual pan — handled by userPanned + settle-alpha gate.
  • Inline stream length on long turns (1527 min, many tools) — progressive lines can get long; mitigate by auto-collapsing finished steps (keep the running step + its tools expanded, prior steps as one-line summaries).
  • Best-effort result summaries may misformat unusual payloads — fallback is always a truncated raw line + expandable raw detail, never a blank.

Validation

  • npm run lint, tsc --noEmit (no NEW errors beyond the known baseline in ui/*, oidc.ts, windows.ts, workspace.ts), vite build, vitest (add a toolResultSummary unit test per tool name + fallback).
  • Manual matrix: (a) start a long task → Thinking line → plan checklist appears and checks off live → each tool streams as its own line with a one-line summary → text streams; (b) reload mid-turn → working still shows; (c) run tool → live output pins to tail then collapses to summary; (d) graph auto-fits at settle + on new entity + drag-pan + double-click reset; (e) no rounded/soft-shadow remains on chat surfaces; (f) rail shows graph only.

Deferred (later phases, after this lands + validates)

  • Edit-and-resubmittruncateFrom store method + POST /sessions/{id}/edit (extract streamTurn from handleChat); reuse reopenSession (already exists, store.go:838 — marks prior session_plan_steps replaced, clears outcome) for the reset. Reject edit while the gate is busy (HTTP 409); edit cannot queue (truncation must be atomic). Regenerate = no-op-edit case.
  • @entity mentions — small GET /api/v1/entities/search?q= + composer autocomplete inserting type:name slugs the agent/graph already parse.
  • Attachments — multipart upload + agent_attachments table + configured OIKOS_ATTACHMENTS_DIR (explicit volume, not relative) + capped text inlining.
  • Continue button — needs /resume to reopenSession first for terminal sessions (today /resume does not reopen done/failed; handleChat's follow-up path does). Small backend tweak.
  • Image vision pending provider confirmation.

Out of scope / follow-ups

  • A collapsible "full timeline" overview toggle if the rail removal is missed.
  • read_attachment MCP tool (lazy full-content fetch, lower context than inlining).
  • Oldest-first timeline toggle / per-tool tool.* events for background turns.