From df76705be55cde4ad9f6738e6685471c7b753bda Mon Sep 17 00:00:00 2001 From: hbrooks Date: Thu, 20 Aug 2026 14:17:55 -0400 Subject: [PATCH] ui: trim whitespace on wrapped fact lines, align wrapped prompt lines, rename placeholder to cloud agent --- src/ui/SessionsApp.tsx | 66 ++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/src/ui/SessionsApp.tsx b/src/ui/SessionsApp.tsx index 649c311..17afed3 100644 --- a/src/ui/SessionsApp.tsx +++ b/src/ui/SessionsApp.tsx @@ -43,6 +43,7 @@ import { mergeSidebarSessions, } from '../lib/sessions' import type { ResolvedSessionBar } from '../lib/config' +import wrapAnsi from 'wrap-ansi' import { randomFact } from '../lib/facts' import { inputSurface, theme } from '../lib/theme' import { useAltScreen } from './altScreen' @@ -1137,11 +1138,14 @@ function NewSessionPane({ {/* The fact box is sized to the text (capped so long facts wrap at a readable measure) so short facts sit centered, not left-aligned - inside a fixed column. */} + inside a fixed column. Pre-wrapped with wrap-ansi rather than ink's + wrap="wrap": ink keeps whitespace at wrap points, so a line that + breaks after a double space starts with a space and looks ragged. + wrap-ansi trims it. */} - - {fact} + + {wrapAnsi(fact, Math.min(fact.length, Math.max(8, width - 4), 72))} @@ -1232,36 +1236,42 @@ function NewSessionPane({ remounts the node so a stale measurement can't wrap the caret onto the border row. */} + {/* The ❯ lives in its own fixed gutter so wrapped prompt lines + align under the first typed character, not under the glyph. */} + + + {focused && row === 'prompt' && openPicker === null ? SELECTION_GLYPH : ' '} + + {/* The colour on the parent is what the bare text children below inherit (ink would leave typed text on the terminal's default foreground, which a light theme paints near-black on our panel), and it gives the inverse caret a known pair to swap. */} - - - {focused && row === 'prompt' && openPicker === null ? SELECTION_GLYPH : ' '}{' '} + + + {text.slice(0, cursor)} + {caretVisible && text !== '' && ( + {cursor < text.length ? text[cursor] : ' '} + )} + {cursor < text.length ? text.slice(cursor + (caretVisible ? 1 : 0)) : ''} + {/* Empty input: the placeholder sits where typed text will land, + its first character carrying the caret (inverse) instead of a + caret cell of its own pushing it a column right. */} + {text === '' && caretVisible && ( + + S + tart a cloud agent… + + )} + {text === '' && !caretVisible && ( + Start a cloud agent… + )} - {text.slice(0, cursor)} - {caretVisible && text !== '' && ( - {cursor < text.length ? text[cursor] : ' '} - )} - {cursor < text.length ? text.slice(cursor + (caretVisible ? 1 : 0)) : ''} - {/* Empty input: the placeholder sits where typed text will land, - its first character carrying the caret (inverse) instead of a - caret cell of its own pushing it a column right. */} - {text === '' && caretVisible && ( - - S - tart a cloud session… - - )} - {text === '' && !caretVisible && ( - Start a cloud session… - )} - +