agent-manager · pr #107 · reader

The last raw text in the reader

The reader rendered everything except the thing a person wrote. Answers have always been markdown; asides, thinking and compactions since #88. The prompt band was still plain text, so a typed **important** or a pasted fence showed its syntax. Every screen below is the running app.

The four shapes a prompt takes

Tight crops of the band itself, at the size it renders. The pixel numbers are measured in a browser by the new test, not eyeballed here.

One line — the common case, and the one that must not move

A one-line prompt
32px tall in this pane, exactly what it was as raw text: the first and last rendered block give up their margins, so a paragraph does not add a fraction of a line to every turn.

Several lines — where someone pressed Enter

A multi-line prompt
Rendered with breaks on: three <br>, 66px, the same height the band had with pre-wrap. Strict markdown would have folded this into one paragraph — a silent reflow of every multi-line prompt in the reader.

A fence and a list — the case the band was not designed for

A prompt with a code fence and a bulleted list
Five blocks: paragraph, fence, paragraph, list, paragraph. The fence is a slab in the band's own tint rather than a white card dropped into it, the list indents from the prompt's column, and the text still starts on the same pixel as the one-liner above.

Queued — the pill still trails the last line

A queued prompt with the queued pill
32px, same as any one-liner. The rendered block is an inline-block, so the pill sits on the last line's baseline instead of dropping below it and making queued prompts a line taller than the rest.

All of it in one reader

The point of the band is that you find turns by it while scrolling. Four different prompt shapes, one left edge — the arrow in the gutter, the words on the text column, and the answer below on that same column.

The reader with several rendered prompts
Plain, multi-line, fence-and-list and queued, in sequence.
The same reader in dark
Dark: the fence takes 5% of the text colour, so it stays part of the band in both themes.
An Overview card with a rendered prompt
The Overview card renders the same band from the same component — nothing to keep in sync.

Search lands in it, through the markdown

The prompt goes through the answer's exact path — highlightHtml(renderMarkdown(...)) — rather than a second highlighting mechanism that agrees with the first until it doesn't. The term is marked inside the rendered HTML, so a hit inside bold stays bold.

A search term highlighted inside a rendered prompt
Two hits in prompts for yesterday; one of them is inside a <strong>, which is how you know the highlight went through the render and not over it.

And the prompt you just sent

The optimistic echo — the band that appears before the transcript catches up — is the same component. That pairing is what #77 was about: written out by hand twice, the echo lost the wrapper whose padding cancels the band's negative margin, and the newest prompt in every conversation hung a gutter's width to the left. There is one band in the file now, used twice.

A markdown reply echoed before the transcript catches up
A list with a code span and bold, typed into the reply line, rendered in the echo the instant it is sent.

What was measured

A browser test builds the real component — real marked, real DOMPurify — renders it inside the reader's own frame, and compares it against the band as it was before this change.

whatraw textrendered
text left edge29.984375px29.984375px
band left / right edge14 / 640px14 / 640px
one-line band height30px30px
four typed lines75px75px
queued one-liner30px30px
column scroll width626 of 612626 of 612

The column overflows its client width by the gutter in both cases: the band is full-bleed by design, and that number is what a wide prompt must not move. Each assertion was checked by breaking the rule it depends on — default last-child margin, no inline-block, no max-width, no breaks, no font-size reset, no highlight, a fence that scrolls — and confirming the right checks fail.

Two judgement calls, since the band was designed for one line

A fence in a prompt wraps; a fence in an answer scrolls. This band held the operator's own text as pre-wrap until now, so every character of a pasted error was on screen. Trading that for a horizontal swipe inside a slab is the same mistake as putting something behind a hover — and it is worst on a phone, where the band is widest relative to the screen. An answer's fence is code an agent wrote, where the line structure is the content and reflowing it lies about it; a prompt's fence is a paste.

The rendered prompt band at phone width
420px wide: the pasted error wraps to four lines and nothing is hidden. The queued pill does drop below on a prompt that wraps — the guarantee is about the one-line case, which is the one that repeats down the whole reader.

Nothing is capped. A prompt with a 200-line paste makes a tall tinted band. It already did as raw text, and the answer's own fences are uncapped too, so a cap here would be new behaviour rather than preserved behaviour. If it turns out to bite, the cap belongs on both.

What did not change

  • The band's geometry: margin-left: -1.23em against .cx's padding, the in the gutter, full bleed to the right.
  • Where prompts get their text from — trace parsing, the queue records, attachment display text: untouched.
  • The answer's rendering. breaks is passed by the prompt band alone, asserted by a test that reads the answer's own call.
  • Safety: prompts go through the same DOMPurify sanitiser as agent output, which is why raw HTML in a prompt renders as HTML but a <script> or an onerror does not survive.