From b2754a0ef4da4be8ed793742ee993e6305c54cdf Mon Sep 17 00:00:00 2001 From: Steven Chen Date: Fri, 1 May 2026 20:19:01 +0800 Subject: [PATCH 1/6] fix: default to full flex mode and make multi-line alignment independent per line - Change default flexMode from 'full-minus-40' to 'full' so status line content is not unnecessarily truncated for new/default configurations - Refactor calculateMaxWidthsFromPreRendered to return number[][] instead of number[], giving each line its own independent column widths so that a wide widget on one line no longer forces the same column wider on other lines when autoAlign is enabled Co-Authored-By: Claude Sonnet 4 --- src/ccstatusline.ts | 2 +- src/tui/components/StatusLinePreview.tsx | 2 +- src/types/Settings.ts | 2 +- src/utils/__tests__/renderer-ansi.test.ts | 2 +- .../__tests__/renderer-flex-width.test.ts | 2 +- .../renderer-powerline-theme.test.ts | 2 +- src/utils/renderer.ts | 23 +++++++++++-------- 7 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/ccstatusline.ts b/src/ccstatusline.ts index be4928910..5b9d27924 100644 --- a/src/ccstatusline.ts +++ b/src/ccstatusline.ts @@ -198,7 +198,7 @@ async function renderMultipleLines(data: StatusJSON) { globalPowerlineThemeIndex, globalPowerlineStartCapIndex }; - let line = renderStatusLine(lineItems, settings, lineContext, preRenderedWidgets, preCalculatedMaxWidths); + let line = renderStatusLine(lineItems, settings, lineContext, preRenderedWidgets, preCalculatedMaxWidths[i] ?? []); // Only output the line if it has content (not just ANSI codes) // Strip ANSI codes to check if there's actual text diff --git a/src/tui/components/StatusLinePreview.tsx b/src/tui/components/StatusLinePreview.tsx index 945a3d013..eb6e413ec 100644 --- a/src/tui/components/StatusLinePreview.tsx +++ b/src/tui/components/StatusLinePreview.tsx @@ -100,7 +100,7 @@ export const StatusLinePreview: React.FC = ({ lines, ter globalPowerlineThemeIndex, globalPowerlineStartCapIndex, preRenderedWidgets, - preCalculatedMaxWidths + preCalculatedMaxWidths[i] ?? [] ); result.push(renderResult.line); if (renderResult.wasTruncated) { diff --git a/src/types/Settings.ts b/src/types/Settings.ts index a596dbcf1..688982c3b 100644 --- a/src/types/Settings.ts +++ b/src/types/Settings.ts @@ -63,7 +63,7 @@ export const SettingsSchema = z.object({ [], [] ]), // Ensure max 3 lines - flexMode: FlexModeSchema.default('full-minus-40'), + flexMode: FlexModeSchema.default('full'), compactThreshold: z.number().min(1).max(99).default(60), colorLevel: ColorLevelSchema.default(2), defaultSeparator: z.string().optional(), diff --git a/src/utils/__tests__/renderer-ansi.test.ts b/src/utils/__tests__/renderer-ansi.test.ts index 5302b9cc8..64bc91cd8 100644 --- a/src/utils/__tests__/renderer-ansi.test.ts +++ b/src/utils/__tests__/renderer-ansi.test.ts @@ -55,7 +55,7 @@ function renderLine( const preCalculatedMaxWidths = calculateMaxWidthsFromPreRendered(preRenderedLines, settings); const preRenderedWidgets = preRenderedLines[0] ?? []; - return renderStatusLine(widgets, settings, context, preRenderedWidgets, preCalculatedMaxWidths); + return renderStatusLine(widgets, settings, context, preRenderedWidgets, preCalculatedMaxWidths[0] ?? []); } describe('renderer ANSI/OSC handling', () => { diff --git a/src/utils/__tests__/renderer-flex-width.test.ts b/src/utils/__tests__/renderer-flex-width.test.ts index 25d949151..ae47b9e65 100644 --- a/src/utils/__tests__/renderer-flex-width.test.ts +++ b/src/utils/__tests__/renderer-flex-width.test.ts @@ -50,7 +50,7 @@ function renderLine( const preCalculatedMaxWidths = calculateMaxWidthsFromPreRendered(preRenderedLines, settings); const preRenderedWidgets = preRenderedLines[0] ?? []; - return renderStatusLine(widgets, settings, context, preRenderedWidgets, preCalculatedMaxWidths); + return renderStatusLine(widgets, settings, context, preRenderedWidgets, preCalculatedMaxWidths[0] ?? []); } describe('renderer flex width behavior', () => { diff --git a/src/utils/__tests__/renderer-powerline-theme.test.ts b/src/utils/__tests__/renderer-powerline-theme.test.ts index 0635cb2ca..2fb8b3165 100644 --- a/src/utils/__tests__/renderer-powerline-theme.test.ts +++ b/src/utils/__tests__/renderer-powerline-theme.test.ts @@ -46,7 +46,7 @@ function renderLine(settings: Settings, globalPowerlineThemeIndex: number): stri const preCalculatedMaxWidths = calculateMaxWidthsFromPreRendered(preRenderedLines, settings); const preRenderedWidgets = preRenderedLines[0] ?? []; - return renderStatusLine(widgets, settings, context, preRenderedWidgets, preCalculatedMaxWidths); + return renderStatusLine(widgets, settings, context, preRenderedWidgets, preCalculatedMaxWidths[0] ?? []); } describe('renderer powerline theme carry-over', () => { diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index f48982e73..58b7e5f14 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -838,20 +838,25 @@ export function preRenderAllWidgets( return preRenderedLines; } -// Calculate max widths from pre-rendered widgets for alignment +// Calculate max widths per line from pre-rendered widgets for alignment. +// Each line gets its own independent widths so a wide cell on one line does +// not force the same column wider on other lines. export function calculateMaxWidthsFromPreRendered( preRenderedLines: PreRenderedWidget[][], settings: Settings -): number[] { - const maxWidths: number[] = []; +): number[][] { const defaultPadding = settings.defaultPadding ?? ''; const { leading: sideLeadingPadding, trailing: sideTrailingPadding } = resolvePaddingSides(defaultPadding, settings.defaultPaddingSide); const paddingPairLength = sideLeadingPadding.length + sideTrailingPadding.length; - for (const preRenderedLine of preRenderedLines) { + return preRenderedLines.map(preRenderedLine => { + const lineMaxWidths: number[] = []; const isSeparatorBoundary = (entry: PreRenderedWidget | undefined): boolean => ( entry?.widget.type === 'separator' || entry?.widget.type === 'flex-separator' ); + const filteredWidgets = preRenderedLine.filter( + w => w.widget.type !== 'separator' && w.widget.type !== 'flex-separator' && w.content + ); const hasNextRenderedWidgetBeforeSeparator = (originalIndex: number): boolean => { for (let j = originalIndex + 1; j < preRenderedLine.length; j++) { const nextEntry = preRenderedLine[j]; @@ -905,20 +910,20 @@ export function calculateMaxWidthsFromPreRendered( } } - const currentMax = maxWidths[alignmentPos]; + const currentMax = lineMaxWidths[alignmentPos]; if (currentMax === undefined) { - maxWidths[alignmentPos] = totalWidth; + lineMaxWidths[alignmentPos] = totalWidth; } else { - maxWidths[alignmentPos] = Math.max(currentMax, totalWidth); + lineMaxWidths[alignmentPos] = Math.max(currentMax, totalWidth); } // Skip over merged widgets since we've already processed them i = j; alignmentPos++; } - } - return maxWidths; + return lineMaxWidths; + }); } export function renderStatusLineWithInfo( From 46b7f031bf17c9aecb325c292910f8f17152ad08 Mon Sep 17 00:00:00 2001 From: Steven Chen Date: Fri, 1 May 2026 20:41:00 +0800 Subject: [PATCH 2/6] chore: add install.sh for local build and Claude Code settings wiring Co-Authored-By: Claude Sonnet 4 --- install.sh | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100755 install.sh diff --git a/install.sh b/install.sh new file mode 100755 index 000000000..1b980367d --- /dev/null +++ b/install.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DIST="$SCRIPT_DIR/dist/ccstatusline.js" +SETTINGS="$HOME/.claude/settings.json" + +echo "==> Installing ccstatusline from $SCRIPT_DIR" + +# Build +echo "==> Building..." +bun install --frozen-lockfile +bun run build + +# Patch settings.json +if [ ! -f "$SETTINGS" ]; then + echo "ERROR: $SETTINGS not found. Is Claude Code installed?" + exit 1 +fi + +COMMAND="node $DIST" + +# Use python3 to safely edit JSON +python3 - "$SETTINGS" "$COMMAND" <<'EOF' +import json, sys +path, cmd = sys.argv[1], sys.argv[2] +with open(path) as f: + s = json.load(f) +s.setdefault('statusLine', {}) +s['statusLine']['type'] = 'command' +s['statusLine']['command'] = cmd +s['statusLine'].setdefault('padding', 0) +with open(path, 'w') as f: + json.dump(s, f, indent=2) +print(f" statusLine.command => {cmd}") +EOF + +echo "==> Done. Restart Claude Code to apply." From 8686e13830a9de593defe7b7b91ce8cda14d3cab Mon Sep 17 00:00:00 2001 From: Steven Chen Date: Fri, 1 May 2026 20:41:46 +0800 Subject: [PATCH 3/6] docs: add local build from source section to README Co-Authored-By: Claude Sonnet 4 --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index e7ce83c1e..0b83feb63 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,15 @@ bunx -y ccstatusline@latest Both commands launch the same TUI. During the initial setup flow, choose **Pinned global install** if you want Claude Code to stay on the ccstatusline version you are running instead of following `@latest`; the TUI will install that version globally with npm or Bun and write the pinned `ccstatusline` command to Claude Code settings. After a pinned install, you can run `ccstatusline` directly to launch the TUI in the future. +### Build from source (for custom/forked versions): + +```bash +git clone git@github.com:godlockin/ccstatusline.git +cd ccstatusline +./install.sh +``` + +`install.sh` builds the project with Bun and automatically wires the local binary into `~/.claude/settings.json`. Restart Claude Code after running it.
Configure ccstatusline From 497049a2e300ed2d0f3132a7e5fdfe7615b13269 Mon Sep 17 00:00:00 2001 From: Steven Chen Date: Fri, 8 May 2026 23:36:13 +0800 Subject: [PATCH 4/6] chore: add upstream sync workflow and gitignore .claude Co-Authored-By: Claude Opus 4.7 --- .github/workflows/sync-upstream.yml | 61 +++++++++++++++++++++++++++++ .gitignore | 1 + 2 files changed, 62 insertions(+) create mode 100644 .github/workflows/sync-upstream.yml diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 000000000..a18bbc9c2 --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,61 @@ +name: Sync Upstream + +on: + schedule: + - cron: '0 8 * * *' + workflow_dispatch: + +permissions: + contents: write + issues: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Fetch upstream + run: | + git remote add upstream https://github.com/sirmalloc/ccstatusline.git + git fetch upstream main + + - name: Merge upstream/main + id: merge + continue-on-error: true + run: | + git merge upstream/main -m "chore: sync upstream/main" + echo "conflict=false" >> $GITHUB_OUTPUT + + - name: Push to origin + if: steps.merge.outcome == 'success' + run: git push origin main + + - name: Handle conflict + if: steps.merge.outcome == 'failure' + uses: actions/github-script@v7 + with: + script: | + const { data: issues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: ['sync-conflict'], + }); + if (issues.length === 0) { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '⚠️ Upstream sync conflict', + body: 'Merge conflict from `sirmalloc/ccstatusline`. Resolve:\n```bash\ngit checkout main\ngit pull upstream main\n# fix conflicts\ngit push origin main\n```', + labels: ['sync-conflict'], + }); + } diff --git a/.gitignore b/.gitignore index ece75eb45..281bd2fce 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Finder (MacOS) folder config .DS_Store +.claude/ From 786d45f78eb7c78aee7394f4ee933cea96bf97d3 Mon Sep 17 00:00:00 2001 From: Steven Chen Date: Fri, 31 Jul 2026 20:42:36 +0800 Subject: [PATCH 5/6] chore: adapt tests to per-line max widths after upstream sync Rebase onto upstream/main (bbd1931..83c8ffd, v2.2.27). calculateMaxWidthsFromPreRendered now returns number[][] (one row per line) instead of a flat number[]; update tests that consumed the old shape. --- .../__tests__/ImportPreviewDialog.test.ts | 2 +- src/utils/__tests__/gradient.test.ts | 4 +- src/utils/__tests__/renderer-dim.test.ts | 2 +- .../renderer-exclude-auto-align.test.ts | 49 ++++++++++++++----- .../__tests__/renderer-flex-width.test.ts | 14 +++--- .../__tests__/renderer-padding-side.test.ts | 6 +-- src/utils/renderer.ts | 5 +- 7 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/tui/components/__tests__/ImportPreviewDialog.test.ts b/src/tui/components/__tests__/ImportPreviewDialog.test.ts index 4034c8966..18d70d6cf 100644 --- a/src/tui/components/__tests__/ImportPreviewDialog.test.ts +++ b/src/tui/components/__tests__/ImportPreviewDialog.test.ts @@ -130,7 +130,7 @@ describe('ImportPreviewDialog helpers', () => { try { await flushInk(); - expect(stdout.getOutput()).toContain('flexMode: full → full-minus-40'); + expect(stdout.getOutput()).toContain('globalBold: false → true'); stdout.clearOutput(); stdin.write('\u001B[B'); diff --git a/src/utils/__tests__/gradient.test.ts b/src/utils/__tests__/gradient.test.ts index af4ece090..f86e3f4a4 100644 --- a/src/utils/__tests__/gradient.test.ts +++ b/src/utils/__tests__/gradient.test.ts @@ -275,7 +275,7 @@ describe('renderStatusLine with a gradient override', () => { const context: RenderContext = { isPreview: false, terminalWidth }; const preRenderedLines = preRenderAllWidgets([widgets], settings, context); const preCalculatedMaxWidths = calculateMaxWidthsFromPreRendered(preRenderedLines, settings); - return renderStatusLine(widgets, settings, context, preRenderedLines[0] ?? [], preCalculatedMaxWidths); + return renderStatusLine(widgets, settings, context, preRenderedLines[0] ?? [], preCalculatedMaxWidths[0] ?? []); } function renderLineWithInfo(widgets: WidgetItem[], settingsOverrides: Partial = {}, terminalWidth = 200) { @@ -283,7 +283,7 @@ describe('renderStatusLine with a gradient override', () => { const context: RenderContext = { isPreview: false, terminalWidth }; const preRenderedLines = preRenderAllWidgets([widgets], settings, context); const preCalculatedMaxWidths = calculateMaxWidthsFromPreRendered(preRenderedLines, settings); - return renderStatusLineWithInfo(widgets, settings, context, preRenderedLines[0] ?? [], preCalculatedMaxWidths); + return renderStatusLineWithInfo(widgets, settings, context, preRenderedLines[0] ?? [], preCalculatedMaxWidths[0] ?? []); } const widgets: WidgetItem[] = [ diff --git a/src/utils/__tests__/renderer-dim.test.ts b/src/utils/__tests__/renderer-dim.test.ts index 57293f5a9..565e2ce66 100644 --- a/src/utils/__tests__/renderer-dim.test.ts +++ b/src/utils/__tests__/renderer-dim.test.ts @@ -56,7 +56,7 @@ function renderLine( const preCalculatedMaxWidths = calculateMaxWidthsFromPreRendered(preRenderedLines, settings); const preRenderedWidgets = preRenderedLines[0] ?? []; - return renderStatusLine(widgets, settings, context, preRenderedWidgets, preCalculatedMaxWidths); + return renderStatusLine(widgets, settings, context, preRenderedWidgets, preCalculatedMaxWidths[0] ?? []); } describe('applyColors dim handling', () => { diff --git a/src/utils/__tests__/renderer-exclude-auto-align.test.ts b/src/utils/__tests__/renderer-exclude-auto-align.test.ts index 098dcd8ef..7ac50fb9e 100644 --- a/src/utils/__tests__/renderer-exclude-auto-align.test.ts +++ b/src/utils/__tests__/renderer-exclude-auto-align.test.ts @@ -10,11 +10,9 @@ import { type Settings } from '../../types/Settings'; import type { WidgetItem } from '../../types/Widget'; -import { getVisibleWidth } from '../ansi'; import { calculateMaxWidthsFromPreRendered, preRenderAllWidgets, - renderStatusLine, type PreRenderedWidget } from '../renderer'; @@ -41,8 +39,8 @@ function text(content: string, extra: Partial = {}): WidgetItem { describe('calculateMaxWidthsFromPreRendered with excludeFromAutoAlign', () => { it.each([ - { name: 'lets a wide widget inflate the shared column by default', exclude: false, expected: [5, 14] }, - { name: 'drops an excluded widget and the rest of its line', exclude: true, expected: [5, 1] } + { name: 'lets a wide widget inflate the shared column by default', exclude: false, expected: [[5, 14], [1, 1]] }, + { name: 'drops an excluded widget and the rest of its line', exclude: true, expected: [[5], [1, 1]] } ])('$name', ({ exclude, expected }) => { const lines = [ [pre('short'), pre('VERYLONGWIDGET', exclude ? { excludeFromAutoAlign: true } : {})], @@ -58,7 +56,7 @@ describe('calculateMaxWidthsFromPreRendered with excludeFromAutoAlign', () => { [pre('AAAAA'), pre('BBBBB'), pre('CCCCC')] ]; - expect(calculateMaxWidthsFromPreRendered(lines, createSettings())).toEqual([5, 5, 5]); + expect(calculateMaxWidthsFromPreRendered(lines, createSettings())).toEqual([[1], [5, 5, 5]]); }); it('ignores exclusions on widgets merged into a previous widget', () => { @@ -81,25 +79,50 @@ describe('calculateMaxWidthsFromPreRendered with excludeFromAutoAlign', () => { [pre('x'), pre('y')] ]; - expect(calculateMaxWidthsFromPreRendered(lines, createSettings())).toEqual([1, 1]); + expect(calculateMaxWidthsFromPreRendered(lines, createSettings())).toEqual([[], [1, 1]]); }); }); describe('renderStatusLine auto-align exemption', () => { + // Per-line max widths: each line computes independent widths. Auto-alignment + // within a line is controlled by excludeFromAutoAlign, which stops column + // computation for the rest of that line. const settings = createSettings({ powerline: { ...DEFAULT_SETTINGS.powerline, enabled: true, autoAlign: true } }); - function renderFirstLine(exclude: boolean): string { + it('computes independent max widths per line (no cross-line sharing)', () => { const lines = [ - [text('a'), text('y', exclude ? { excludeFromAutoAlign: true } : {}), text('z')], - [text('AAAAA'), text('BBBBB'), text('CCCCC')] + [text('a'), text('b')], + [text('AAAAA'), text('BBBBB')] ]; const context: RenderContext = { isPreview: false, terminalWidth: 200, lineIndex: 0 }; const preRendered = preRenderAllWidgets(lines, settings, context); const maxWidths = calculateMaxWidthsFromPreRendered(preRendered, settings); - return renderStatusLine(lines[0] ?? [], settings, context, preRendered[0] ?? [], maxWidths); - } + // Each line independent — line 0 stays narrow, line 1 stays wide + expect(maxWidths).toEqual([[1, 1], [5, 5]]); + }); - it('exempts the excluded widget and the rest of its line from alignment padding', () => { - expect(getVisibleWidth(renderFirstLine(false))).toBeGreaterThan(getVisibleWidth(renderFirstLine(true))); + it('per-line max widths keep each line independent (cross-line sharing removed)', () => { + // line 0 has 'a' (1), line 1 has 'AAAAA' (5). With per-line widths, + // line 0 stays at 1 and line 1 stays at 5 — no cross-line inflation. + const lines = [ + [text('a')], + [text('AAAAA')] + ]; + const context: RenderContext = { isPreview: false, terminalWidth: 200, lineIndex: 0 }; + const preRendered = preRenderAllWidgets(lines, settings, context); + const maxWidths = calculateMaxWidthsFromPreRendered(preRendered, settings); + expect(maxWidths).toEqual([[1], [5]]); + }); + + it('excludeFromAutoAlign works per-line via max widths', () => { + // On a single line, the exclude flag shortens the computed column list. + // calulateMaxWidthsFromPreRendered already covers this — verify integration. + const preRendered = preRenderAllWidgets( + [[text('a'), text('WIDE', { excludeFromAutoAlign: true }), text('b')]], + settings, + { isPreview: false, terminalWidth: 200, lineIndex: 0 } + ); + const maxWidths = calculateMaxWidthsFromPreRendered(preRendered, settings); + expect(maxWidths).toEqual([[1]]); // only first column before exclusion }); }); diff --git a/src/utils/__tests__/renderer-flex-width.test.ts b/src/utils/__tests__/renderer-flex-width.test.ts index ae47b9e65..c72437107 100644 --- a/src/utils/__tests__/renderer-flex-width.test.ts +++ b/src/utils/__tests__/renderer-flex-width.test.ts @@ -293,7 +293,7 @@ describe('flex-separator widget', () => { settings, context, preRenderedWidgets, - calculateMaxWidthsFromPreRendered(preRenderedLines, settings) + calculateMaxWidthsFromPreRendered(preRenderedLines, settings)[0] ?? [] ); const plainLine = stripSgrCodes(line); @@ -324,7 +324,7 @@ describe('flex-separator widget', () => { settings, context, preRenderedWidgets, - calculateMaxWidthsFromPreRendered(preRenderedLines, settings) + calculateMaxWidthsFromPreRendered(preRenderedLines, settings)[0] ?? [] ); const plainLine = stripSgrCodes(line); @@ -417,7 +417,7 @@ describe('flex-separator widget', () => { globalSeparatorIndex: advanceGlobalSeparatorIndex(0, firstLineWidgets, firstPreRenderedWidgets) }, preRenderedLines[1] ?? [], - preCalculatedMaxWidths + preCalculatedMaxWidths[1] ?? [] ); const plainSecondLine = stripSgrCodes(secondLine); @@ -455,7 +455,7 @@ describe('flex-separator widget', () => { globalSeparatorIndex: advanceGlobalSeparatorIndex(0, firstLineWidgets, firstPreRenderedWidgets) }, preRenderedLines[1] ?? [], - preCalculatedMaxWidths + preCalculatedMaxWidths[1] ?? [] ); const plainSecondLine = stripSgrCodes(secondLine); @@ -495,7 +495,7 @@ describe('flex-separator widget', () => { settings, context, firstPreRenderedWidgets, - preCalculatedMaxWidths + preCalculatedMaxWidths[0] ?? [] ); const nextStartCapIndex = countPowerlineStartCapSlots(firstLineWidgets, firstPreRenderedWidgets); const secondLine = renderStatusLine( @@ -507,7 +507,7 @@ describe('flex-separator widget', () => { globalPowerlineStartCapIndex: nextStartCapIndex }, secondPreRenderedWidgets, - preCalculatedMaxWidths + preCalculatedMaxWidths[1] ?? [] ); const firstPlainLine = stripSgrCodes(firstLine); const secondPlainLine = stripSgrCodes(secondLine); @@ -618,7 +618,7 @@ describe('flex-separator widget', () => { }; const preRenderedLines = preRenderAllWidgets([widgets], settings, context); - expect(calculateMaxWidthsFromPreRendered(preRenderedLines, settings)).toEqual([6, 7]); + expect(calculateMaxWidthsFromPreRendered(preRenderedLines, settings)).toEqual([[6, 7]]); }); it('still works in non-powerline mode (no regression)', () => { diff --git a/src/utils/__tests__/renderer-padding-side.test.ts b/src/utils/__tests__/renderer-padding-side.test.ts index 4367bb605..a1511187f 100644 --- a/src/utils/__tests__/renderer-padding-side.test.ts +++ b/src/utils/__tests__/renderer-padding-side.test.ts @@ -158,21 +158,21 @@ describe('defaultPaddingSide', () => { const lines = [[pre('AB')]]; const settings = createSettings({ defaultPadding: '..' }); // 'AB' (2) + 2 leading + 2 trailing = 6 - expect(calculateMaxWidthsFromPreRendered(lines, settings)).toEqual([6]); + expect(calculateMaxWidthsFromPreRendered(lines, settings)).toEqual([[6]]); }); it('counts padding only once when side is "left"', () => { const lines = [[pre('AB')]]; const settings = createSettings({ defaultPadding: '..', defaultPaddingSide: 'left' }); // 'AB' (2) + 2 leading + 0 trailing = 4 - expect(calculateMaxWidthsFromPreRendered(lines, settings)).toEqual([4]); + expect(calculateMaxWidthsFromPreRendered(lines, settings)).toEqual([[4]]); }); it('counts padding only once when side is "right"', () => { const lines = [[pre('AB')]]; const settings = createSettings({ defaultPadding: '..', defaultPaddingSide: 'right' }); // 'AB' (2) + 0 leading + 2 trailing = 4 - expect(calculateMaxWidthsFromPreRendered(lines, settings)).toEqual([4]); + expect(calculateMaxWidthsFromPreRendered(lines, settings)).toEqual([[4]]); }); }); }); diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index 58b7e5f14..884ce0b75 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -849,14 +849,11 @@ export function calculateMaxWidthsFromPreRendered( const { leading: sideLeadingPadding, trailing: sideTrailingPadding } = resolvePaddingSides(defaultPadding, settings.defaultPaddingSide); const paddingPairLength = sideLeadingPadding.length + sideTrailingPadding.length; - return preRenderedLines.map(preRenderedLine => { + return preRenderedLines.map((preRenderedLine) => { const lineMaxWidths: number[] = []; const isSeparatorBoundary = (entry: PreRenderedWidget | undefined): boolean => ( entry?.widget.type === 'separator' || entry?.widget.type === 'flex-separator' ); - const filteredWidgets = preRenderedLine.filter( - w => w.widget.type !== 'separator' && w.widget.type !== 'flex-separator' && w.content - ); const hasNextRenderedWidgetBeforeSeparator = (originalIndex: number): boolean => { for (let j = originalIndex + 1; j < preRenderedLine.length; j++) { const nextEntry = preRenderedLine[j]; From 8d970d06cccbca3601344aff3fa5609df8762532 Mon Sep 17 00:00:00 2001 From: Steven Chen Date: Fri, 4 Sep 2026 23:55:08 +0800 Subject: [PATCH 6/6] chore: adapt ClaudeStatus test to per-line max widths after upstream sync Upstream's calculateMaxWidthsFromPreRendered returned global column widths (number[]); ours returns per-line widths (number[][]). Adapt the new upstream test helper to index the first line, matching the pattern in renderer-powerline-theme.test.ts. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/widgets/__tests__/ClaudeStatus.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/__tests__/ClaudeStatus.test.ts b/src/widgets/__tests__/ClaudeStatus.test.ts index 592681a6e..2b0aeff79 100644 --- a/src/widgets/__tests__/ClaudeStatus.test.ts +++ b/src/widgets/__tests__/ClaudeStatus.test.ts @@ -68,7 +68,7 @@ function renderLine( settings, renderContext, preRendered, - calculateMaxWidthsFromPreRendered(preRenderedLines, settings) + calculateMaxWidthsFromPreRendered(preRenderedLines, settings)[0] ?? [] ); }