From e5b6ea4cea152e64300b2155b3730186eac00457 Mon Sep 17 00:00:00 2001 From: Griffin Yourick Date: Fri, 28 Aug 2026 19:44:17 -0400 Subject: [PATCH 1/2] fix: drop spacing separator stranded against a flex separator When the widget between a spacing-only separator and a flex-separator renders nothing, the separator survived the backward-only collapse logic. In the no-width flex fallback (piped/no-TTY, the common case under Claude Code) this produced a visible double space before the fallback " | ". Add a pass after trailing-separator trimming that removes spacing-only separators sitting directly against a flex-separator in the assembled elements array. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Wu2bixWqAAfsMR72ttgZQA (cherry picked from commit bc6b5c36a19e6b58fb0cf00292394215f9cc5ad4) --- .../renderer-separator-collapse.test.ts | 17 +++++++++++++++++ src/utils/renderer.ts | 14 ++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/utils/__tests__/renderer-separator-collapse.test.ts b/src/utils/__tests__/renderer-separator-collapse.test.ts index b5188e1c..aa8c7805 100644 --- a/src/utils/__tests__/renderer-separator-collapse.test.ts +++ b/src/utils/__tests__/renderer-separator-collapse.test.ts @@ -199,6 +199,23 @@ describe('renderer separator collapse around empty widgets', () => { expect(out).toContain('C'); }); + it('drops a spacing separator stranded against a flex separator when the widget between renders empty', () => { + const space: WidgetItem = { id: 'space', type: 'separator', character: ' ' }; + const widgets: WidgetItem[] = [ + T('a'), + space, + T('b'), + { id: 'flex', type: 'flex-separator' }, + T('c') + ]; + const settings = createSettings({ colorLevel: 0 }); + const context: RenderContext = { isPreview: false, terminalWidth: 0 }; + const preRenderedWidgets = makePreRendered(widgets, { 0: 'A', 2: '', 4: 'C' }); + const out = stripSgrCodes(renderStatusLine(widgets, settings, context, preRenderedWidgets, [])); + + expect(out).toBe('A | C'); + }); + it('does not borrow visible content across a flex separator', () => { const widgets: WidgetItem[] = [ T('left'), diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index f48982e7..65fed9f2 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -1132,6 +1132,20 @@ export function renderStatusLine( elements.pop(); } + // Drop spacing-only separators that ended up directly against a flex-separator. + // The flex-separator owns that gap (it expands to fill the line, or falls back + // to its own ' | ' when terminal width is unknown), so an adjacent space just + // doubles up. This happens when the widget between them renders nothing. + for (let i = elements.length - 1; i >= 0; i--) { + if (elements[i]?.type !== 'separator' + || !isSpacingSeparator(elements[i]?.widget, settings.defaultSeparator)) { + continue; + } + if (elements[i - 1]?.type === 'flex-separator' || elements[i + 1]?.type === 'flex-separator') { + elements.splice(i, 1); + } + } + // Apply default padding and separators const finalElements: string[] = []; const padding = settings.defaultPadding ?? ''; From ead15e4f2a9841d87f48d64d900040ba7ab9e57f Mon Sep 17 00:00:00 2001 From: Matthew Breedlove Date: Thu, 3 Sep 2026 14:16:03 -0400 Subject: [PATCH 2/2] fix(renderer): preserve saturated flex boundaries Collapse spacing-only separators beside flex separators only when terminal width is unavailable and flex rendering falls back to its own pipe boundary. For known widths, a flex separator can receive zero columns when visible content fills the line. Retaining the explicit spacing separator prevents adjacent widget output from running together before truncation. Add regression coverage for a ten-column effective width while preserving the existing no-width fallback assertion. --- .../renderer-separator-collapse.test.ts | 18 +++++++++++++ src/utils/renderer.ts | 25 +++++++++++-------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/utils/__tests__/renderer-separator-collapse.test.ts b/src/utils/__tests__/renderer-separator-collapse.test.ts index aa8c7805..8fd7d2c7 100644 --- a/src/utils/__tests__/renderer-separator-collapse.test.ts +++ b/src/utils/__tests__/renderer-separator-collapse.test.ts @@ -216,6 +216,24 @@ describe('renderer separator collapse around empty widgets', () => { expect(out).toBe('A | C'); }); + it('keeps a stranded spacing separator when a known-width flex separator allocates no space', () => { + const space: WidgetItem = { id: 'space', type: 'separator', character: ' ' }; + const widgets: WidgetItem[] = [ + T('a'), + space, + T('hidden'), + { id: 'flex', type: 'flex-separator' }, + T('c') + ]; + const settings = createSettings({ colorLevel: 0, flexMode: 'full' }); + // Full mode reserves six columns, leaving a ten-column render width. + const context: RenderContext = { isPreview: false, terminalWidth: 16 }; + const preRenderedWidgets = makePreRendered(widgets, { 0: 'AAAAA', 2: '', 4: 'CCCCC' }); + const out = stripSgrCodes(renderStatusLine(widgets, settings, context, preRenderedWidgets, [])); + + expect(out).toBe('AAAAA C...'); + }); + it('does not borrow visible content across a flex separator', () => { const widgets: WidgetItem[] = [ T('left'), diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index 93276d1f..d7bbae3c 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -1196,17 +1196,20 @@ export function renderStatusLine( elements.pop(); } - // Drop spacing-only separators that ended up directly against a flex-separator. - // The flex-separator owns that gap (it expands to fill the line, or falls back - // to its own ' | ' when terminal width is unknown), so an adjacent space just - // doubles up. This happens when the widget between them renders nothing. - for (let i = elements.length - 1; i >= 0; i--) { - if (elements[i]?.type !== 'separator' - || !isSpacingSeparator(elements[i]?.widget, settings.defaultSeparator)) { - continue; - } - if (elements[i - 1]?.type === 'flex-separator' || elements[i + 1]?.type === 'flex-separator') { - elements.splice(i, 1); + // When width detection fails, flex separators fall back to their own ' | ' + // boundary. Drop any spacing-only separator stranded directly beside one so + // that fallback does not render a duplicate space. With a known width, keep + // the separator: a fully occupied line can leave the flex gap at zero columns, + // making this space the only boundary between the surrounding content. + if (!terminalWidth) { + for (let i = elements.length - 1; i >= 0; i--) { + if (elements[i]?.type !== 'separator' + || !isSpacingSeparator(elements[i]?.widget, settings.defaultSeparator)) { + continue; + } + if (elements[i - 1]?.type === 'flex-separator' || elements[i + 1]?.type === 'flex-separator') { + elements.splice(i, 1); + } } }