Skip to content

Give subject the width it needs, and name a commit's branches in the detail panel - #55

Merged
LeadcodeDev merged 7 commits into
mainfrom
feat/history-subject-width-and-detail-refs
Sep 7, 2026
Merged

Give subject the width it needs, and name a commit's branches in the detail panel#55
LeadcodeDev merged 7 commits into
mainfrom
feat/history-subject-width-and-detail-refs

Conversation

@LeadcodeDev

@LeadcodeDev LeadcodeDev commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

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's Column has neither flex nor grow — every width is a number of pixels, resolved once per TableState::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_prepaint and hands them to the delegate, which subtracts the four fixed columns and the twelve pixels of render_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_cell is 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 follow Window::rem_size rather than a hardcoded pixel count.

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 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 +1 standing 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/x beside origin/feat/x — then overran the whole subject cell rather than merely the budget, and overflow_hidden cut the second one: the crop the counter exists to remove. A +1 beside 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 --contains set. 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 commits to build the child map, run when a selection or the history changes, never per frame.

badges moves out of history to 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 Date row under Author now.

Verification

cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings and cargo fmt --all --check are 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), and owning_references over 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.

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.
@LeadcodeDev LeadcodeDev added the enhancement New feature or request label Sep 7, 2026
@LeadcodeDev LeadcodeDev self-assigned this Sep 7, 2026
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.
@LeadcodeDev
LeadcodeDev merged commit 40f6346 into main Sep 7, 2026
3 checks passed
@LeadcodeDev
LeadcodeDev deleted the feat/history-subject-width-and-detail-refs branch September 7, 2026 08:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant