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

+3 -3
View File
@@ -29,7 +29,7 @@ type BreadcrumbOptions struct {
// BreadcrumbResult is the built Breadcrumb.
type BreadcrumbResult struct {
Element *ui.Element
Element ui.Element
chosen int
}
@@ -135,14 +135,14 @@ func breadcrumbMore(c *ui.Context, hidden []BreadcrumbItem, choose func(int)) {
*open = !*open
}
b.Children(func() { ui.Icon(c, icons.Must("ellipsis")).Size(16, 16) })
ui.PopoverBase(c, b, open, func(panel *ui.Element) {
ui.PopoverBase(c, b, open, func(panel ui.Element) {
core.OverlayStyle(c, panel, core.OverlayEnter(c, panel))
panel.Margin(u, 0, 0, 0).Padding(u).Gap(u * 0.5).MinWidth(160).MaxWidth(320)
for j, it := range hidden {
item := ui.ButtonBase(c).Role(ui.RoleLink).Justify(ui.Start).Gap(u*2).Padding(u*1.5, u*2).
Radius(theme.ControlRadius).MinHeight(core.Density(c).ControlHeight() - u*2).Label(it.Label).TextColor(k.Text)
if j == 0 {
item.AutoFocus()
core.AutoFocus(c, item)
}
internal.Ring(item, k.Focus, theme.ControlRadius)
if item.Hovered() {
+4 -4
View File
@@ -33,7 +33,7 @@ type CommandPaletteOptions struct {
// CommandPaletteResult is the built CommandPalette; Element is nil while closed.
type CommandPaletteResult struct {
Element *ui.Element
Element ui.Element
chosen string
}
@@ -79,7 +79,7 @@ func CommandPalette(c *ui.Context, open *bool, commands []Command, opts CommandP
k := core.Tokens(c)
d := core.Density(c)
u := d.Unit()
res.Element = ui.DialogBase(c, open, func(back, panel *ui.Element) {
res.Element = ui.DialogBase(c, open, func(back, panel ui.Element) {
ww, wh := c.Size()
p := core.OverlayEnter(c, panel)
back.Background(ui.RGBA(0, 0, 0, 0.4*p)).Justify(ui.Start)
@@ -87,10 +87,10 @@ func CommandPalette(c *ui.Context, open *bool, commands []Command, opts CommandP
panel.Margin(wh*0.12, 0, 0, 0).Width(560).MaxWidth(ww - 32).MaxHeight(wh * 0.76).
AlignItems(ui.Stretch).Label(core.Msg(c, "command_palette.label"))
found := commandPaletteFilter(commands, st.query)
var in *ui.Element
var in ui.Element
ui.Row(c).AlignItems(ui.Center).Gap(u*3).Padding(u*3, u*4).Children(func() {
ui.Icon(c, icons.Must("search")).Size(16, 16).TextColor(k.TextMuted)
in = ui.TextInputBase(c, &st.query).FillWidth().AutoFocus().Label(core.Msg(c, "command_palette.search"))
in = core.AutoFocus(c, ui.TextInputBase(c, &st.query).FillWidth()).Label(core.Msg(c, "command_palette.search"))
})
ui.Box(c).Height(1).Background(k.Ornament).Role(ui.RoleNone)
if in.Changed() {
+3 -3
View File
@@ -57,7 +57,7 @@ type MenubarOptions struct {
// MenubarResult is the built Menubar.
type MenubarResult struct {
Element *ui.Element
Element ui.Element
chosen string
}
@@ -167,7 +167,7 @@ func (m *menubarRun) current() string {
return ""
}
func (m *menubarRun) title(i int, label string) *ui.Element {
func (m *menubarRun) title(i int, label string) ui.Element {
c, st := m.c, m.st
k := core.Tokens(c)
d := core.Density(c)
@@ -211,7 +211,7 @@ func (m *menubarRun) title(i int, label string) *ui.Element {
}
// keys drives an open menu from the focused title.
func (m *menubarRun) keys(t *ui.Element) {
func (m *menubarRun) keys(t ui.Element) {
st := m.st
l := len(st.hl) - 1
items := m.items(l)
+7 -4
View File
@@ -50,7 +50,7 @@ func (m *menubarRun) overlay(titles []ui.Rect) {
var prev ui.Rect
for l := 0; l < len(st.hl) && st.open >= 0; l++ {
panel, anchor := m.panel(l, x, y, prev)
if anchor == nil || l+1 >= len(st.hl) {
if !anchor.Valid() || l+1 >= len(st.hl) {
break
}
a := anchor.Bounds()
@@ -61,7 +61,7 @@ func (m *menubarRun) overlay(titles []ui.Rect) {
}
// panel builds level l at (x, y) and returns it with its highlighted row.
func (m *menubarRun) panel(l int, x, y float32, parent ui.Rect) (*ui.Element, *ui.Element) {
func (m *menubarRun) panel(l int, x, y float32, parent ui.Rect) (ui.Element, ui.Element) {
c, st := m.c, m.st
k := core.Tokens(c)
u := core.Density(c).Unit()
@@ -84,7 +84,7 @@ func (m *menubarRun) panel(l int, x, y float32, parent ui.Rect) (*ui.Element, *u
if panel.Clicked() || panel.Pressed() {
m.focus(st.open)
}
var anchor *ui.Element
var anchor ui.Element
panel.Children(func() {
for j, it := range items {
if it.Kind == MenubarSeparator {
@@ -92,6 +92,9 @@ func (m *menubarRun) panel(l int, x, y float32, parent ui.Rect) (*ui.Element, *u
continue
}
row := m.item(it, st.hl[l] == j)
if it.Disabled {
row.PassThrough() // its press reaches the panel, which keeps the title focused
}
if st.hl[l] == j {
anchor = row
if st.reveal && l == len(st.hl)-1 {
@@ -126,7 +129,7 @@ func (m *menubarRun) panel(l int, x, y float32, parent ui.Rect) (*ui.Element, *u
return panel, anchor
}
func (m *menubarRun) item(it MenubarItem, hl bool) *ui.Element {
func (m *menubarRun) item(it MenubarItem, hl bool) ui.Element {
c := m.c
k := core.Tokens(c)
d := core.Density(c)
+5 -5
View File
@@ -31,7 +31,7 @@ type NavigationMenuOptions struct {
}
// NavigationMenu shows links pushing pages on router; the router's path marks the current one.
func NavigationMenu(c *ui.Context, router *ui.Router, links []NavigationLink, opts NavigationMenuOptions) *ui.Element {
func NavigationMenu(c *ui.Context, router *ui.Router, links []NavigationLink, opts NavigationMenuOptions) ui.Element {
if router == nil {
panic("mujica: NavigationMenu needs a router")
}
@@ -69,7 +69,7 @@ func NavigationMenu(c *ui.Context, router *ui.Router, links []NavigationLink, op
return row
}
func navigationMenuHistory(c *ui.Context, b *ui.Element, name string) {
func navigationMenuHistory(c *ui.Context, b ui.Element, name string) {
k := core.Tokens(c)
d := core.Density(c)
b.Label(name).MinHeight(d.ControlHeight()).MinWidth(d.ControlHeight()).Radius(theme.ControlRadius).Background(k.Surface)
@@ -147,7 +147,7 @@ func navigationMenuTop(c *ui.Context, l NavigationLink, path string, push func(s
} else if b.Shortcut(0, ui.KeyDown) {
*open = true
}
ui.PopoverBase(c, b, open, func(panel *ui.Element) {
ui.PopoverBase(c, b, open, func(panel ui.Element) {
core.OverlayStyle(c, panel, core.OverlayEnter(c, panel))
// Capped to the room beside the anchor; the links scroll inside.
_, wh := c.Size()
@@ -189,13 +189,13 @@ func navigationMenuPanel(c *ui.Context, items []NavigationLink, path string, ope
list(items)
}
func navigationMenuItem(c *ui.Context, l NavigationLink, on, first bool) *ui.Element {
func navigationMenuItem(c *ui.Context, l NavigationLink, on, first bool) ui.Element {
k := core.Tokens(c)
u := core.Density(c).Unit()
e := ui.ButtonBase(c).Role(ui.RoleLink).Justify(ui.Start).AlignItems(ui.Start).Gap(u*3).
Padding(u*2, u*3).Radius(theme.ControlRadius).Label(l.Label).TextColor(k.Text).Cursor(ui.CursorPointer)
if first {
e.AutoFocus()
core.AutoFocus(c, e)
}
internal.Ring(e, k.Focus, theme.ControlRadius)
switch {
+4 -6
View File
@@ -36,7 +36,6 @@ func TestNavigationPopupsFadeIn(t *testing.T) {
CommandPalette(c, &open, commandPaletteSample(), CommandPaletteOptions{})
}},
} {
opened := time.Now()
tt := ui.NewTester(func(c *ui.Context) {
core.Use(c, core.Settings{Mode: core.Light})
ui.Column(c).Fill().Padding(16).AlignItems(ui.Start).Children(func() { tc.view(c) })
@@ -51,12 +50,11 @@ func TestNavigationPopupsFadeIn(t *testing.T) {
t.Fatalf("%s: no %q", tc.name, tc.sample)
}
x, y := int(r.X-3), int(r.Y+r.H/2)
// The Tester clock stands still, so a quarter of the way in is exact.
tt.Advance(theme.PopupDuration / 4)
early := tt.Image().RGBAAt(x, y)
mid := time.Since(opened) < theme.PopupDuration/2
time.Sleep(200 * time.Millisecond)
tt.Frame()
// A slow run may finish the fade before the first sample.
if late := tt.Image().RGBAAt(x, y); mid && early == late {
tt.Advance(200 * time.Millisecond)
if late := tt.Image().RGBAAt(x, y); early == late {
t.Errorf("%s: popup appeared at once (%v)", tc.name, late)
}
}
+3 -3
View File
@@ -126,7 +126,7 @@ func paginationItems(cur, pages, sib int) []int {
return append(out, pages)
}
func paginationButton(c *ui.Context, text string, icon *ui.SVG, name string, on, disabled bool) *ui.Element {
func paginationButton(c *ui.Context, text string, icon *ui.SVG, name string, on, disabled bool) ui.Element {
k := core.Tokens(c)
h := core.Density(c).ControlHeight()
b := ui.ButtonBase(c).MinHeight(h).MinWidth(h).Padding(0, core.Density(c).Unit()*2).Radius(theme.ControlRadius).
@@ -175,7 +175,7 @@ func paginationSizes(c *ui.Context, page, pageSize *int, sizes []int, res *core.
ui.Text(c, fmt.Sprintf(core.Msg(c, "pagination.per_page"), *pageSize)).SingleLine()
ui.Icon(c, icons.Must("chevron-down")).Size(14, 14).TextColor(k.TextMuted)
})
sel.Popup(func(panel *ui.Element) {
sel.Popup(func(panel ui.Element) {
core.OverlayStyle(c, panel, core.OverlayEnter(c, panel))
panel.Margin(u, 0, 0, 0).Padding(u)
for _, s := range sizes {
@@ -211,7 +211,7 @@ func paginationJump(c *ui.Context, pages int, set func(int)) {
Radius(theme.ControlRadius).Background(k.Surface)
text := ui.Local(box, "text", func() string { return "" })
bad := ui.Local(box, "bad", func() bool { return false })
var in *ui.Element
var in ui.Element
box.Children(func() {
in = ui.TextInputBase(c, text).FillWidth().Label(core.Msg(c, "pagination.field"))
})
+1 -1
View File
@@ -28,7 +28,7 @@ type SegmentedControlOptions struct {
}
// SegmentedControl chooses one of segments into *selected; Changed reports a new choice.
func SegmentedControl(c *ui.Context, selected *int, segments []Segment, opts SegmentedControlOptions) *ui.Element {
func SegmentedControl(c *ui.Context, selected *int, segments []Segment, opts SegmentedControlOptions) ui.Element {
if len(segments) == 0 {
panic("mujica: SegmentedControl needs at least one segment")
}
+1 -1
View File
@@ -86,7 +86,7 @@ func Steps(c *ui.Context, current *int, steps []Step, opts StepsOptions) core.Ch
func stepsItem(c *ui.Context, i, n int, s Step, st stepsState, vertical, tail, doneLine bool) bool {
k := core.Tokens(c)
u := core.Density(c).Unit()
var e *ui.Element
var e ui.Element
if s.Clickable {
e = ui.ButtonBase(c).Cursor(ui.CursorPointer)
internal.Ring(e, k.Focus, theme.ControlRadius)
+4 -4
View File
@@ -65,7 +65,7 @@ func Tabs(c *ui.Context, selected *int, tabs []Tab, opts TabsOptions) core.Choic
}
res := core.ChoiceResult{Element: root}
root.Children(func() {
var strip *ui.Element
var strip ui.Element
if opts.Vertical {
strip = ui.Scroll(c).Shrink(0).Padding(pad)
} else {
@@ -87,11 +87,11 @@ func Tabs(c *ui.Context, selected *int, tabs []Tab, opts TabsOptions) core.Choic
if opts.Label != "" {
list.Label(opts.Label)
}
built := make([]*ui.Element, len(tabs))
built := make([]ui.Element, len(tabs))
list.Children(func() {
n := 0
for i, t := range tabs {
var tab *ui.Element
var tab ui.Element
if t.Disabled {
tab = ui.Row(c).Shrink(0).Role(ui.RoleTab).Disabled(true)
} else {
@@ -121,7 +121,7 @@ func Tabs(c *ui.Context, selected *int, tabs []Tab, opts TabsOptions) core.Choic
return res
}
func TabsStyle(c *ui.Context, tab *ui.Element, t Tab, on, vertical bool) {
func TabsStyle(c *ui.Context, tab ui.Element, t Tab, on, vertical bool) {
k := core.Tokens(c)
d := core.Density(c)
u := d.Unit()
+1 -2
View File
@@ -186,8 +186,7 @@ func TestTabsStateColorsEase(t *testing.T) {
if got := tt.Image().RGBAAt(x, y); got == hover {
t.Fatal("hover color applied at once")
}
time.Sleep(theme.StateDuration + 40*time.Millisecond)
tt.Frame()
tt.Advance(theme.StateDuration + 40*time.Millisecond)
if got := tt.Image().RGBAAt(x, y); got != hover {
t.Fatalf("hover color %v after the transition, want %v", got, hover)
}
+3 -3
View File
@@ -15,7 +15,7 @@ type ToolbarOptions struct {
}
// Toolbar is one Tab stop of actions; those that do not fit go to an overflow menu.
func Toolbar(c *ui.Context, opts ToolbarOptions, fn func()) *ui.Element {
func Toolbar(c *ui.Context, opts ToolbarOptions, fn func()) ui.Element {
k := core.Tokens(c)
tb := ui.Toolbar(c, fn).Background(k.Surface).Border(theme.BorderWidth, k.Border).Radius(theme.CardRadius).
MinHeight(core.Density(c).ControlHeight() + 8)
@@ -34,7 +34,7 @@ type ToolbarButtonOptions struct {
}
// ToolbarButton is an action of a Toolbar; label names it and its overflow menu item.
func ToolbarButton(c *ui.Context, label string, opts ToolbarButtonOptions) *ui.Element {
func ToolbarButton(c *ui.Context, label string, opts ToolbarButtonOptions) ui.Element {
if label == "" {
panic("mujica: ToolbarButton needs a label")
}
@@ -70,7 +70,7 @@ func ToolbarButton(c *ui.Context, label string, opts ToolbarButtonOptions) *ui.E
}
// ToolbarGroup keeps related actions together; the overflow menu sets groups apart.
func ToolbarGroup(c *ui.Context, fn func()) *ui.Element {
func ToolbarGroup(c *ui.Context, fn func()) ui.Element {
return ui.Row(c).AlignItems(ui.Center).Gap(core.Density(c).Unit() * 0.5).Shrink(0).Role(ui.RoleGroup).Children(fn)
}