# Claude chat A chat pane for a live Claude Code session. A small two-icon toggle sits at the right of the tab row. Press it, or `ctrl-shift-j`, and the terminal running Claude Code swaps for a chat view of the same session: your messages as bubbles, replies as formatted text, tool calls as lines you can open, code as cards you can copy. You type in a box at the foot, answer permission prompts on cards, and press again to get the terminal back. The session never stops. It runs on macOS only.  ## What you see **The toggle.** A block 84px wide at the right end of the 42px tab row, on `chrome` with a 1px `border` line along its foot. Inside it, right-aligned with 10px to spare, is a pill 30px high: `surface`, 1px `border`, radius 15, 2px padding. It holds two 30×24 cells, chat on the left and terminal on the right, each with a 14px icon. A 30×24 thumb in `solid` sits behind the side showing, and that side's icon turns `background`. The other icon is `muted`. The pill shows only while the focused pane is a Claude Code session on this machine, or that session's chat. Anywhere else the block is empty. **The chat pane**, on `canvas`, in `foreground` at 14px, top to bottom: - **The thread** fills the pane. Everything sits in a centred column 85% of the pane wide, never under 800px or over 1400px, with 24px at each side. A narrow pane just fills. - Your messages: right-aligned bubbles, `solid` with `solid_foreground` text, radius 14, 14×9px padding, up to 85% of the column. A message sent but not yet in the transcript draws at 60% opacity. - Replies: Markdown, 14px on 23px lines, selectable. - Tool calls: 30px lines in one `background` card, 1px `border`, radius 12. Each line has a glyph, the tool's name in JetBrains Mono, its target in `dim`, how long it took, and a chevron. The glyph is a tick in `done`, a cross in `blocked`, a spinner while running, or a dot if the turn ended first. - Code: a card with a 33px band showing the language and Copy, and a body of 12.5px JetBrains Mono on 20px lines in theme colours. - Quiet lines at 12.5px in `dim`: command output, "Conversation cleared", and each turn's end, as `Worked for 1m 27s · 15:56`. - The status row while a turn runs, 24px high: a spinner in `accent` cycling `· ✢ ✳ ✶ ✻ ✽` every 110ms, the session's own status text at 13px in `muted`, and the seconds so far in `dim`. - With nothing to show yet, a line in the middle of the pane: `…`, or `Transcript unreadable`. - **The panel slot**, between the thread and the foot, empty until needed. It holds the permission and question cards, the usage panel, the `/` list and the "End this session?" card, all on the column's edges. - **The composer**, 24px from the sides and 14px from the bottom: `surface`, 1px `border`, radius 14. - Queued messages, when there are any, in a tray of rows at the top. The first row has Edit and a solid `Send now` pill marked `⌃↵`, and every row has a remove button. - Images waiting to go, as 44px thumbnails with a remove button. - The text box, 14px, placeholder `Reply`, growing from 1 to 10 rows. - A note at 12px in `muted`, when there is one, such as `A dialog is open in the terminal`. - The bottom row, 40px: on the left a context gauge of 13 ticks with its percent, on the right the model and effort control, then a 30px round `solid` send button with an arrow. **The model pad** opens above the model control: a 324px `popover` card with the model and effort as its heading, and a 246×144 field of dots. Each row is a model, and the columns are Low, Medium, High, xHigh and Max. A 22px puck marks the choice. **The usage panel** is a `background` card, radius 14, with a 41px "Usage" header, refresh and close buttons, and at most 56% of the pane's height. At the top are the limits: a label, a percent and an 8px bar each, with `↻ Sep 21 at 9am` below. The bar is `done` under 80%, `ansi3` under 100% and `blocked` at 100%. Below them are two tabs. "What's using it" has request and session counts, Last 24h and Last 7d, advice rows with bars, and top skills and subagents. "Stats" has a grid of active days, eight figures and token totals for All time, Last 7 days and Last 30 days. ## How it behaves - **The swap.** A first press splits a chat pane to the right of the terminal, then zooms and focuses it, so the chat fills the tab. Later presses zoom and focus whichever side you asked for, and the keyboard goes with it. Pressing the icon already showing does nothing. `ctrl-shift-j` always goes to the other side. The thread fades in under a cover in its ground colour. Two sessions in one tab each keep their own chat. - **Agent status.** - `working`: the status row shows, and the send button becomes a square Stop button while the box is empty. - `blocked`: the status row reads `Waiting for you`, and a card opens in the panel slot. - `done` and `idle`: no status row. Nothing else in the pane changes. - **Cards.** Every word on a card is read from the terminal's screen. A press works after the card has been up for 400ms. The screen is read again first, and the key goes only if the same dialog is still there. Keys `1` to `9` and Esc work when the composer is empty. With text in it, keys stay typing and the card takes presses only. A dialog the pane can't read, such as a plan or a multi-select, swaps to the terminal and the status row reads `Waiting for you in the terminal`. - **Sending.** Enter sends and Shift+Enter makes a new line. During a turn a message queues and shows in the tray. Ctrl+Enter sends the queue now. Text half typed in the terminal's own prompt is set aside first and comes back after. A send that can't go puts the text back in the box and says why. - **Images.** Paste with the usual key, or drop an image file. Any other dropped file goes in as its path. - **`/` commands.** Typing `/` and a name opens a list grouped as This session, In the chat, Skills, Opens the terminal and End. Up, Down, Tab, Enter and Esc work it, and a press picks a row. - `/usage` opens the usage panel and `/stats` opens it on Stats. With the panel open, `/usage` closes it. `/stats` moves to Stats, or closes it if Stats is showing. - `/model` and `/effort` open the pad, or with a value change this session only. They are never typed into the terminal, where a value would save a default for new sessions. - `/tui` and `/terminal` swap. - `/exit` and `/quit` ask first: `Keep going` or `End session`. - `/permissions`, `/memory`, `/status`, `/hooks`, `/plugin`, `/resume`, `/rewind`, `/add-dir`, `/ide`, `/statusline`, `/login`, `/logout` and `/vim` go to the session, then swap, because they open a dialog. - Anything else goes to the session and is looked at after 2 and 5 seconds, in case it opened a dialog. - **The pad.** Press or drag in the field, or use the arrow keys. Enter or a press outside applies the choice once the session is between turns, and `…` on the control means it's waiting. Esc closes it and applies nothing. - **The thread.** It stays on the newest message unless you scroll up. Scrolling to the top loads older messages, 60 at a time. A press on a tool line opens its result. Copy puts the whole block on the clipboard and reads `Copied`, or `Copied part` if only the cut version could be read. `/clear` and `/resume` move the pane to the new transcript. - **Motion.** New reply text sweeps in line by line behind soft-edged covers, and the list glides down after it. Other rows fade and rise 6px. Tool lines, cards and panels spring open. Rows that land while the terminal is showing are simply there when you come back. - **Closing.** When the terminal ends, its chat closes itself. ## Built from One mod, `mods/claude-chat/`, with `mod.json` set to `{ "place": "none", "title": "Claude Code" }`, in two roles: - **Chat pane.** `pane.split` makes it with `props: { terminal: "
" }`. - **Toggle.** A layout block with `props: { "role": "toggle" }`. `main.js` checks the role first and hands the copy to `toggle.js`. That copy owns `swap.js` and listens for `swap` from `nextCommand()`. Adding it to a window: 1. Copy `mods/claude-chat/` to `mods/`. 2. Merge `keys.json`: `{ "global": { "ctrl-shift-j": "claude-chat.swap" } }`. Herdr's default keys don't use `ctrl-shift-j`, but ask first if the person's do. 3. Clone the layout, which also switches to it: `herdr-app do layout.clone '{"name":"main","as":"main-chat"}'`. The `tabs` mod needs no clone. 4. In `layouts/main-chat.json`, replace the `tabs` block in `work` with this row: ```json { "id": "tabs-row", "type": "row", "size": { "px": 42 }, "resizable": false, "children": [ { "id": "tabs", "type": "block", "window_drag": true, "content": { "mod": "tabs" } }, { "id": "chat-toggle", "type": "block", "size": { "px": 84 }, "window_drag": true, "content": { "mod": "claude-chat", "props": { "role": "toggle" } } } ] } ``` The layout's shipped `pane` header stays as it is. The rest: - Files: `main.js` holds the pane's state. `session.js` finds the session. `tail.py` reads the transcript and `longest.py` the longest sessions. `rows.js` and `thread.js` are the thread. `composer.js` and `send.js` send. `cards.js` and `dialog.js` are the cards. `commands.js`, `route.js`, `slash.js` and `usage-*.js` are the `/` commands and the usage panel. `model-control.js`, `pad.js` and `picker.js` are the model pad. `reveal.js`, `motion.js` and `covers.js` are the motion. - `herdr`: `appHome`, `exec`, `hc`, `home`, `nextCommand`, `panes`, `props`, `readTextFile`, `run`, `setShared`, `settings`, `shared`, `spaces`, `terminals`, `theme`, `watchFolder`, `writeTextFile`. - `gpui-base`: `v_virtual_list`, `VirtualListScrollHandle`, `Scrollbar`, `TextView`, `Editor`, `EditorState`, `Textarea`, `TextareaState`, `h_flex`, `v_flex`. - Commands: `pane.split`, `pane.zoom`, `pane.focus`, `pane.close`. - Shared state: `chatpane.pairs`, `{ [chat pane]: terminal address }`, because `panes()` doesn't give a mod pane's props. `chat.unseen`, `{ [terminal address]: count }`. - `hc`: `get`, `read`, `send`, `keys`, `interrupt`, `wait`. - Programs: `python3`, `zsh`, `sh`, `osascript`, `mkdir`, `cp`, `rm`, `find`, `sleep`. `zsh` starts `claude -p`. - Art: ten 1-colour SVGs in the mod folder (`arrow`, `chat`, `check`, `close`, `copy`, `cross`, `dot`, `refresh`, `terminal`, `tick`), plus `mods/files/chevron-down.svg` and `chevron-right.svg` from the shipped `files` mod. ## Watch out **macOS only.** Pasting an image uses `osascript` with AppKit, the `/` list and usage read use `zsh`, and the file steps assume `sh`, `cp --` and `find -mmin`. It has not been built for Linux or Windows. The toggle also ignores sessions on other machines. **What it runs. Warn the person before adding it.** - `python3 -I -S tail.py` on every transcript read, and `python3 -I -S longest.py` each time the usage panel opens or refreshes. - `zsh -ic` running `claude -p --no-session-persistence --input-format stream-json --output-format stream-json --verbose` with `/usage` as its message. It runs the first time `/` is typed in a folder, and each time the usage panel opens or refreshes. It loads `~/.zshrc`, and it runs that folder's Claude Code settings and hooks, plus whatever network calls Claude Code makes at start. `/usage` costs no turn. It runs only in a folder the person has already trusted, and a watchdog stops it after 20 seconds. - `osascript` to read an image from the clipboard. - `sh -c 'ls -t ...'` to find a transcript by id. - `mkdir`, `cp`, `rm` and `find` in the image folder, and `sleep` for pauses. - `hc get`, `read`, `send`, `keys`, `interrupt` and `wait` on the session's terminal. **What it reads.** - `~/.claude/sessions/