A tab strip shrinks its labels to fit its pane - #37
Conversation
TabControl draws its tabs left to right from index 0 and writes each one clipped to the header rectangle. There is no first-visible-tab offset anywhere in the control, so a strip narrower than its tabs stops drawing — the tabs past the edge are gone, the selected one included, and so are the ×, the separator and the framework's own ← → hint, which only draws when there is slack for it and the strip has keyboard focus (never, here: focus is pinned to the armed command line). Five captures in a split pane drew two tabs and half of a third, and said nothing about the rest. The titles are ours, so the titles shrink. TabStripFit drops the repeated owner prefix across the whole strip first — a pane of one character's captures repeats "Corvid - " on every tab, and the chip behind the tab already says whose it is — then water-fills what is left, so the long name gives up cells and the short one keeps them all. The tab the pane is showing is spared while any other tab can still pay, and keeps a floor of its own when it cannot be. A tab's fixed cost is measured off a rendered title rather than derived a second time, so the badge, pen, ⌁ and focus ▌ keep one definition; the width is synced on PostBufferPaint, gated on having moved, and through RetitlePane rather than RefreshTabTitles so it does not drag the rail — which resizes the sidebar column, which moves the very widths that triggered it. TabControl.CalculateSize returns the constraint's width and never its content's, so there is no loop to guard against beyond that. Past the floors the tabs do overflow and that is left alone: eight tabs cannot be drawn in forty cells however they are labelled. Making the last tab reachable is a scroll offset, which is upstream work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 22 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (7)
Comment |
The report
The answer to the first was you can't, and to the second no.
TabControldraws its tabs left to right fromx = headerLeftand writes each one withWriteCellsClippedagainst the header rectangle (TabControl.Rendering.cs:75,103). There is no first-visible-tab offset anywhere in the control — I grepped it — so a strip narrower than its tabs simply stops drawing. The tabs past the edge are gone, the selected one included, and so are the×and the│, each gated onx < headerRight. The framework's own" ← → "hint (:124) cannot cover for it either: it is drawn only whenHasFocusand there is slack left after the tabs, and focus here is pinned to the armed command line.Measured on the new
tabs-manyview: five of one character's captures in a split pane drew two tabs and half of a third.The fix
The titles are ours, so the titles shrink.
TabStripFitdecides how many cells of name each tab may draw:Corvid -on every tab — the cheapest thing there is to lose and the most there is of it. What it said is still said twice over, by the chip the tab is painted on and by the⌁a window belonging to another character wears. Dropping it alone is often the whole fix.O-Gatecrashersgives up cells andChatkeeps them all.MinimumSelectedName) when it cannot be. It is the tab the strip exists to name.⌁and focus▌are facts about the window, each one or two cells against a name that is routinely twenty.Past the floors the tabs do overflow, and that is left alone rather than papered over: eight tabs cannot be drawn in forty cells however they are labelled. Making the last tab reachable needs a scroll offset in the control, which is upstream work.
Frame, before and after
Two things that could have gone wrong and are guarded
TabTitles.Forand overflow the pane while every pure test still passed. The fixed cost is measured off a rendered title (VisibleLengthless the name) plus the framework's own cells, andTabStripElisionTestsasserts the sum against a realPaneOutputRectswidth rather than against its own model.TabControl.CalculateSizereturns the constraint's width and never its content's, so the dependency runs one way. The sync is gated on the width having actually moved, and goes throughRetitlePanerather thanRefreshTabTitlesso it does not dragRefreshRailwith it — the rail resizes the sidebar's column, which moves the very widths that triggered the sync.Verification
dotnet build -c Releaseclean and warning-free; all five suites green — Core 938, Graphics 83, Scripting 42, Web 37, Tui 1814. New tests:TabStripFitTests(7, the rule) andTabStripElisionTests(5, through a real layout and a real frame).tabsandtint-tabsrender byte-identically — those strips have room, and a strip with room is left exactly as it was.🤖 Generated with Claude Code