Upgrade MyGo to v0.5.5

MyGo v0.5.5 returns ui.Element as a checked value handle instead of a
pointer. Migrated with `mygo migrate-ui`, then fixed what the codemod
leaves to review:

- Element nil checks become .Valid(); absent elements are ui.Element{}.
- Input handlers and paint callbacks run outside the build pass, where
  elements expire and Context methods are inert: read IsDisabled into a
  local during the build, keep a ui.Handle for Focus, use c.Services()
  for clipboard access, create ui.Local state during the build.
- Add core.AutoFocus: v0.5.5 records an overlay's opener after its
  content builds, so same-frame autofocus lost focus restore on close.
  It focuses one frame later; cascader rows defer their focus likewise.
- core.OverlayLayer identifies build passes by the root element, not
  c.Now(), which repeats across passes and under the Tester clock.
- Context menus survive the opening press counted as PressedOutside;
  split button tooltip no longer reorders its open menu; disabled
  menubar rows pass presses to the panel; Form defers blur-started
  async checks so the growing message no longer moves Submit away
  from the release; Combobox drops its disabled copy now that MyGo
  blocks typing under a disabled wrapper (canary flipped to a guard).
- The Tester clock is virtual: tests use Advance/Now instead of
  time.Sleep/time.Now and read element state inside the view. The web
  mirror advances the clock to wall time before each frame and input.
- AGENTS.md and docs describe the value Element API and the new pin.

Snapshot goldens are darwin-only and were not re-verified here.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-17 01:37:54 -04:00
1 parent bc716d27be
commit bcce34cda1
603 files changed
+1366 -1277

No files matched your search

+1 -1
View File
@@ -18,7 +18,7 @@ type AvatarOptions struct {
}
// Avatar shows a person as picture, initials or icon.
func Avatar(c *ui.Context, name string, opts AvatarOptions) *ui.Element {
func Avatar(c *ui.Context, name string, opts AvatarOptions) ui.Element {
size := opts.Size
if size < 0 {
panic("mujica: Avatar Size must not be negative")
+8 -8
View File
@@ -29,7 +29,7 @@ type avatarGroupKey struct{}
// AvatarGroupView is a shown AvatarGroup and its full list's state.
type AvatarGroupView struct {
Element *ui.Element
Element ui.Element
// Open reports whether the full list shows.
Open bool
// Toggled reports the full list opened or closed this frame.
@@ -60,7 +60,7 @@ func AvatarGroup(c *ui.Context, items []AvatarItem, opts AvatarGroupOptions) Ava
was := *open
row.Children(func() {
for i, it := range items[:shown] {
cell := ui.Box(c).Key(i).Padding(ring).Radius(size/2 + ring).Background(k.Background).Shrink(0)
cell := ui.Box(c.Key(i)).Padding(ring).Radius(size/2 + ring).Background(k.Background).Shrink(0)
if i > 0 {
cell.Margin(0, 0, 0, -size/4)
}
@@ -72,7 +72,7 @@ func AvatarGroup(c *ui.Context, items []AvatarItem, opts AvatarGroupOptions) Ava
return
}
more := avatarGroupMore(c, k, open, size, ring, rest, len(items))
ui.PopoverBase(c, more, open, func(panel *ui.Element) {
ui.PopoverBase(c, more, open, func(panel ui.Element) {
avatarGroupList(c, k, panel, items)
})
})
@@ -84,10 +84,10 @@ func AvatarGroup(c *ui.Context, items []AvatarItem, opts AvatarGroupOptions) Ava
}
// avatarGroupMore is the "+N" disclosure toggling *open.
func avatarGroupMore(c *ui.Context, k theme.Tokens, open *bool, size, ring float32, rest, total int) *ui.Element {
func avatarGroupMore(c *ui.Context, k theme.Tokens, open *bool, size, ring float32, rest, total int) ui.Element {
hit := max(size+2*ring, theme.MinHitSize)
var b *ui.Element
ui.Box(c).Key("more").Margin(0, 0, 0, -size/4).Shrink(0).Children(func() {
var b ui.Element
ui.Box(c.Key("more")).Margin(0, 0, 0, -size/4).Shrink(0).Children(func() {
b = ui.CollapsibleBase(c, open).Trigger.Size(hit, hit).Center().Radius(hit / 2).
Label(fmt.Sprintf(core.Msg(c, "avatar_group.more"), total))
internal.Ring(b, k.Focus, hit/2)
@@ -111,7 +111,7 @@ func avatarGroupMore(c *ui.Context, k theme.Tokens, open *bool, size, ring float
return b
}
func avatarGroupList(c *ui.Context, k theme.Tokens, panel *ui.Element, items []AvatarItem) {
func avatarGroupList(c *ui.Context, k theme.Tokens, panel ui.Element, items []AvatarItem) {
u := core.Density(c).Unit()
panel.Margin(u, 0, 0, 0).Padding(u*2).Radius(theme.PopupRadius).Background(k.Surface).
Border(theme.BorderWidth, k.Border).MinWidth(200)
@@ -121,7 +121,7 @@ func avatarGroupList(c *ui.Context, k theme.Tokens, panel *ui.Element, items []A
ui.Scroll(c).MaxHeight(240).Children(func() {
ui.Column(c).Gap(u * 2).Role(ui.RoleList).Children(func() {
for i, it := range items {
ui.Row(c).Key(i).Gap(u * 2).AlignItems(ui.Center).Role(ui.RoleListItem).Children(func() {
ui.Row(c.Key(i)).Gap(u * 2).AlignItems(ui.Center).Role(ui.RoleListItem).Children(func() {
Avatar(c, it.Name, AvatarOptions{Image: it.Image, Size: 24})
ui.Text(c, it.Name).SingleLine()
})
+2 -2
View File
@@ -48,7 +48,7 @@ type BadgeOptions struct {
}
// Badge shows text or a count, inline or cornered.
func Badge(c *ui.Context, opts BadgeOptions, content func()) *ui.Element {
func Badge(c *ui.Context, opts BadgeOptions, content func()) ui.Element {
if opts.Count < 0 || opts.Max < 0 {
panic("mujica: Badge Count and Max must not be negative")
}
@@ -92,7 +92,7 @@ func badgeText(opts BadgeOptions) (shown, full string) {
return full, full
}
func badgePill(c *ui.Context, opts BadgeOptions, shown, full string) *ui.Element {
func badgePill(c *ui.Context, opts BadgeOptions, shown, full string) ui.Element {
k := core.Tokens(c)
bg, fg, border := badgeColors(k, opts.Tone)
label := full
+1 -1
View File
@@ -101,7 +101,7 @@ func ExampleTag() {
ui.Row(c).Gap(8).Children(func() {
for i, t := range tags {
var v display.TagView
ui.Box(c).Key(t).Children(func() {
ui.Box(c.Key(t)).Children(func() {
v = display.Tag(c, t, display.TagOptions{Icon: "star", Clickable: true, Closable: true})
})
if v.Closed {
+1 -1
View File
@@ -19,7 +19,7 @@ type HeadingOptions struct {
}
// Heading shows a serif title scaled with desktop text.
func Heading(c *ui.Context, title string, opts HeadingOptions) *ui.Element {
func Heading(c *ui.Context, title string, opts HeadingOptions) ui.Element {
k := core.Tokens(c)
size := headingSize(opts.Level)
u := core.Density(c).Unit()
+2 -2
View File
@@ -36,7 +36,7 @@ type IconOptions struct {
type iconMissingKey struct{}
// Icon shows a named icon; missing ones show a fallback.
func Icon(c *ui.Context, name string, opts IconOptions) *ui.Element {
func Icon(c *ui.Context, name string, opts IconOptions) ui.Element {
if name == "" {
panic("mujica: Icon needs a name")
}
@@ -62,7 +62,7 @@ func Icon(c *ui.Context, name string, opts IconOptions) *ui.Element {
return e
}
func iconMissing(c *ui.Context, name string, size float32, k theme.Tokens) *ui.Element {
func iconMissing(c *ui.Context, name string, size float32, k theme.Tokens) ui.Element {
text := fmt.Sprintf(core.Msg(c, "icon.missing"), name)
e := ui.Box(c).Size(size, size).Shrink(0).Role(ui.RoleImage).Label(text).Tooltip(text)
logged := ui.Local(e, iconMissingKey{}, func() string { return "" })
+5 -5
View File
@@ -26,7 +26,7 @@ type ImageOptions struct {
// ImageView is a shown Image and this frame's events.
type ImageView struct {
Element *ui.Element
Element ui.Element
// Retry reports the error state's retry button was pressed.
Retry bool
}
@@ -46,7 +46,7 @@ func Image(c *ui.Context, src ui.ImageSource, opts ImageOptions) ImageView {
}
loaded := opts.Err == nil && !opts.Loading && src != nil
var v ImageView
v.Element = imageSized(c, src, opts, func(box, pic *ui.Element) {
v.Element = imageSized(c, src, opts, func(box, pic ui.Element) {
if loaded {
if opts.Alt != "" {
pic.Label(opts.Alt)
@@ -60,7 +60,7 @@ func Image(c *ui.Context, src ui.ImageSource, opts ImageOptions) ImageView {
}
// imageLayers builds the state layers over the hidden picture.
func imageLayers(c *ui.Context, box *ui.Element, opts ImageOptions) (retry bool) {
func imageLayers(c *ui.Context, box ui.Element, opts ImageOptions) (retry bool) {
k := core.Tokens(c)
u := core.Density(c).Unit()
room := box.Bounds()
@@ -99,7 +99,7 @@ func imageLayers(c *ui.Context, box *ui.Element, opts ImageOptions) (retry bool)
}
// imageSized builds the outer box and its picture, sized alike in every state.
func imageSized(c *ui.Context, src ui.ImageSource, opts ImageOptions, layers func(box, pic *ui.Element)) *ui.Element {
func imageSized(c *ui.Context, src ui.ImageSource, opts ImageOptions, layers func(box, pic ui.Element)) ui.Element {
if src == nil {
src = imageBlank
}
@@ -123,7 +123,7 @@ func imageSized(c *ui.Context, src ui.ImageSource, opts ImageOptions, layers fun
}
// imageRetry is the 28 DIP icon-only retry button.
func imageRetry(c *ui.Context, k theme.Tokens, problem string) *ui.Element {
func imageRetry(c *ui.Context, k theme.Tokens, problem string) ui.Element {
b := ui.ButtonBase(c).Size(theme.MinHitSize, theme.MinHitSize).Shrink(0).Radius(theme.ControlRadius).
Label(core.Msg(c, "image.retry")).Description(problem).Tooltip(problem)
bg := k.Surface
+2 -2
View File
@@ -11,7 +11,7 @@ import (
)
// Kbd shows keys as caps; "Mod" is Command or Ctrl.
func Kbd(c *ui.Context, keys ...string) *ui.Element {
func Kbd(c *ui.Context, keys ...string) ui.Element {
if len(keys) == 0 {
panic("mujica: Kbd needs at least one key")
}
@@ -31,7 +31,7 @@ func Kbd(c *ui.Context, keys ...string) *ui.Element {
ui.Text(c, "+").FontSize(core.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
}
face, _ := KbdKey(key, mac)
ui.Box(c).Key(i).Row().Center().MinWidth(core.FontSize(c, 20)).MinHeight(core.FontSize(c, 20)).Padding(0, 5).
ui.Box(c.Key(i)).Row().Center().MinWidth(core.FontSize(c, 20)).MinHeight(core.FontSize(c, 20)).Padding(0, 5).
Radius(theme.ControlRadius).Background(k.Surface).BorderColor(k.ControlBorder).
BorderWidth(1, 1, 2, 1).Children(func() {
ui.Text(c, face).Font(theme.MonoFont).FontSize(core.FontSize(c, theme.CaptionSize)).TextColor(k.Text).SingleLine()
+2 -2
View File
@@ -21,7 +21,7 @@ type LinkOptions struct {
}
// Link is underlined text; malformed URLs show an error instead.
func Link(c *ui.Context, label string, opts LinkOptions) *ui.Element {
func Link(c *ui.Context, label string, opts LinkOptions) ui.Element {
if label == "" {
panic("mujica: Link needs a label")
}
@@ -40,7 +40,7 @@ func Link(c *ui.Context, label string, opts LinkOptions) *ui.Element {
}
}
}
var l *ui.Element
var l ui.Element
ui.Column(c).Gap(u).AlignItems(ui.Start).Children(func() {
ui.Row(c).Gap(u).AlignItems(ui.Center).Children(func() {
l = ui.Link(c, label, target).Underline().DecorationThickness(1)
+3 -3
View File
@@ -24,7 +24,7 @@ type TagOptions struct {
// TagView is a shown Tag and this frame's events.
type TagView struct {
Element *ui.Element
Element ui.Element
// Clicked reports the label was activated, not the remove button.
Clicked bool
// Closed reports the remove button, Delete or Backspace.
@@ -43,7 +43,7 @@ func Tag(c *ui.Context, label string, opts TagOptions) TagView {
Background(k.Surface).Border(theme.BorderWidth, k.Border).Shrink(0).Disabled(opts.Disabled)
v.Element = box
box.Children(func() {
var main *ui.Element
var main ui.Element
if opts.Clickable {
main = ui.ButtonBase(c).Label(label)
internal.Ring(main, k.Focus, theme.ControlRadius)
@@ -89,7 +89,7 @@ func Tag(c *ui.Context, label string, opts TagOptions) TagView {
return v
}
func tagClose(c *ui.Context, k theme.Tokens, label string) *ui.Element {
func tagClose(c *ui.Context, k theme.Tokens, label string) ui.Element {
b := ui.ButtonBase(c).Size(theme.MinHitSize, theme.MinHitSize).
Radius(theme.ControlRadius).Label(fmt.Sprintf(core.Msg(c, "tag.remove"), label))
fg := k.TextMuted
+3 -3
View File
@@ -19,7 +19,7 @@ type TextOptions struct {
}
// Text shows body text wrapping at its width.
func Text(c *ui.Context, s string, opts TextOptions) *ui.Element {
func Text(c *ui.Context, s string, opts TextOptions) ui.Element {
if opts.MaxLines < 0 {
panic("mujica: Text MaxLines must not be negative")
}
@@ -49,7 +49,7 @@ type EditableTextOptions struct {
// EditableTextView is a shown EditableText and this frame's outcome.
type EditableTextView struct {
Element *ui.Element
Element ui.Element
// Canceled reports Escape discarded an edit.
Canceled bool
changed bool
@@ -118,7 +118,7 @@ func editableTextField(c *ui.Context, k theme.Tokens, value *string, opts Editab
return v
}
}
var in *ui.Element
var in ui.Element
box.Children(func() {
in = ui.TextInputBase(c, &st.draft).Grow(1).MinWidth(theme.MinHitSize*3).Padding(pad, pad*2).
Radius(theme.ControlRadius).Background(k.Surface).Border(theme.BorderWidth, k.Accent).TextColor(k.Text).