agent-ui: real fixed-width icon column for terminal rows
Replaces the first-character-glyph + negative-text-indent trick (which let a wide emoji or a leading disclosure caret knock the icon out of column) with a genuine icon cell. terminal.js: row() / details() / detailsDiff() take an optional `icon` that goes in a fixed-width `.row-glyph` element (inline-block, 1.4em). Details summaries wrap their text in a `.summary-text` span; the disclosure caret moves to `.summary-text::before` so it leads the text, not the icon — keeping the icon in the shared column. terminal.css carries the cell + caret rules. app.js passes the per-tool emoji as `icon` for the flat tool-use row and every expandable tool summary (Write/Edit/send/ask/answer/bash) plus the 💭 thinking row, instead of string-prefixing it. A details `🖥️` now lines up under a flat row's `🧠` regardless of emoji width. Doc: terminal-rendering.md layout contract updated. Closes #1844.
This commit is contained in:
parent
0c48caff1e
commit
1c47bd4333
4 changed files with 106 additions and 50 deletions
|
|
@ -29,11 +29,14 @@
|
|||
//
|
||||
// Renderers receive (ev, api) where api exposes:
|
||||
//
|
||||
// api.row(cls, text) → appends a flat <div class="row cls">
|
||||
// api.details(cls, summary, body) → appends <details class="row cls">
|
||||
// with a <pre.tool-body>
|
||||
// api.detailsDiff(cls, summary, body) → ditto but body is line-coloured by
|
||||
// leading "+ " / "- " prefix
|
||||
// api.row(cls, text, icon?) → appends a flat <div class="row cls">;
|
||||
// optional `icon` lands in a
|
||||
// fixed-width `.row-glyph` cell
|
||||
// api.details(cls, summary, body, icon?) → appends <details class="row cls">
|
||||
// with a <pre.tool-body>; `icon` (if
|
||||
// given) shares the `.row-glyph` column
|
||||
// api.detailsDiff(cls, summary, body, icon?) → ditto but body is
|
||||
// line-coloured by leading "+ "/"- "
|
||||
// api.placeholder(text) → replaces log content with a single
|
||||
// muted "(placeholder)" row, cleared
|
||||
// on the next real row
|
||||
|
|
@ -222,24 +225,47 @@ export function create(opts) {
|
|||
log.appendChild(e);
|
||||
placeholderEl = e;
|
||||
}
|
||||
function row(cls, text) {
|
||||
// A leading icon (`→ ← 🧠 🖥️ …`) goes in a fixed-width `.row-glyph`
|
||||
// element so every row's icon lands in one column regardless of the
|
||||
// glyph's rendered width (emoji vary; some carry variation selectors).
|
||||
// Optional: callers that pass no `icon` keep the bare first-character
|
||||
// prefix the older rows rely on.
|
||||
function glyphSpan(icon) {
|
||||
const g = document.createElement('span');
|
||||
g.className = 'row-glyph';
|
||||
g.textContent = icon;
|
||||
return g;
|
||||
}
|
||||
// Build a <summary> whose icon (if any) sits in the shared `.row-glyph`
|
||||
// column and whose text lives in a `.summary-text` span — the disclosure
|
||||
// caret (CSS `.summary-text::before`) then leads the text, not the icon,
|
||||
// so the icon stays aligned with flat-row icons.
|
||||
function buildSummary(summary, icon) {
|
||||
const s = document.createElement('summary');
|
||||
if (icon != null && icon !== '') s.appendChild(glyphSpan(icon));
|
||||
const st = document.createElement('span');
|
||||
st.className = 'summary-text';
|
||||
st.textContent = summary;
|
||||
s.appendChild(st);
|
||||
return s;
|
||||
}
|
||||
function row(cls, text, icon) {
|
||||
clearPlaceholder();
|
||||
const wasNearBottom = isNearBottom();
|
||||
const e = document.createElement('div');
|
||||
e.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
||||
if (icon != null && icon !== '') e.appendChild(glyphSpan(icon));
|
||||
e.appendChild(linkify(text));
|
||||
log.appendChild(e);
|
||||
afterAppend(wasNearBottom);
|
||||
return e;
|
||||
}
|
||||
function details(cls, summary, body) {
|
||||
function details(cls, summary, body, icon) {
|
||||
clearPlaceholder();
|
||||
const wasNearBottom = isNearBottom();
|
||||
const d = document.createElement('details');
|
||||
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
||||
const s = document.createElement('summary');
|
||||
s.textContent = summary;
|
||||
d.appendChild(s);
|
||||
d.appendChild(buildSummary(summary, icon));
|
||||
const pre = document.createElement('pre');
|
||||
pre.className = 'tool-body';
|
||||
pre.appendChild(linkify(body));
|
||||
|
|
@ -248,14 +274,12 @@ export function create(opts) {
|
|||
afterAppend(wasNearBottom);
|
||||
return d;
|
||||
}
|
||||
function detailsDiff(cls, summary, body) {
|
||||
function detailsDiff(cls, summary, body, icon) {
|
||||
clearPlaceholder();
|
||||
const wasNearBottom = isNearBottom();
|
||||
const d = document.createElement('details');
|
||||
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
||||
const s = document.createElement('summary');
|
||||
s.textContent = summary;
|
||||
d.appendChild(s);
|
||||
d.appendChild(buildSummary(summary, icon));
|
||||
const pre = document.createElement('pre');
|
||||
pre.className = 'tool-body diff-body';
|
||||
for (const line of String(body).split('\n')) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue