chat: box only your own messages, and make the cursor findable - #102
Conversation
The tint and pad on every message turned the transcript into a stack of
stripes with no room to read. Only what you sent is a lifted box now;
everything the agent says or does sits bare on the canvas.
- metadata reads as prose ("23s, 4 tokens", not "(23s - 4 tokens)")
- the selection highlight is gone; the cyan ▶ carries "you are here"
- a turn-opening tool run no longer branches off YOUR message, where it
read as work you did
- opening a message replaces its "Ran N ..." fold with the calls it stood
for, instead of leaving a stale count above them
- ⎿ gets a column of breathing room before its text
There was a problem hiding this comment.
Caution
4 issues detected
Reviewed f30a92a in 8 minutes, 54 seconds.
- Reviewed
1commit with584lines of code in6files - Ran
1review agent producing4comments where4were posted - This pipeline runs no gatekeeper, so findings are posted as written.
- View full details on ellipsis.dev
This review was created by . You can tag
@ellipsis in this pull request.
| if (!isToolActivity(item)) { | ||
| out.push({ item, indent: 0, nested: false, attach: false }) | ||
| parent = item.key | ||
| parent = item.kind === 'assistant' ? item.key : null |
There was a problem hiding this comment.
A turn-opening tool run becomes unreachable: its fold is a flat nav stop that → cannot open, so the calls and their output can only be seen with ctrl+r.
For [user, tool, tool_result] (the agent starting a turn with a tool call — the common Claude Code shape), collapseToolRuns yields [user, grp:tool]. With parent now null after a user message, the fold gets navKey undefined, so it is its own ↑/↓ stop. In the → handler (ConnectApp.tsx:1141-1144) hasToolRun('grp:t1') is false (no row names it as its block) and isCollapsible(notice) is false, so → is inert on it; ← is inert too (no parentKey). On main the same fold carried navKey 'u', so → on your message revealed the run. Verified against both revisions.
| out.push(item) | ||
| if (!isToolActivity(item)) { | ||
| out.push(item) | ||
| parent = item.key |
There was a problem hiding this comment.
visible still treats any non-tool item as a fold's parent while layOutItems restricts parenthood to assistant messages, so opening a long user message silently unfolds the tool run below it.
A user turn over COLLAPSE_LINES is collapsible, so → adds its key to openedKeys (ConnectApp.tsx:1142) just to un-clamp the text. Here parent is that user key, so the following grp: fold is replaced by foldRun(...) — the "Ran N shell commands" line vanishes and the raw ●/⎿ lines appear at indent 0 (layOutItems keeps them flat and un-nested, since its own parent is null). Confirmed by replaying this memo over [user(long), tool, tool_result] with openedKeys={'u'}.
| parent = item.key | |
| parent = item.kind === 'assistant' ? item.key : null |
| // ("Ran 2 tool calls") takes the branch glyph — as a notice it would | ||
| // otherwise wear ✦, the mark for the infrastructure speaking, which is not | ||
| // what a fold is. | ||
| const gutter = opts.nested && item.kind === 'notice' ? BRANCH_GLYPH : gutterFor(item) |
There was a problem hiding this comment.
A flat (turn-opening) fold now wears ✦ instead of ⎿, the exact case the comment two lines above says must not happen; key the glyph on the fold itself, not on nested.
Turn-opening runs are no longer nested, so opts.nested is false for their fold and gutterFor(notice) returns '✦' — the infrastructure mark. Ran itemRows over collapseToolRuns([user, tool, tool_result, assistant, tool, tool_result]): the first fold renders '✦ Ran 1 shell command', the second '⎿ Ran 1 shell command'.
| const gutter = opts.nested && item.kind === 'notice' ? BRANCH_GLYPH : gutterFor(item) | |
| const gutter = item.key.startsWith('grp:') ? BRANCH_GLYPH : gutterFor(item) |
| : row.panel | ||
| ? SURFACE_ELEVATED | ||
| : undefined | ||
| const background = row.panel ? SURFACE_ELEVATED : undefined |
There was a problem hiding this comment.
With the tint gone, a block taller than the chat window shows no cursor at all: the only row that can carry ▶ is scrolled off-frame.
markerRowId is the block's FIRST row with a gutter, and ↑ onto a too-tall entry calls ensureVisible(key, -1) → snapToEntry bottom-aligns it, so that first row sits above view.start. Assistant prose isn't collapsible, so there is no "+N lines (→ to expand)" hint on screen either — nothing on the frame indicates which block is selected until several more ↑ presses scroll its top back into view.
Separating "which message does this run BELONG to" from "does it indent under that message" is the root fix for the first three: I had collapsed both onto one variable, so refusing to indent under your message also took away the run's owner. - a turn-opening run belongs to your message again, so → opens it and ← steps back out; it just doesn't branch off it visually - opening a long user message no longer unfolds the run below it as a side effect of un-clamping the text - a flat fold still wears ⎿, not the ✦ infrastructure mark - the ▶ falls to the topmost VISIBLE row of the selected block: with no highlight bar left, a block taller than the window showed no cursor
There was a problem hiding this comment.
Caution
Changes requested ❌ — 1 issue
Incrementally reviewed 96b6463 in 10 minutes, 15 seconds.
- Reviewed
1commit with114lines of code in3files - Ran
1review agent producing1comment where1was posted - This pipeline runs no gatekeeper, so findings are posted as written.
- View full details on ellipsis.dev
This review was created by . You can tag
@ellipsis in this pull request.
| if (!isToolActivity(item)) { | ||
| out.push({ item, indent: 0, nested: false, attach: false }) | ||
| parent = item.key | ||
| parentIsAgent = item.kind === 'assistant' |
There was a problem hiding this comment.
parentIsAgent is false for thinking items, so a tool run that follows a thinking block never nests — with extended thinking on that is nearly every run, and the ⎿ hierarchy this PR is built around disappears.
Claude Code's usual turn shape is thinking → tool_use → tool_result (eventToItems emits kind: 'thinking' for every thinking block). layOutItems([think('th'), fold('t1')]) returns {indent: 0, nested: false, attach: false} for the fold, where main returned {indent: 2, nested: true, attach: true}. The stated reason for staying flat is that a ⎿ branch under YOUR lifted box reads as work you did — that does not apply to a ✻ thinking line, which is the agent. Note the live-tail counterpart at ConnectApp.tsx:816 (nested: said?.kind === 'assistant') needs the same predicate, or the running ✻ line will sit flat and then jump right one level when its ⎿ result lands.
| parentIsAgent = item.kind === 'assistant' | |
| parentIsAgent = item.kind === 'assistant' || item.kind === 'thinking' |
With extended thinking on, thinking -> tool_use -> tool_result is the usual turn shape, so treating thinking as not-the-agent flattened almost every run and took the hierarchy with it. Both the committed rows and the live activity line now share one isAgentSpeech predicate; when they disagreed, the running line sat flat and jumped a level as its result landed.
There was a problem hiding this comment.
Important
Looks good to me! 👍
Incrementally reviewed 540d640 in 4 minutes.
- Reviewed
1commit with77lines of code in3files - Ran
1review agent producing0comments where0were posted - This pipeline runs no gatekeeper, so findings are posted as written.
- View full details on ellipsis.dev
This review was created by . You can tag
@ellipsis in this pull request.
Important
Changes chat display to reduce visual clutter and clarify message ownership: only your messages get the lifted panel box; agent output stays on the canvas.
▶cursor marker in the gutter; the full-row highlight is removed. All rows of the selected block remain navigable with→hints.→replaces the collapsed "Ran N tool calls" fold with the actual calls it stood for, instead of keeping a stale count above them."23s, 4 tokens") instead of parenthesized asides ("(23s) · ↓ 4 tokens").⎿) now get extra padding before their text to prevent the body from reading as touching the mark.cursor: '#5fd3e0'(cyan) to theme as the sole color for selection; emphasis is now carried by hue (brightness alone was too quiet on busy frames).This description was created by
for 540d640. It will automatically update as commits are pushed.