Skip to content

fix(view): underline and open a badge the row cut - #74

Merged
GraemeF merged 2 commits into
mainfrom
badge-cut-link
Sep 11, 2026
Merged

fix(view): underline and open a badge the row cut#74
GraemeF merged 2 commits into
mainfrom
badge-cut-link

Conversation

@GraemeF

@GraemeF GraemeF commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

A badge whose config names a link draws underlined and opens when clicked. Where the row ran out of width and cut that badge, bdi took the underline off and told the terminal nothing, so the reader lost the destination along with the columns. That is the wrong trade. A cut is not the case 5d276f0 was reasoning about: the destination is still known and the terminal can still open it, so a clipped label on a live link beats refusing both. A badge the emitter genuinely refuses still draws plain and dead, because there the old reasoning holds.

fit and cut_to reported how many spans the block kept whole, and that count is what dropped a cut link; they now report how many reached the row. None of the emitting machinery needed changing, because the whole escape sequence already lived in the cell the link starts on — so the columns the cut took were never columns the sequence needed.

The part worth a reviewer's attention is opens_at, which the diff makes look like tidying. bead_line pushed a link for every badge whose config named one, while badge_style underlined only those the emitter would write. That mismatch was harmless while a cut badge lost both, because the emitter is asked again at the last moment and refuses. It stops being harmless here: a badge whose whole text the emitter refuses can have a head it would accept, and that head would open with no underline on it, which is the inverse of the lie this change is fixing. Both sites now ask one function of the badge's own text.

Of the tests the old behaviour rested on, two invert and one is left exactly as it was, because what it pins — that the cut reaches a badge's underline and nothing else — is still true.

The mutation gate over the first commit reported 44 mutants, 40 caught, 4 unviable, none missed. Read that figure knowing bead_line was scored by nothing at all, its only mutant being Default::default() on a return type that has none. Hand-weakening found two real holes, both closed in the second commit: opens_at was untested outright, and a cut link's forced width was pinned only where nothing was cut, so hardcoding the whole badge's five columns stayed green. That width is what tells the diff how many columns to skip behind the link's cell. The second commit is tests only, so the figure still stands for this tree.

One weakening survives and is left alone on purpose. The reasoning is in the second commit's message, where a future gate-runner will find it.

0.7.0 gains a release-notes line for this, written by the seat that owns that file. Nothing under RELEASE-NOTES/ is touched here.

A badge whose config names a link is drawn underlined and opens when
clicked. Where the row ran out of width and cut that badge, it was drawn
without the underline and the terminal was never told about the link, so
the reader lost the destination along with the columns.

The cut takes columns off what a link says and nothing off where it goes,
and the whole escape sequence lives in the cell the link starts on, so the
columns the cut took were never columns the sequence needed. A cut badge
now keeps its underline and opens the same destination a fitted one opens,
round the head the row kept. Where the cut leaves the badge no columns at
all there is nothing to be told round, and no link is opened.

`bead_line` pushed a link for every badge whose config named one while
`badge_style` underlined only the ones the emitter would write. That
mismatch was harmless while a cut badge lost both, because the emitter was
asked again at the last moment and refused. It is not harmless now: a badge
whose whole text the emitter refuses can have a head it would accept, which
would open unmarked. Both sites ask `opens_at` of the badge's own text, so a
badge is a link or it is not, at every width.
Hand-weakening the change found two holes the mutation run could not. The
run offered `bead_line` one mutant, which did not compile, and `surviving`
only whole-body replacements alongside its operators, so neither function's
internals were scored.

A badge whose whole text the emitter refuses can have a head it would
accept, and drawing that head as an unmarked live link is the inverse of the
lie the cut used to tell. Swept over every width, because which width cuts
back to a clean head falls out of a control character's own zero columns.

A cut link's cell reports the head's width rather than the badge's, and the
diff skips exactly that many columns behind it. Hardcoding the whole badge's
width left the suite green, because the width was pinned only where nothing
was cut.

One weakening survives on purpose, for whoever runs a gate over this code
next. Deleting `cut_to`'s `!head.is_empty()` guard changes nothing a test can
see: an empty span draws nothing, and `Kept::told_to` refuses a zero width
through `NonZeroU16`. The guard keeps `cut_to`'s count honest at the layer
that knows what it cut, and the `NonZeroU16` guards the write in a function
that does not know. Two guards each honest about their own layer beat one
that has to know about both, so neither is weakened to make a tally read
clean.
@GraemeF
GraemeF merged commit f2a011b into main Sep 11, 2026
2 checks passed
@GraemeF
GraemeF deleted the badge-cut-link branch September 11, 2026 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant