Skip to content

Agent mode: attachments, send/stop button, picture viewer, working sign, iA layout, and typography - #886

Merged
penso merged 18 commits into
mainfrom
worktree/agent-mode-improvements
Oct 29, 2026
Merged

penso merged 18 commits into
mainfrom
worktree/agent-mode-improvements

Conversation

@penso

@penso penso commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

Agent mode (the native chat over a herdr-acp pane) gets easier to read and to use: files and screenshots can be dropped or pasted, the send button behaves like the phone app's, pictures open large, the agent always shows that it is working and on what, tool calls step back behind the answer, prose keeps a readable measure, and a new iA layout mutes everything but the current turn. The in-pane terminal view renders Markdown too.

Commits, each its own changelog entry:

  • fix(chat): attach dropped files and pasted screenshots in agent mode. The chat occluded the terminal's drop handler, so drops never arrived; it now takes them itself. Pasting only read text: copied Finder files now attach instead of pasting their names, and a copied image is prepared off the UI thread (TIFF to PNG, oversized images shrunk), saved to a private per-user folder, and attached (copied to the host for an SSH chat).
  • feat(chat): stop a running turn from the send button and fix(chat): disable the send button until a prompt is written. As in the phone app: Stop while the agent works and nothing is written, Send once something is (queued behind the turn), greyed out otherwise. The header's Stop is gone.
  • feat(acp): draw Markdown and quiet tool calls in the terminal chat view. Inline code, bold, links, lists, headings, and quotes are drawn rather than shown; tool titles are muted, lose their ticks, and stop after two lines.
  • feat(chat): point the send button's arrow up, as Messages does.
  • feat(chat): open a picture large with a click, and close it with Escape. Pictures in messages, under tool calls, from the web, and diagrams; fit to the pane, at most 2× their size, animated through crate::motion.
  • feat(chat): set tool calls smaller and muted so the agent's answer reads first.
  • feat(chat): show what the agent is doing while it works, in every layout. A row above the composer with a pulsing dot, what the agent is doing (thinking, the running command or file, writing), and the turn's elapsed time; it sits outside the list so it shows when scrolled back, and keeps its height between turns.
  • feat(chat): add an iA layout that mutes earlier turns, like iA Writer. One column: prompts small and italic under a hanging ›, steps one muted line each until opened, prose in full ink, and everything before the latest prompt at 40% until pointed at.
  • feat(chat): keep the agent's prose to a book's measure, with gentler headings. At the default reading width prose keeps to ~70 characters (34 em) while code, tables, and pictures use the column; a width the user sets is followed as set. Headings are 1.35/1.18/1.05×, and block spacing binds headings to their text and keeps list items together.
  • test(chat): preview tool calls, earlier turns, attachments, and a picture (native fixture cases and settings used for the screenshots below), plus a formatting commit.

Protocol and compatibility: no wire changes. Layouts gained prompt_text and tool_preview hooks, rows know whether they belong to the current turn, and the rich text renderer takes its text size.

Screenshots

Captured from the native chat fixture at 5f7f342e, except the terminal-view pair (real herdr-acp). "Before" images are from the same fixture on the earlier code.

Typography (before / after, then dark and narrow)

Before After
Before: long lines, large headings After: ~70 character measure, gentler headings
After, dark After, narrow window

Tool calls muted (before / after)

Before After
Before: tool output as loud as the answer After: tool text smaller and muted

Send button and attachments

Idle, empty: Send greyed out Working, empty: Stop
Send disabled Stop button
Working with text: Send queues it Pasted screenshot and file as chips

Working sign and picture viewer

Timeline: what the agent is doing iMessage: typing with the dot
Working row in Timeline Typing row in iMessage
A picture in the timeline The picture opened large

iA layout (light while working, dark, narrow)

Light Dark
iA layout, light iA layout, dark
iA layout, narrow

Terminal view in the pane (before / after)

Before After
Terminal view before: raw Markdown Terminal view after: drawn Markdown, quiet tool calls

Validation

  • just ci on 5f7f342e (macOS arm64): passed.
  • Every commit in the PR was checked to build on its own: cargo check -p herdr-gpui -p herdr-acp --locked --tests at each commit, no errors or warnings.
  • cargo clippy -p herdr-gpui --locked --all-targets --features integration-test,mockup: clean.
  • Focused suites while iterating: cargo test -p herdr-gpui --locked --bins chat:: (343 passed) and cargo test -p herdr-acp --locked tui:: (36 passed).
  • New regression tests: a real file drop on the chat, paste of copied files and of a clipboard image, the send button's three states, the picture viewer (click, click-out, Escape), the working row's text and its presence in every layout, the iA column geometry, the prose rhythm, muted tool titles in the terminal view, and the terminal view's Markdown. The drop, paste, and viewer tests were confirmed to fail without their fix.
  • Screenshots are from the native chat fixture (HERDR_TEST_RICH_CHAT, --features integration-test,mockup), which draws the real views with a synthetic conversation; the terminal-view pair is the real herdr-acp binary against a stub ACP agent in a headless terminal (vhs).

QA

  • just ci on 5f7f342e, macOS arm64: passed
  • Each commit builds on its own (cargo check per commit)
  • Headless regression tests listed above
  • Native fixture screenshots, light, dark, and narrow
  • Drag a screenshot from Finder onto an agent-mode chat; it appears as a chip and goes with the prompt
  • Take a screenshot with ⌃⇧⌘4, press ⌘V in the composer; it attaches as a PNG. Copy a file in Finder and paste; it attaches by path, not by name
  • Repeat both on an SSH chat; the files are copied to the host
  • While the agent works with an empty composer, the button is a red stop that cancels the turn; typing turns it back into Send, and it is greyed out when idle and empty
  • Click a picture in a message and under a tool call; it opens large, and a click or Escape closes it, animated both ways (also with QA > Slow Animations)
  • Watch the working row during a real turn: the dot pulses, the text follows thinking, a running command, and writing, and the time counts up
  • Switch to iA; earlier turns are muted and come back to full ink under the pointer
  • just lint-linux and just lint-windows (no cfg gates were touched, so not run here)

Known limitations

  • The composer still reads the clipboard on the UI thread (as it already did for text); a very large copied image could cause a short hitch. The terminal paste uses a helper process for this.
  • Pasted images are kept in the user's temporary folder (herdr-chat-images) and not cleaned up, like the chat's HTML pages.
  • In iA, pointer focus switches instantly rather than fading, and focus is per entry, not per paragraph.
  • Running sub-agents and background shells are not shown anywhere yet; that needs a protocol addition and is being done separately on worktree/agent-activity.

penso added 12 commits October 28, 2026 18:11
The chat view occludes the terminal element, so GPUI's hit test never
reached the terminal's drop handler, which already knows how to turn a
drop over a chat into attachments. The chat now takes the drop itself
and routes it the same way.

Pasting into the composer only handled text: a copied screenshot did
nothing and a Finder file copy pasted the file's name. Copied files now
attach as a drop would, and a clipboard image is prepared off the UI
thread with the terminal's image preparation (TIFF becomes PNG,
oversized images shrink), saved to a private per-user folder named by
its contents, and attached; on an SSH chat it is copied to the host
like any other attachment.
The Stop control sat in the chat header, away from where the prompt is
written. While the agent works and the composer holds nothing to send,
the send button now shows a stop square and cancels the turn; once
something is written or attached it goes back to sending, which queues
the prompt as before. The header Stop is gone.

The window does not redraw on each keystroke in the composer, so the
per-tick chat poll, which already reads the composer's text, records
whether the button stops and redraws only when that flips.
Match the phone app's composer: the round button stops a running turn
while nothing is written or attached, sends once something is (queued
behind a running turn), and is greyed out and inert otherwise, including
for blank text. The button's state is one enum the chat poll keeps
current, rather than a stop flag beside an always-enabled send.
The in-pane view showed the agent's Markdown as written, with tool
commands in bold as loud as the reply and their output six lines deep.
Inline code, strong text, and links are now drawn rather than shown,
bullets, numbers, headings, and quotes become styled leads, and wrapped
items hang under their text; a mark left unclosed stays as written.
Tool titles are muted, lose their code ticks, and stop after two lines,
and output previews show four lines.
A picture in the conversation, under a tool call, from the web, or a
drawn diagram, was only ever shown at its small inline size. Clicking a
drawn one now shows it over the chat, fit to the pane with a margin and
enlarged at most twice its own size, and a click anywhere or Escape
closes it. It fades and grows in and back out through crate::motion.
…ads first

A tool call's command, files, and output were set in the terminal font
at nearly the prose's size and ink, so a long test log outweighed the
reply under it. They now take one size two points under the prose, in
the theme's muted color; the kind label, the done or failed status, and
the added and removed counts keep their colors.
Only the iMessage layout said anything under the conversation during a
turn; the others left it to a small word in the header, out of sight of
the reading. Every layout now keeps a row above the composer with a
pulsing dot, what the agent is doing (thinking, the command or file a
step runs, or writing), and how long the turn has run. It sits outside
the list, so it shows however far the conversation is scrolled back,
and keeps its height between turns so nothing jumps. iMessage keeps its
typing line and gains the dot.
A new chat layout, iA, sets the conversation as one column: the user's
prompts smaller and italic under a hanging mark, the agent's steps as
one muted line each until opened, and its prose in full ink. Everything
before the user's latest prompt is drawn at 40% and comes back to full
ink while the pointer rests on it.

Rows now know whether they belong to the current turn, and a layout can
set the size the user's prompts are drawn at and keep folded tool calls
to their title line, so the rich text renderer takes its text size.
…headings

At the default reading width, prose ran the full 800 px column, about
95 to 100 characters a line. Paragraphs, lists, and quotes now keep to
34 em, about 70 characters, while code, tables, and pictures still take
the column; a reading width the user sets is followed as set. Headings
step down to 1.35, 1.18, and 1.05 times the text, and the space between
blocks follows what they are: a heading binds to its text, list items
sit close, and a list stands apart from the prose around it, all in ems
so the rhythm holds at every zoom.
…ture

The native chat fixture gains a tools case and a turns case with
earlier exchanges, and settings for a working agent with a running
turn, a draft, attached files, an agent picture, and that picture
opened large, so each new state can be captured without input. Its
composer button follows the app's rule, which the fixture's missing
chat poll would otherwise leave unset.
@penso

penso commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner Author

just ci ran locally and passed on 2b2c6832 (macOS arm64), after merging main and fixing the review findings. just lint-linux and just lint-windows were not run; no cfg gates changed.

@greptile-apps

greptile-apps Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; the previous picture-focus issue is fixed.

Summary

Agent mode gains attachments, a send/stop button, a picture viewer, working indicators, the iA layout, and quieter typography.

  • Agent chat attaches dropped files and pasted pictures to the next prompt.
  • The composer button sends a prompt or stops the running turn.
  • Pictures in chat open in a larger viewer that closes by click or Escape.
  • Every chat layout shows what the agent is doing while it works.
  • The iA layout puts the current reply in one quiet reading column.
  • Agent replies use a book-like text width and gentler heading sizes.
  • Terminal chat draws common Markdown and keeps tool calls quiet.

Reviews (3) · Last reviewed commit: "fix(chat): make a picture's pane active ..." · Reviewed by Greptile

Comment thread crates/herdr-gpui/src/chat/pasted.rs Resolved
Comment thread crates/herdr-gpui/src/chat/pasted.rs Resolved
Comment thread crates/herdr-gpui/src/chat/window.rs Resolved
Comment thread crates/herdr-acp/src/tui/lines/prose.rs Resolved
Comment thread crates/herdr-gpui/src/chat/view/rich.rs Resolved
penso added 5 commits October 29, 2026 00:20
…mprovements

# Conflicts:
#	crates/herdr-gpui/src/config/chat.rs
A pasted picture was saved in the background without being marked as
pending, so a prompt sent meanwhile went without it and the picture
joined the next one. It now counts as pending, as a file being copied
to an SSH host already does, so sending waits for it; a local chat says
"Preparing a picture" while it does. The save also remembers the
conversation it was pasted into and attaches only while that one still
owns the pane, so an agent restarted meanwhile never receives it.
…arge

The composer kept the keyboard behind an open picture, so typing edited
the hidden draft and Enter sent it. The viewer now holds the keyboard
through its own focus handle, which the chat's focus keeping treats as
its own; keys and paste stop at it, Escape closes it, and the keyboard
returns to the composer as it closes.
The iA layout sets prompts smaller, but a formula in one kept the
picture, baseline, and line box drawn for the chat's full text size.
Formulas now scale with the size of the message they sit in.
Drawing Markdown links as underlined text dropped their address, and
the terminal cannot open them, so the destination was lost. The address
now follows the text, muted, in brackets, unless the text already is
the address.
@penso

penso commented Oct 29, 2026

Copy link
Copy Markdown
Owner Author

@greptileai review — main is merged (the only conflict was the layout table, now on Fluent ids with a chat-layout-ia entry), and all five findings are fixed in 74404dd, 63269fb, 2fdafd0, and 685242e, with regression tests and a reply on each thread. Full local just ci passes on 685242e.

The picture's click stops at the picture, so it never reached the chat
handler that makes the chat's pane the active one. Opening a picture in
a split pane that was not active put the keyboard on the viewer, and the
next focus check handed it back to the active pane: Escape then left
the picture open, and typing went to the other pane's draft. Opening a
picture now does what a click on the chat does first.
@penso

penso commented Oct 29, 2026

Copy link
Copy Markdown
Owner Author

@greptileai review — the new finding (a picture in an inactive split pane losing the keyboard) is fixed in 2b2c683, with a regression test and a reply on its thread. Full local just ci passes on 2b2c683.

@penso
penso merged commit 22111c5 into main Oct 29, 2026
19 checks passed
@penso
penso deleted the worktree/agent-mode-improvements branch October 29, 2026 17:32
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.

1 participant