Keep a Toast inside its host: Toast.MaxW, and export WrapText - #459
Merged
Conversation
A Toast sizes itself to its widest line with no upper bound. A host that
docks one to a centre anchor then paints a pill wider than the view, so
BOTH ENDS are cut and the reader gets the middle of a sentence -- which is
worse than a truncated message, because it does not look truncated.
Measured in go-xrkit/desk, on a 1920-wide view at the size it draws its
notices (30px ink, 43px body):
"screen 3 is back" 329px fits
"the camera was refused; it is turned on again
in System Settings > Privacy & Security > Camera" 2373px 1.2x the width
a longer refusal 4975px 2.6x the width
So this is not hypothetical: the middle one already ships, and anybody
whose camera is denied has been reading the middle of that sentence.
Toast.MaxW, when positive, is the widest the pill may be drawn; Text is
wrapped across as many rows as it needs. The zero value keeps the old
behaviour exactly -- no wrapping, no measuring, one line -- and Lines still
wins, since a caller supplying its own rows has already decided where they
break. The wrap is memoised because lines() is asked three times a frame
(sizing, height, drawing) and wrapping costs a measure per word.
WrapText exports the routine the card widgets already lay their bodies out
with. It was private, so a widget outside cardframe.go had to write its own
-- and wrap to different rules than the card beside it.
MaxW is set-once layout config, so it joins the Toast entry in the MVVM
gate's allow-list alongside Lines, Icon and Actions.
The tests are proved by sabotage: making wrapWidthFor return 0 makes them
fail, naming the overflow ("pill 584px, want at most 300px"). The first
sabotage attempt did not apply and the suite stayed green -- checked that
the file had actually changed before believing it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The repo's gate is 100% of statements, and the new code left three holes: WrapText was exported but never called from a test, lines() never met an all-whitespace message, and wrapWidthFor never met a toast with an action button to pay for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
A
Toastsizes itself to its widest line with no upper bound. A host that docks one to a centre anchor then paints a pill wider than the view, so both ends are cut and the reader gets the middle of a sentence — which is worse than a truncated message, because it does not look truncated.Measured, not supposed
In
go-xrkit/desk, on a 1920-wide view at the size it draws its notices (30 px ink, 43 px body):screen 3 is backthe camera was refused; it is turned on again in System Settings > Privacy & Security > CameraThe middle one already ships. Anybody whose camera has been denied has been reading the middle of that sentence.
What changed
Toast.MaxW— when positive, the widest the pill may be drawn;Textis wrapped across as many rows as it needs. The zero value keeps the old behaviour exactly: no wrapping, no measuring, one line.Linesstill wins, since a caller supplying its own rows has already decided where they break.lines()is asked three times a frame (sizing, height, drawing) and wrapping costs a measure per word. The key is (text, width, glyph height) and not theFontitself — comparing an interface can panic on an uncomparable dynamic type; the documented consequence is a mis-wrap between two fonts of identical height, never a crash, and it corrects itself on any change.WrapTextexports the routine the card widgets already lay their bodies out with. It was private, so a widget outsidecardframe.gohad to write its own — and wrap to different rules than the card beside it.MaxWis set-once layout config, so it joins theToastentry in the MVVM gate's allow-list alongsideLines,IconandActions.The tests are proved by sabotage
Making
wrapWidthForreturn 0 makes them fail, naming the overflow (pill 584px, want at most 300px). The first sabotage attempt silently did not apply and the suite stayed green — the file was checked for the change before anything was concluded from it.🤖 Generated with Claude Code