Skip to content

Feat/chat UI improvements - #237

Closed
dave-apmic wants to merge 122 commits into
coder:mainfrom
k1dav-c:feat/chat-ui-improvements
Closed

Feat/chat UI improvements#237
dave-apmic wants to merge 122 commits into
coder:mainfrom
k1dav-c:feat/chat-ui-improvements

Conversation

@dave-apmic

Copy link
Copy Markdown

No description provided.

k1dav and others added 30 commits July 24, 2026 09:34
The vt10x emulator advances the cursor one column per rune, but TUI
agents lay out CJK and other double-width runes as two columns and
reposition with absolute escapes, leaving spurious gaps mid-line.
Inject a one-column padding rune after each wide rune printed in
ground state and strip it from ReadScreen output.

Co-Authored-By: Claude <noreply@anthropic.com>
TUI agents sometimes re-render already-finalized transcript lines
(e.g. Codex redraws previous cells when starting a new turn), which
pushes the screen diff boundary above the previous turn's output and
leaks its tail into the head of the new agent message. Detect the
line-level overlap between the previous turn's message tail and the
new message head and remove it.

Co-Authored-By: Claude <noreply@anthropic.com>
Tail the agent's session JSONL log (Claude Code, Codex) as a sidecar
alongside the PTY. Agent-specific resolvers locate the session file
from the agent PID; line parsers assemble rich structured messages
(text, thinking, tool calls, tool results, model and usage data) and
normalized session events. The watcher keeps no state: completed
messages and raw lines are handed to callbacks for downstream
storage and deduplication.

Co-Authored-By: Claude <noreply@anthropic.com>
…SSE heartbeat

- POST /message now queues user messages while the agent is busy
  (queued: true) instead of returning an error. A polling dispatch
  loop sends the queue head when the agent is stable, retries
  transient validation failures, and drops poison messages after 5
  consecutive failures with an agent_error event. New GET/PUT/DELETE
  /queue endpoints manage pending messages in FIFO order.
- Wire the JSONL watcher into the server: GET /rich-messages and the
  rich_message_update SSE event expose structured content blocks,
  model info, and token usage. The event emitter is the single store
  (upsert by message id and role, late-subscriber replay).
- GET /session/export downloads all normalized session events for the
  current run with stable event ids.
- /events sends a heartbeat every 15s so clients can detect
  connections that died without a FIN (e.g. after system sleep).

Co-Authored-By: Claude <noreply@anthropic.com>
Add a header menu action that fetches /session/export and saves the
normalized session events as a timestamped .jsonl file.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
k1dav and others added 29 commits September 5, 2026 04:36
- Add --status-success, --status-warning, --status-error tokens (light+dark)
- Add --code-block-bg/border/text tokens replacing hardcoded zinc-950
- Add --search-highlight/text tokens replacing hardcoded amber-300
- Add --hl-* syntax highlight tokens (8 colors, light+dark)
- Add color-scheme: light/dark for native form controls
- Remove 26 unused --sidebar-* and --chart-* token declarations

Co-Authored-By: Claude <noreply@anthropic.com>
…kens

Replace all instances of text-amber-600 dark:text-amber-400,
text-emerald-600 dark:text-emerald-400, bg-zinc-950, and related
color variants with the new semantic tokens (text-status-success,
text-status-warning, bg-code-block-bg, etc.) across chat.tsx,
explorer.tsx, and message-input.tsx.

Also surfaces the attach-files button as a first-class Paperclip icon
next to send (previously buried in overflow menu).

Co-Authored-By: Claude <noreply@anthropic.com>
- Add Usage interface and usage/model/stop_reason fields to RichMessage
- Add customTitle support via ?title= query parameter
- Extract formatTokenCount, computeTokenTotals, getStatusMeta into
  lib/session-status.ts for reuse by header and embed status bar

Co-Authored-By: Claude <noreply@anthropic.com>
…om title

- Display session token count in header bar (compact pill) and session
  details dropdown (input/output breakdown)
- Replace hardcoded amber/emerald with semantic status tokens
- Add Keyboard shortcuts menu item opening shortcuts dialog
- Show customTitle from ?title= query param instead of 'AgentAPI'

Co-Authored-By: Claude <noreply@anthropic.com>
- Add CodeBlockCopy component with clipboard button on hover
- Add rehype-highlight with selective language imports (~50KB)
- Create custom hljs-theme.css using --hl-* design tokens
- Fix dark mode leak: replace hardcoded zinc-950 with code-block tokens
- Fix search highlight: replace hardcoded amber-300 with semantic tokens
- Update test assertions for new token class names

Co-Authored-By: Claude <noreply@anthropic.com>
Split 1755-line monolith into focused modules:

lib/
  format-time.ts      — formatMessageTime, formatDateLabel, formatElapsedTime
  tool-format.ts      — formatToolInput, getToolSummary
  content-fingerprint.ts — contentFingerprint (FNV-1a)
  scroll-anchors.ts   — getPreviousUserMessageTop, getNextUserMessageTop
  task-timeline.ts    — types (ToolCall, TaskSection, etc.) + 6 functions

components/message-list/
  tool-call.tsx       — ToolCallCard, ToolCallGroup, ToolDetail (w/ duration)
  message-item.tsx    — MessageItem, CopyButton, LoadingDots (w/ Tooltip)
  empty-state.tsx     — EmptyState with AgentType displayName + 4 prompts
  task-group.tsx      — TaskGroup with Markdown preview dialog

message-list.tsx reduced from 1755 to 616 lines (↓65%).

Also includes:
- Tool call argument preview in collapsed summary
- Tool call duration display (resultTimestamp)
- User bubble visual weight rebalanced (bg-primary/10)
- Agent output wrapped in subtle card container
- Global render mode toggle moved to toolbar
- Multi-day date separators between tasks
- Visible tasks threshold lowered from 8 to 5
- Focus-visible rings on all hand-rolled buttons
- Search highlight uses semantic tokens

Co-Authored-By: Claude <noreply@anthropic.com>
- Create ui/tooltip.tsx with Radix @radix-ui/react-tooltip
- Mount TooltipProvider (delayDuration=300) in root layout
- Import hljs-theme.css in layout

Co-Authored-By: Claude <noreply@anthropic.com>
Shows General, Message Input, and Terminal Mode shortcut sections.
Triggered by pressing ? (when not in an input), or via the header
session details dropdown menu.

Co-Authored-By: Claude <noreply@anthropic.com>
Compact 32px bar showing status icon, agent display name (or custom
title from ?title=), and session token count. Mounted above Chat in
embed/page.tsx. Uses shared session-status.ts helpers.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace plain text 'Loading chat interface...' with an animated
skeleton matching the real layout (header + content + composer).

Co-Authored-By: Claude <noreply@anthropic.com>
Add ?title= URL parameter support for distinguishing multiple agent
tabs. Affects browser tab title, header brand text, and embed status
bar. Also adds rehype-highlight, highlight.js, lowlight, and
@radix-ui/react-tooltip dependencies.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
When JSONL rich activity provides real text+tool interleaving,
tools are already at their correct positions in the conversation
flow. Only apply groupConsecutiveTools() in the fallback PTY-only
path where tools lack positional context.

This fixes the '4 consecutive tool calls' container appearing at
the top instead of tools showing inline where they were called.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Claude stamps stop_reason on every JSONL line of a multi-block turn,
so FlushCompleted (called every 200ms) can fire between blocks of the
same message.id. This caused the pending entry to be deleted, and when
the next block arrived, a new empty RichMessage was created — losing
all previously accumulated content blocks (thinking, text).

The frontend then saw only the last block (usually tool_use) per turn,
with all text blocks missing. This prevented rich interleaving from
working, causing tools to cluster into 'N consecutive tool calls'
instead of appearing inline in the conversation flow.

Fix:
- Add a 'flushed' map to ClaudeParser that retains emitted messages
- When a new block arrives for a flushed message.id, re-attach it
  to pending with its accumulated content intact
- Clean up flushed entries when a genuinely new message.id appears
- Make EventEmitter.EmitRichMessage keep the longer content slice
  when upserting, as a defensive guard against partial re-emissions

Co-Authored-By: Claude <noreply@anthropic.com>
Claude Code v2.1.261+ uses session parking: the initial interactive
process may park its work to a background process with a different
PID and sessionId. The JSONL file is written under the background
process's sessionId, not the initial one.

The resolver now:
1. Tries the direct sessionId path first
2. If the file doesn't exist and parkedJobId is set, scans other
   session files to find the background process with matching jobId
3. Falls back to the original path if nothing matches

This fixes rich messages (thinking, tool calls, usage) being empty
when Claude Code uses session parking.

Co-Authored-By: Claude <noreply@anthropic.com>
EmitRichMessage now unions content blocks by key instead of keeping
whichever slice was longer. This prevents block loss when Claude streams
partial deltas that contain only the newest block.

The merged snapshot is always broadcast so clients never replace a
complete message with a single-block delta.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The watcher now periodically re-resolves the session path and hot-swaps
the tailed file when Claude Code parks a session to a new JSONL. The
resolver priority is reordered so the ParkedJobID scan runs before the
direct os.Stat check, preventing permanent selection of a stale file.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Add thinking block support to the task timeline: activity-groups and
task-timeline now carry a 'thinking' variant, and task-group renders a
collapsible ThinkingBlock with a Brain icon.

Move the markdown/raw render toggle from the global toolbar to each
individual message, giving users per-message control. The global state
and localStorage persistence are removed in favor of local component
state.

Co-Authored-By: Claude Code <noreply@anthropic.com>
- README: add thinking block rendering, per-message markdown toggle,
  rich message merging fix, and session parking hot-swap to the
  Changes in this fork section
- CHANGELOG: add v0.13.0 section with features and fixes

Co-Authored-By: Claude Code <noreply@anthropic.com>
Detect when the agent's output contains an interactive TUI prompt
(plan approval, trust confirmation, etc.) that cannot be operated
from the chat Text mode. Show a warning banner instructing the user
to switch to Terminal mode to respond.

Detects patterns like 'Would you like to proceed?' + '❯' for plan
approval, and 'Enter to confirm' for trust/other confirmations.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace hardcoded pattern matching with broader detection of TUI
interactive elements: selection menus (❯ + numbered options),
confirmation prompts (Enter to confirm / Esc to cancel),
permission prompts (Allow/Deny), auth flows (/login, authorize),
and question prompts (AskUserQuestion).

Shows a single unified banner: 'This prompt requires terminal
input. Switch to the Terminal tab below to respond.'

Co-Authored-By: Claude <noreply@anthropic.com>
… Codex

Rewrite terminalActionNeeded to cover all known TUI interactive
prompts from both Claude Code and Codex:

Claude Code:
- Selection menus (❯ cursor + numbered options)
- Permission/tool approval (Allow/Deny, Do you want to proceed)
- Plan mode approval (Would you like to proceed, Ready to code)
- Auth flows (authorize/authentication/sign in, /login, /mcp)
- MCP server trust and elicitation prompts
- Press Enter to continue/connect/install/retry/open

Codex:
- Selection menus (> cursor + numbered options + Press Enter)
- Edit/run approval (wants to edit, wants to run)
- Network access approval
- ChatGPT device-code sign-in

Removed previous false-positive-prone patterns (bare 'Login',
'once', question-mark heuristic) in favor of more targeted checks.

Co-Authored-By: Claude <noreply@anthropic.com>
When the agent shows a selection prompt (plan approval, AskUserQuestion,
permission dialogs, etc.), the chat UI now:

1. Detects numbered options from the terminal output
2. Renders clickable buttons for each option in the warning banner
3. Clicking a button sends the option number + Enter as raw keystrokes

This lets users respond to interactive prompts directly from the chat
UI without switching to Terminal mode. The 'Switch to Terminal tab'
fallback message still shows for prompts without numbered options.

Plumbing: onSendRaw callback threaded from Chat → MessageList →
TaskGroup → MessageItem.

Co-Authored-By: Claude <noreply@anthropic.com>
Parse AskUserQuestion and ExitPlanMode tool_use inputs from rich
messages and render their options as interactive buttons directly
in the ToolCallCard. Users can now:

- See the question text and all options clearly formatted
- Click an option button to send the selection as raw keystrokes
- See option descriptions below the buttons

AskUserQuestion cards auto-open when pending (waiting for input).
onSendRaw callback threaded through ToolCallGroup → ToolCallCard.

Co-Authored-By: Claude <noreply@anthropic.com>
Expand selectableOptions parsing to cover all prompt variants:

1. Numbered options (❯ 1. Label) — sends digit + Enter
2. Non-numbered cursor options (❯ Yes, I trust) — sends Enter
   for current selection, arrow-down + Enter for siblings
3. Enter-only confirmations (Press Enter to continue) — sends Enter

Buttons no longer show a number badge for non-numbered options.
Covers: trust dialogs, bypass permissions, ExitPlanMode approval,
permission prompts (Allow/Deny), and Enter-gated flows.

Co-Authored-By: Claude <noreply@anthropic.com>
The rich interleaving path (hasRichText && hasRichTools) was
discarding task.responses entirely, showing only rich text blocks.
This lost PTY-specific content like progress indicators, tool
execution summaries, and intermediate status lines.

Now PTY responses are included first, followed by rich activity
for structured text/tool interleaving.

Co-Authored-By: Claude <noreply@anthropic.com>
@dave-apmic dave-apmic closed this Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants