feat(tui): rework the header box to the reference element allocation - #764
Merged
Conversation
The startup header sized its left column as `min(mascotWidth + 4, cols * 0.4)`,
which never looked at the content it had to hold. At 140 columns that pinned it
to 20 and truncated the cwd to `/Users/ericlee2/wor...` while ~65 columns sat
empty to the right, and the panel title floated inside the box over the right
column only.
Port the allocation math from the in-repo reference
(`calculateOptimalLeftWidth` / `calculateLayoutDimensions` in
typescript/src/utils/logoV2Utils.ts): size the left column to its own widest
line, book the divider and padding explicitly, and hand the remainder to the
feed column. The title moves into the top border, the identity block gains a
welcome line and labeled Model/Path rows, and a rule separates the columns.
Section content is unchanged -- only the allocation.
Four defects surfaced while building it, each pinned by a test that fails when
the fix is reverted:
- Collapse toggles were sibling <Text>es in a flex row, so an overflowing
toggle wrapped each span into its own column ("|Available (8) in 2" /
" Skills categories") instead of truncating. Now one nested run.
- Splitting was gated on terminal width alone. Because the left column is
sized to the cwd, a deep path wants 45+ columns at a terminal width of 72;
forcing the split there overflowed the box and flexbox resolved it by
shrinking both children. `fitsHorizontal` now also requires the columns to
fit, and stacks otherwise.
- ink stops embedding `borderText` once the title reaches `cols - 2` and
instead returns the raw string with the corners dropped
(render-border.ts:43-44), leaving a box with no top edge. `brand.name` is
operator-configurable, so a branded install hit this at ordinary widths.
`borderTitleParts` degrades name+version -> name -> none.
- Path truncation clipped by code unit, so a CJK cwd overshot its budget and
got re-truncated at the far end -- eliding both ends and destroying the
trailing segment middle-truncation exists to preserve. It also split
surrogate pairs. Clipping now walks grapheme clusters via Intl.Segmenter,
the same segmenter stringWidth uses internally.
Supporting changes: export `applyColor` from the vendored ink fork so the
border title is tinted through the fork's terminal-aware downgrade rather than
a hand-rolled truecolor escape; `artWidth` measures columns now that it feeds
layout sizing; drop the dead `release_date` field, whose only consumer was the
old title; add a `pretest` hook so a stale gitignored ink `dist/` cannot fail
the suite with an ESM link error.
brandingRenderWidth.test.ts documents a trap worth knowing: `useStdout()`
returns `process.stdout`, not the stream passed to `renderSync`, so the usual
`Object.assign(passThrough, {columns})` harness leaves the component on its
`?? 100` fallback and every "render at width N" silently produces the same
100-column layout.
Suite: 8 failed / 1545 passed -- the same 8 pre-existing failures as baseline
on 11c3bed (8 failed / 1493 passed), +52 from the two new files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Test Results 1 files 1 suites 8m 5s ⏱️ Results for commit dea83bd. |
This was referenced Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Reworks the startup header box (
SessionPanel) so its element allocation matches the reference Claude Code header. Section content is unchanged — Tools/Skills/System Prompt/MCP collapsibles stay exactly as they were. Only the layout changed.Before — left column hard-coded to
min(mascotWidth + 4, cols × 0.4)= 20, so the cwd truncated while ~65 columns sat empty and the title floated inside the box:After — title in the border, identity column sized to its own content and centered, real divider, feed column filling the remainder:
Allocation math ported from the in-repo reference —
calculateOptimalLeftWidth/calculateLayoutDimensions/formatWelcomeMessage/truncatePathintypescript/src/utils/logoV2Utils.ts. Below ~90 columns the layout stacks rather than splitting.Defects found and fixed
Each is pinned by a test that fails when the fix is reverted (mutation-tested, counts below).
/Users/ericlee2/wor…with ~65 columns empty▸Available (8) in 2/Skills categoriesborderTextpastcols - 2render-border.ts:43-44) → box with no top edge.brand.nameis operator-configurable, so branded installs hit it at ordinary widths1️⃣undercounts (cluster is 2 columns) → overflow; regional-indicator flags split into reversed letter pairsClipping now walks grapheme clusters via
Intl.Segmenter— the same segmenterstringWidthuses internally.Supporting changes
applyColorexported from the vendored ink fork (+1 line each inentry-exports.ts/index.d.ts) so the border title is tinted through the fork's terminal-aware downgrade instead of a hand-rolled truecolor escape, which would bypass the 8-bit path for legacy Apple Terminal.artWidthmeasures columns, now that it feeds left-column sizing — a custombannerHeroskin with wide glyphs would otherwise under-size the column it has to fit inside.release_datefield. Its only consumer was the old title; no producer exists (grep -rn release_date --include=*.pyreturns nothing).pretesthook. The vendored inkdist/is gitignored andnpm testresolves through it, so a stale build fails every ink-importing test with an ESM link error. Verified by deletingdist/entry-exports.jsand runningnpm test.Testing
npm run typecheckclean;eslintclean on all 8 files.11c3beddwith these changes stashed is 8 failed / 1493 passed — same five files (createGatewayEventHandler,cursorDriftRegression,statusRule,useConfigSync,virtualHeights). Zero regressions; +52 tests are new here.criticsubagent across two passes; all raised defects fixed and re-verified.A trap worth knowing
brandingRenderWidth.test.tsdocuments it:useStdout()returnsprocess.stdout, not the stream passed torenderSync. The usual harness (Object.assign(passThrough, {columns}), as inbrandingMcpCount.test.ts) therefore leavescolumnsundefined and the component falls back to?? 100— so every "render at width N" silently produces the same 100-column layout clipped to N. It looks responsive and tests nothing. Both the critic and I hit this independently.Relatedly, two of my own tests were initially vacuous: row-width assertions can't see content corruption at identical width, and an ANSI strip that leaves the ESC byte filters out the very rows being asserted on. Both now carry explicit non-vacuity guards.
🤖 Generated with Claude Code