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
@@ -28,7 +28,7 @@ type AppShellOptions struct {
// AppShellResult is an AppShell and its event in this frame.
type AppShellResult struct {
Element *ui.Element
Element ui.Element
// Toggled reports the sidebar opened or collapsed.
Toggled bool
}
+1 -1
View File
@@ -13,7 +13,7 @@ type AspectRatioOptions struct {
}
// AspectRatio centers the largest Ratio frame that fits; the child is centered, not stretched.
func AspectRatio(c *ui.Context, opts AspectRatioOptions, child func()) *ui.Element {
func AspectRatio(c *ui.Context, opts AspectRatioOptions, child func()) ui.Element {
if opts.Ratio < 0 {
panic("mujica: AspectRatio Ratio must be positive")
}
+1 -1
View File
@@ -62,7 +62,7 @@ type ContainerOptions struct {
}
// Container pads its children, caps their width and can center, fill and clip.
func Container(c *ui.Context, opts ContainerOptions, children func()) *ui.Element {
func Container(c *ui.Context, opts ContainerOptions, children func()) ui.Element {
if opts.MaxWidth < 0 {
panic("mujica: Container MaxWidth must not be negative")
}
+1 -1
View File
@@ -22,7 +22,7 @@ type DividerOptions struct {
}
// Divider is a thin separating line. It never takes focus and its ornament is not read out.
func Divider(c *ui.Context, opts DividerOptions) *ui.Element {
func Divider(c *ui.Context, opts DividerOptions) ui.Element {
if opts.Vertical && opts.Label != "" {
panic("mujica: a vertical Divider cannot have a Label")
}
+3 -3
View File
@@ -19,7 +19,7 @@ type GridOptions struct {
}
// Grid lays children out in columns and rows; GridCell places and spans them.
func Grid(c *ui.Context, opts GridOptions, children func()) *ui.Element {
func Grid(c *ui.Context, opts GridOptions, children func()) ui.Element {
if children == nil {
panic("mujica: Grid needs children")
}
@@ -48,7 +48,7 @@ type GridCellOptions struct {
}
// GridCell is one child of a Grid, placed and spanned by opts; build makes one element, stretched over the cell.
func GridCell(c *ui.Context, opts GridCellOptions, build func()) *ui.Element {
func GridCell(c *ui.Context, opts GridCellOptions, build func()) ui.Element {
if build == nil {
panic("mujica: GridCell needs build")
}
@@ -59,7 +59,7 @@ func GridCell(c *ui.Context, opts GridCellOptions, build func()) *ui.Element {
panic("mujica: GridCell spans must be positive, or -1 for the rest")
}
// A one-track grid stretches its child over the whole cell, as the grid does its own children.
e := ui.Grid(c).Columns(1).Rows(1)
e := ui.Grid(c).Columns(1).GridRows(1)
if opts.Column > 0 {
e.ColumnStart(opts.Column)
}
+2 -2
View File
@@ -24,11 +24,11 @@ type ScrollAreaOptions struct {
}
// ScrollArea scrolls children; state keeps and sets the offset, nil keeps it by place.
func ScrollArea(c *ui.Context, state *ui.ScrollState, opts ScrollAreaOptions, children func()) *ui.Element {
func ScrollArea(c *ui.Context, state *ui.ScrollState, opts ScrollAreaOptions, children func()) ui.Element {
if children == nil {
panic("mujica: ScrollArea needs children")
}
var e *ui.Element
var e ui.Element
switch opts.Direction {
case ScrollVertical:
e = ui.Scroll(c)
+9 -9
View File
@@ -40,7 +40,7 @@ type SplitPaneOptions struct {
// SplitPaneResult is a SplitPane and its events in this frame.
type SplitPaneResult struct {
Element *ui.Element
Element ui.Element
Resized bool
Toggled bool
}
@@ -80,7 +80,7 @@ func SplitPane(c *ui.Context, state *SplitPaneState, opts SplitPaneOptions, firs
var res SplitPaneResult
e.Children(func() {
// Sizes come from this inner box: the caller's border and padding stay outside it.
in := ui.Row(c).Key("in").Grow(1).MinWidth(0).AlignItems(ui.Stretch)
in := ui.Row(c.Key("in")).Grow(1).MinWidth(0).AlignItems(ui.Stretch)
if opts.Vertical {
in.Column().MinHeight(0)
}
@@ -94,7 +94,7 @@ func SplitPane(c *ui.Context, state *SplitPaneState, opts SplitPaneOptions, firs
}
// splitPaneBuild lays the panes, divider and drag handle out in the inner box in.
func splitPaneBuild(c *ui.Context, in *ui.Element, state *SplitPaneState, opts SplitPaneOptions, lo, minSecond float32, res *SplitPaneResult, first, second func()) {
func splitPaneBuild(c *ui.Context, in ui.Element, state *SplitPaneState, opts SplitPaneOptions, lo, minSecond float32, res *SplitPaneResult, first, second func()) {
k := core.Tokens(c)
u := core.Density(c).Unit()
was := in.Bounds()
@@ -123,7 +123,7 @@ func splitPaneBuild(c *ui.Context, in *ui.Element, state *SplitPaneState, opts S
}
firstShown := !(state.Collapsed && opts.Collapsible == SplitPaneFirst)
secondShown := !(state.Collapsed && opts.Collapsible == SplitPaneSecond)
along := func(el *ui.Element, v float32) {
along := func(el ui.Element, v float32) {
if opts.Vertical {
el.Height(v)
} else {
@@ -132,7 +132,7 @@ func splitPaneBuild(c *ui.Context, in *ui.Element, state *SplitPaneState, opts S
}
in.Children(func() {
if firstShown {
a := ui.Box(c).Key("first").Shrink(0).Clip()
a := ui.Box(c.Key("first")).Shrink(0).Clip()
switch {
case !secondShown:
a.Grow(1).Shrink(1)
@@ -145,7 +145,7 @@ func splitPaneBuild(c *ui.Context, in *ui.Element, state *SplitPaneState, opts S
}
a.Children(first)
}
div := ui.Box(c).Key("divider").Shrink(0).Focusable().FocusRing(false).Role(ui.RoleSplitter).Label(core.Msg(c, "split_pane.divider"))
div := ui.Box(c.Key("divider")).Shrink(0).Focusable().FocusRing(false).Role(ui.RoleSplitter).Label(core.Msg(c, "split_pane.divider"))
along(div, theme.BorderWidth)
if state.Collapsed {
div.Description(core.Msg(c, "split_pane.collapsed"))
@@ -154,7 +154,7 @@ func splitPaneBuild(c *ui.Context, in *ui.Element, state *SplitPaneState, opts S
}
splitPaneKeys(c, div, state, opts, lo, hi, u, resize, toggle)
if secondShown {
b := ui.Box(c).Key("second").Grow(1).Shrink(1).Clip()
b := ui.Box(c.Key("second")).Grow(1).Shrink(1).Clip()
if opts.Vertical {
b.MinHeight(0)
} else {
@@ -170,7 +170,7 @@ func splitPaneBuild(c *ui.Context, in *ui.Element, state *SplitPaneState, opts S
at = total - theme.BorderWidth
}
grip := 1.5 * u
handle := ui.Box(c).Key("handle").Absolute()
handle := ui.Box(c.Key("handle")).Absolute()
if opts.Vertical {
handle.Left(0).Right(0).Top(at - (grip-1)/2).Height(grip).Cursor(ui.CursorResizeNS)
} else {
@@ -238,7 +238,7 @@ func splitPaneFollow(state *SplitPaneState, opts SplitPaneOptions, want, lo, min
}
// splitPaneKeys moves the divider with the arrows, Home and End, and toggles the collapsible pane with Enter.
func splitPaneKeys(c *ui.Context, div *ui.Element, state *SplitPaneState, opts SplitPaneOptions, lo, hi, u float32, resize func(float32), toggle func()) {
func splitPaneKeys(c *ui.Context, div ui.Element, state *SplitPaneState, opts SplitPaneOptions, lo, hi, u float32, resize func(float32), toggle func()) {
back, forth := ui.KeyLeft, ui.KeyRight
if opts.Vertical {
back, forth = ui.KeyUp, ui.KeyDown
+1 -1
View File
@@ -31,7 +31,7 @@ type StackOptions struct {
}
// Stack arranges children in a row or column. A child not built takes no room and no gap.
func Stack(c *ui.Context, opts StackOptions, children func()) *ui.Element {
func Stack(c *ui.Context, opts StackOptions, children func()) ui.Element {
if children == nil {
panic("mujica: Stack needs children")
}
+9 -9
View File
@@ -47,7 +47,7 @@ type StatusBarOptions struct {
// StatusBarResult is a StatusBar and its event in this frame.
type StatusBarResult struct {
Element *ui.Element
Element ui.Element
// Clicked is the ID of the action item clicked, or empty.
Clicked string
}
@@ -86,7 +86,7 @@ func StatusBar(c *ui.Context, opts StatusBarOptions) StatusBarResult {
return hasFocus
}
group := func(key string, items []StatusItem) {
ui.Row(c).Key(key).Gap(gap).Shrink(0).Dividers(theme.BorderWidth, k.Border).Children(func() {
ui.Row(c.Key(key)).Gap(gap).Shrink(0).Dividers(theme.BorderWidth, k.Border).Children(func() {
for _, it := range items {
if !hidden[it.ID] && item(it) {
*focused = it.ID
@@ -96,7 +96,7 @@ func StatusBar(c *ui.Context, opts StatusBarOptions) StatusBarResult {
}
e.Children(func() {
group("status_bar.left", opts.Left)
ui.Spacer(c).Key("status_bar.spacer")
ui.Spacer(c.Key("status_bar.spacer"))
if len(hidden) > 0 {
more := statusBarMore(c, len(hidden))
if wasFocused != "" && hidden[wasFocused] {
@@ -108,7 +108,7 @@ func StatusBar(c *ui.Context, opts StatusBarOptions) StatusBarResult {
slog.Debug("mujica: status_bar hidden items", "open", *open)
}
if *open {
ui.PopoverBase(c, more, open, func(panel *ui.Element) {
ui.PopoverBase(c, more, open, func(panel ui.Element) {
core.OverlayStyle(c, panel, core.OverlayEnter(c, panel))
panel.Margin(d.Unit(), 0, 0, 0).Padding(d.Unit()).Gap(d.Unit()).AlignItems(ui.Stretch)
for _, it := range statusBarAll(opts) {
@@ -126,9 +126,9 @@ func StatusBar(c *ui.Context, opts StatusBarOptions) StatusBarResult {
}
// statusBarMore is the "+N" button that opens the hidden items.
func statusBarMore(c *ui.Context, n int) *ui.Element {
func statusBarMore(c *ui.Context, n int) ui.Element {
k := core.Tokens(c)
b := ui.ButtonBase(c).Key("status_bar.more").MinHeight(theme.MinHitSize).MinWidth(theme.MinHitSize).
b := ui.ButtonBase(c.Key("status_bar.more")).MinHeight(theme.MinHitSize).MinWidth(theme.MinHitSize).
PaddingX(core.Density(c).Unit()).Radius(theme.ControlRadius).TextColor(k.Text).
Label(fmt.Sprintf(core.Msg(c, "status_bar.hidden"), n))
internal.Ring(b, k.Focus, theme.ControlRadius)
@@ -248,9 +248,9 @@ func statusBarItem(c *ui.Context, it StatusItem) (clicked, focused bool) {
default:
panic("mujica: invalid StatusItemTone")
}
var e *ui.Element
var e ui.Element
if it.Action {
e = ui.ButtonBase(c).Key(it.ID).MinHeight(theme.MinHitSize).MinWidth(theme.MinHitSize).Radius(theme.ControlRadius)
e = ui.ButtonBase(c.Key(it.ID)).MinHeight(theme.MinHitSize).MinWidth(theme.MinHitSize).Radius(theme.ControlRadius)
internal.Ring(e, k.Focus, theme.ControlRadius)
if e.Pressed() {
e.Background(k.Selection)
@@ -258,7 +258,7 @@ func statusBarItem(c *ui.Context, it StatusItem) (clicked, focused bool) {
e.Background(k.SurfaceHover)
}
} else {
e = ui.Row(c).Key(it.ID).MinHeight(theme.MinHitSize)
e = ui.Row(c.Key(it.ID)).MinHeight(theme.MinHitSize)
}
e.PaddingX(d.Unit()).Gap(d.Unit()).TextColor(fg).Shrink(0)
if it.Label != "" {
+1 -1
View File
@@ -16,7 +16,7 @@ type TitleBarOptions struct {
}
// TitleBar is a window title row that drags the window and clears the system buttons.
func TitleBar(c *ui.Context, title string, opts TitleBarOptions) *ui.Element {
func TitleBar(c *ui.Context, title string, opts TitleBarOptions) ui.Element {
k := core.Tokens(c)
d := core.Density(c)
bar := c.TitleBar()