Give subject the width it needs, and name a commit's branches in the detail panel - #55
Merged
Merged
Conversation
The subject column was 420 pixels whatever the window did, so on anything wider than the five columns put together the table stopped short of the right edge and the leftover showed as dead space past Date — while a branch badge was being cropped two columns to its left. `gpui_component`'s `Column` has neither flex nor grow: every width is a number of pixels, resolved once per `TableState::refresh`. The panel therefore measures its own bounds through `on_prepaint` and hands them to the delegate, which subtracts the four fixed columns and the twelve pixels of `render_last_empty_col` and gives the rest to Subject, down to a floor of 240 past which the table scrolls instead. The delegate answers whether the width actually moved, and the panel refreshes only when it did. That is not a micro-optimisation: a refresh notifies, a notify prepaints, and the prepaint is what reports the width.
…ts own row The detail panel showed a commit with no idea where it lived. It now opens with the same reference badges the history table draws beside a subject, above the Subject row. For a branch tip that is the same set the table shows. For anything else the answer comes from `History::owning_references`, which walks *down the first-parent chain* — from the commit to the children whose first parent it is — until a level carries a reference. First-parent is what makes it read as "the branch this commit was written on": a merge takes the feature branch as a second parent, so the walk climbs the feature to its tip and stops rather than spilling onto the trunk. The rejected alternative was every branch containing the commit, the `git branch --contains` set. It answers a different question and is unusable here: an old commit on the trunk is contained by every branch cut since, which on the repository this was built against is thirty badges above a subject. `badges` moves out of `history` to the crate root, because a branch has to read as one thing across the window and two copies of the colour mapping would drift. The author's timestamp moves to a Date row of its own. Sharing a row with the name and the address ran past the panel and wrapped, which put the date on a second line with no label beside it.
A badge now renders whole or not at all. Whatever is left over becomes a single `+N` at the end of the strip, with the names it stands for on its tooltip. The strip was a scroller clipped at half the subject cell. It put a branch name cut mid-word in front of the reader and asked them to discover that the fragment could be dragged. Widening Subject made the cut rarer without making it any less of a lie about the name. Fitting has to be predicted rather than laid out: gpui hands an element its bounds one phase after its children are built, so the choice of what to build cannot read them. The subject column's width is already known to the delegate, so the strip shapes each name through the window's text system and adds `Tag`'s own xsmall metrics — text_xs, px_1p5, border_1, in rems so they follow the root size. `fitting_badge_count` walks down from "everything fits" rather than up from nothing, because dropping the last badge takes the counter's own reservation away with it: the largest set that fits is not always reachable by adding one at a time. Mismeasuring cannot crop anything either way — the budget is half the cell, so a strip that overruns its estimate still lands well inside the cell and only costs the subject some room.
A `+1` takes nearly the width of the badge it replaces and says strictly less: the reader learns that a branch exists and not which one. On a dependabot commit, whose single long reference never fits half a subject cell, the whole row was reduced to a counter. Where exactly one reference is left over it is now drawn instead, budget overrun and all — the subject beside it truncates, which is the ordinary thing for a subject to do, and the budget is half the cell so the overrun still lands inside it. A counter now only ever stands for two or more.
A row shrunk to a counter alone had lost the one name it had room to say. The leading reference now renders regardless of what the arithmetic allows, and the rule that a counter never stands for a single reference is applied after it — forcing the first badge can itself leave exactly one behind. Both rules let the strip overrun its budget. That is bounded rather than loose: the budget is half the subject cell, so an overrun still lands inside it and the subject truncates.
The `+N` opens a popover holding the badges it stands for, drawn exactly as the ones on the row and carrying the same context menu — so a local branch pushed out of the strip can still be deleted. It was a tooltip until now, which listed the names as plain text. A tooltip dismisses the moment the pointer leaves the trigger, so those names could be read and never reached, and a branch reachable only until you move towards it is worse than one plainly absent. `badge_cell` is factored out rather than duplicated: the counter changes how many names are on screen, never what can be done with them, and two copies of that rule would drift. `OverflowBadge` becomes a type of its own because `Popover::trigger` takes `Selectable` — it wants to mark the trigger while the popover is open, and nothing here reads the flag, since a badge has no selected appearance and inventing one would make the strip's colours mean two things at once.
Showing it instead of the counter overran the budget by the badge's whole width, and the claim that the overrun stayed inside the cell only held while the badge was narrower than half of it. Two long names of nearly equal length — `feat/x` beside `origin/feat/x` — overran the cell itself, and `overflow_hidden` cut the second: the crop the counter exists to remove. The rule was only ever needed for a row whose *only* reference does not fit, and "the first badge always renders" already covers that. So the exception goes and the overrun goes with it — the strip now exceeds its budget for the leading badge alone, and by at most that one badge.
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.
Three things reported from the same screenshots: branch badges cropped mid-name in the history table, a detail panel that never says which branch a commit belongs to, and a date sharing a row with the author.
Subject takes the leftover width
gpui_component'sColumnhas neither flex nor grow — every width is a number of pixels, resolved once perTableState::refresh. Subject was pinned at 420, so on any window wider than the five columns put together the table stopped short of the right edge, the leftover showed as dead space past Date, and a badge was cropped two columns to its left.The panel now measures its own bounds through
ElementExt::on_prepaintand hands them to the delegate, which subtracts the four fixed columns and the twelve pixels ofrender_last_empty_col. Subject gets the rest, with a floor of 240 past which the table scrolls horizontally instead.The delegate answers whether the width actually moved and the panel refreshes only when it did — a refresh notifies, a notify prepaints, and the prepaint is what reports the width, so an unconditional refresh here is a loop, not a measurement. That constraint is now recorded in
CLAUDE.md.A badge renders whole, or is counted
The strip from #49 was a scroller clipped at half the subject cell. It put a branch name cut mid-word in front of the reader and asked them to discover that the fragment could be dragged. Widening Subject made the cut rarer without making it any less of a lie about the name.
Badges that do not fit are dropped whole and replaced by a single
+N. Clicking it opens a popover holding the badges it stands for, drawn exactly as the ones on the row and carrying the same context menu — so a local branch pushed out of the strip can still be deleted.That started as a tooltip listing the names as plain text. A tooltip dismisses the moment the pointer leaves the trigger, so those names could be read and never reached, and a branch reachable only until you move towards it is worse than one plainly absent.
badge_cellis shared between the strip and the popover rather than duplicated: the counter changes how many names are on screen, never what can be done with them.Fitting has to be predicted rather than laid out: gpui hands an element its bounds one phase after its children are built, so the choice of what to build cannot read them. The subject column's width is already known to the delegate, so the strip shapes each name through the window's text system and adds
Tag's own xsmall metrics —text_xs,px_1p5,border_1, kept in rems so they followWindow::rem_sizerather than a hardcoded pixel count.fitting_badge_countwalks down from "everything fits" rather than up from nothing, because dropping the last badge takes the counter's own reservation away with it: the largest set that fits is not always reachable by adding one badge at a time. A mismeasurement cannot crop anything either way — the budget is half the cell, so a strip that overruns its estimate still lands well inside the cell and costs the subject some room at worst.One rule then overrides that arithmetic: the first badge always renders, whatever the budget. A row shrunk to a counter alone has lost the one name it had room to say, and a
+1standing for the row's only reference says strictly less than the reference does. This is the sole case where the strip may overrun its budget, and it costs at most one badge.Suppressing the counter for any single leftover was tried and reverted. Two long names of nearly equal length —
feat/xbesideorigin/feat/x— then overran the whole subject cell rather than merely the budget, andoverflow_hiddencut the second one: the crop the counter exists to remove. A+1beside a name that fits is the lesser loss, and the rule was only ever needed for a row whose only reference does not fit — which the leading-badge rule already covers.The detail panel names the commit's branches
The General tab now opens with the same reference badges the table draws, above Subject.
For a branch tip that is the same set the table shows. For anything else the answer comes from
History::owning_references, which walks down the first-parent chain — from the commit to the children whose first parent it is — until a level carries a reference.First-parent is what makes the answer read as "the branch this commit was written on". A commit made on a feature branch is the first parent of the next feature commit, and the branch's merge into the trunk takes it as a second parent, so the walk climbs the feature to its tip and stops rather than spilling onto the trunk.
The rejected alternative was every branch that contains the commit, the
git branch --containsset. It is the honest answer to a different question and unusable here: an old commit on the trunk is contained by every branch cut since it, which on the repository this was built against is thirty badges above a subject. Stopping at the first level that carries a reference keeps it to the one or two branches that actually claim the commit — a fork point legitimately answers with several.Walked on demand rather than indexed at load: one pass over
commitsto build the child map, run when a selection or the history changes, never per frame.badgesmoves out ofhistoryto the crate root. A branch has to read as one thing across the window, and two copies of the colour mapping would drift apart. The detail panel's strip wraps rather than counting — a header is free to take a second line, where a fixed-height table row is not.Date on its own row
The author's timestamp shared a row with the name and the address. Widened, that ran past the panel and wrapped, which put the date on a second line with no label beside it. It is a
Daterow underAuthornow.Verification
cargo test --workspace,cargo clippy --workspace --all-targets -- -D warningsandcargo fmt --all --checkare green. New tests cover the width arithmetic, the refresh guard, the badge-fitting count (including the case where the counter's own width pushes out the last badge that would otherwise fit), andowning_referencesover a trunk that merges a feature branch — tip, mid-branch commit, fork point, and a history with no references at all.The app was launched against a real repository and paints, but the visual check is the reviewer's: this machine has no screen-recording permission, so no before/after capture is attached.
No issue to reference — reported directly in conversation.