Repository navigation
feat(tabs): L-shaped header strip, wrapped rows run under the toolbar - #721
Conversation
A wrapped classic tab strip stopped at the toolbar's left edge on every row, so the space under the header chips stayed empty and long tabs took a row each. Once the strip wraps, the toolbar now sits over the strip's top-right corner and the strip lays its tabs out inline around a float sized to the toolbar's box (_syncTabStripCorner), so only the rows beside the toolbar stop short of it and every later row runs the header's full width. - Wrap is still measured with the toolbar in flow (the corner is dropped before the measure), so the single-row strip is unchanged. - A ResizeObserver on the toolbar re-decides wrap + corner and redraws the lineage routes when a reading or the divider changes its size. - Falls back to the plain strip when the first tab cannot fit beside the toolbar (it would otherwise leave an empty band). - Classic arrangement, desktop, header host only; state/case/ledger, the sidebar and the tab rail keep their own layouts.
|
Thank you for this, timkjr, and welcome to Codeman. This PR turns a wrapped classic tab strip into an L-shape: the toolbar sits over the strip's top-right corner, only the rows beside it stop short, and every later row uses the full header width. I ran it in Chromium against the real header markup and styles.css at several widths, with lineage, a toolbar narrowed onto two lines and a toolbar too wide for the first tab. It does what the description says, the fallback works, the toolbar observer settles after one pass, and every check is green. Two things to change before merge:
Also, please add a sentence to the Tab layouts entry in Once those are in, this is ready to merge. |
…test Review follow-up for the L-shaped header strip: - _syncTabStripCorner() keeps the plain wrap when the plain strip is already past --tab-strip-max-height, checked before .tabs-corner goes on: the float scrolled with the tabs while the toolbar stayed put, so a scrolled strip put tabs under #headerRight, and a re-render of a scrolled two-rows strip sized the float from the scrolled frame. The "- scrollTop" in contentTop is gone (always 0 now). - A strip just under its max-height that passes it once the float is in falls back to the plain wrap as well. - test/tab-strip-corner.browser.test.ts (real Chromium, real header and styles.css), listed in BROWSER_TEST_GLOBS. - src/web/CLAUDE.md Tab layouts entry describes the L-shaped classic strip.
|
Thanks for the careful run-through. All three are in:
|
|
Thank you, timkjr, for the quick and thorough follow-up. To recap for anyone reading along: this PR makes a wrapped classic tab strip L-shaped, so the toolbar sits over the strip's top-right corner and every row below it uses the full header width. All three points from the last review are in, and I checked each one:
I also drove the toolbar observer directly (a toolbar width change with no tab render re-sized the corner on its own), and checked lineage spacing (row gap and bottom room identical to the plain wrap). Typecheck, lint, format, frontend syntax, the browser-exclude check and the full One thing I will do at merge time: add a paragraph on the L-shape to the Tab layouts section of This is ready to merge. |
Add the L-shaped strip to the Tab layouts section of docs/architecture-invariants.md, the mechanism file src/web/CLAUDE.md links to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged in 1.47.0 (https://github.com/Ark0N/Codeman/releases/tag/codeman%401.47.0). Thank you for the L-shaped strip and for the quick round with the scroll guard and the browser test. At merge I added the paragraph on the L-shape to the Tab layouts section of |
What
When the classic header tab strip wraps (
tabs-auto-wrap, or the manual two-row setting), every row stops at the toolbar's left edge, because the strip and#headerRightare side-by-side flex items. Only the first row actually sits beside the toolbar; the space under the stats/buttons on every later row stays empty, so with a couple of long tab names each tab ends up on a row of its own.With this change, once the strip wraps the toolbar sits over the strip's top-right corner and the strip runs the header's full width: only the row(s) beside the toolbar stop short of it, and every later row uses the full width under it.
Screenshots (1280px with a lineage line, and a toolbar narrowed onto two lines):


How
updateTabOverflowMode()is unchanged in what it decides: the corner class is removed before the wrap is measured, so the wrap is still judged against the normal one-row layout (toolbar in flow), and a strip that fits on one row looks exactly as before._syncTabStripCorner()then adds.tabs-cornerto a wrapped classic strip in the header and measures the toolbar's box into--tab-corner-width/--tab-corner-height.#headerRightgoesposition: absoluteover the corner; the strip becomesdisplay: blockwithinline-flextabs flowing around a::beforefloat of the toolbar's size. A flex line cannot be shortened on its own, which is why the strip leaves flex layout in this one state. The oldrow-gap(including lineage's--lineage-row-gap) becomes a bottom margin on the tabs, and the strip's bottom padding is reduced by the same amount, so row spacing and the lineage routing room are unchanged.ResizeObserveron the toolbar re-decides wrap + corner and redraws the connection lines when a reading or the header divider changes the toolbar's size without a tab render.Scope: classic arrangement, desktop, header host only. The state / case / ledger arrangements lay out their own rows, and the sidebar and vertical rail are untouched (all gated in
_syncTabStripCorner;applySessionListLayoutalso drops the class when moving the strip to the sidebar).Tests
Checked in a real browser (Playwright, headless Chromium) at 1000 / 1100 / 1280 / 2400 px with lineage lines, a toolbar narrowed until it wraps onto two lines, and a toolbar widened past the first tab: no tab overlaps the toolbar in any case, lineage routes draw, no page errors.
npm run lint,format:check,check:frontend-syntaxand the fullnpm testpass locally. There is no automated test for the layout itself; happy to add a browser test if you want one.🤖 Generated with Claude Code