Skip to content

Keep a Toast inside its host: Toast.MaxW, and export WrapText - #459

Merged
tannevaled merged 2 commits into
mainfrom
toast-fits-its-host
Sep 7, 2026
Merged

Keep a Toast inside its host: Toast.MaxW, and export WrapText#459
tannevaled merged 2 commits into
mainfrom
toast-fits-its-host

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

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, not supposed

In go-xrkit/desk, on a 1920-wide view at the size it draws its notices (30 px ink, 43 px body):

message pill verdict
screen 3 is back 329 px fits
the camera was refused; it is turned on again in System Settings > Privacy & Security > Camera 2373 px 1.2× the width
a longer refusal 4975 px 2.6× the width

The 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; Text is wrapped across as many rows as it needs. The zero value keeps the old behaviour exactly: no wrapping, no measuring, one line. 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. The key is (text, width, glyph height) and not the Font itself — 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.
  • 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 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

tannevaled and others added 2 commits September 6, 2026 23:12
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>
@tannevaled
tannevaled merged commit b50ae8a into main Sep 7, 2026
1 check passed
@tannevaled
tannevaled deleted the toast-fits-its-host branch September 7, 2026 07:00
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