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
@@ -56,7 +56,7 @@ type ArchiveViewerOptions struct {
// ArchiveViewerResult is the viewer and the entry opened this frame.
type ArchiveViewerResult struct {
Element *ui.Element
Element ui.Element
Opened *ArchiveEntry
Retried bool
}
+1 -1
View File
@@ -83,7 +83,7 @@ func (s *FileExplorerState) step(from, to *[]string) {
// FileExplorerResult is a FileExplorer and what happened in this frame.
type FileExplorerResult struct {
Element *ui.Element
Element ui.Element
// Navigated reports a new current folder; Changed a new selection.
Navigated, Changed bool
// Opened is a file the user opened.
+1 -1
View File
@@ -23,7 +23,7 @@ type FileGridOptions struct {
// FileGridResult is a FileGrid and what happened in this frame.
type FileGridResult struct {
Element *ui.Element
Element ui.Element
// Changed reports a new selection; Opened is the entry opened.
Changed bool
Opened *FileEntry
+1 -1
View File
@@ -75,7 +75,7 @@ func fileKindColor(k theme.Tokens, kind FileKind) ui.Color {
}
// FileIcon shows file type or folder opening state.
func FileIcon(c *ui.Context, name string, opts FileIconOptions) *ui.Element {
func FileIcon(c *ui.Context, name string, opts FileIconOptions) ui.Element {
if opts.Size < 0 {
panic("mujica: FileIcon Size is negative")
}
+1 -1
View File
@@ -47,7 +47,7 @@ const (
// FileOperationResult is the panel and the user's answer this frame.
type FileOperationResult struct {
Element *ui.Element
Element ui.Element
Canceled bool
// Resolved answers a conflict; ApplyAll extends that decision.
Resolved ConflictChoice
+1 -1
View File
@@ -37,7 +37,7 @@ type FilePreviewOptions struct {
// FilePreviewResult exposes the panel and requested actions.
type FilePreviewResult struct {
Element *ui.Element
Element ui.Element
// Closed, Opened and Retried report requested actions.
Closed, Opened, Retried bool
}
+2 -2
View File
@@ -24,7 +24,7 @@ type PathBarOptions struct {
// PathBarResult exposes the toolbar and requested path.
type PathBarResult struct {
Element *ui.Element
Element ui.Element
// Navigated is the chosen ancestor path, or empty.
Navigated string
}
@@ -140,7 +140,7 @@ func pathBarFold(c *ui.Context, segs []pathSegment, room float32) int {
}
// pathBarCrumb builds a button, emphasizing the current segment.
func pathBarCrumb(c *ui.Context, label string, current bool) *ui.Element {
func pathBarCrumb(c *ui.Context, label string, current bool) ui.Element {
k := core.Tokens(c)
d := core.Density(c)
b := ui.ButtonBase(c).MinHeight(theme.MinHitSize).MinWidth(theme.MinHitSize).Padding(0, d.Unit()*2).
+5 -5
View File
@@ -34,7 +34,7 @@ type PresenceAvatarsOptions struct {
// PresenceAvatarsResult is the avatars and the follow request this frame.
type PresenceAvatarsResult struct {
Element *ui.Element
Element ui.Element
// Follow identifies a collaborator; Unfollow requests stopping.
Follow string
Unfollow bool
@@ -94,7 +94,7 @@ func PresenceAvatars(c *ui.Context, people []Collaborator, opts PresenceAvatarsO
rows.Key = func(i int) any { return people[limit+i].ID }
ui.List(c, rows, extra, func(i int) {
p := people[limit+i]
ui.Row(c).Key(p.ID).Gap(u * 2).AlignItems(ui.Center).Children(func() {
ui.Row(c.Key(p.ID)).Gap(u * 2).AlignItems(ui.Center).Children(func() {
if presenceAvatar(c, p, 24, p.ID == opts.Following).Clicked() {
pick(p)
}
@@ -108,7 +108,7 @@ func PresenceAvatars(c *ui.Context, people []Collaborator, opts PresenceAvatarsO
}
// presenceAvatar builds a collaborator button with presence status.
func presenceAvatar(c *ui.Context, p Collaborator, size float32, following bool) *ui.Element {
func presenceAvatar(c *ui.Context, p Collaborator, size float32, following bool) ui.Element {
if p.ID == "" {
panic("mujica: Collaborator needs an ID")
}
@@ -117,7 +117,7 @@ func presenceAvatar(c *ui.Context, p Collaborator, size float32, following bool)
if following {
key = "presence_avatars.unfollow"
}
b := ui.ButtonBase(c).Key(p.ID).Size(max(size+4, theme.MinHitSize), max(size+4, theme.MinHitSize)).Radius((size + 4) / 2).Label(fmt.Sprintf(core.Msg(c, key), p.Name)).Tooltip(p.Name)
b := ui.ButtonBase(c.Key(p.ID)).Size(max(size+4, theme.MinHitSize), max(size+4, theme.MinHitSize)).Radius((size + 4) / 2).Label(fmt.Sprintf(core.Msg(c, key), p.Name)).Tooltip(p.Name)
if p.Away {
b.Description(core.Msg(c, "presence_avatars.away"))
}
@@ -134,7 +134,7 @@ func presenceAvatar(c *ui.Context, p Collaborator, size float32, following bool)
}
// presenceStyle styles hover, focus and following rings.
func presenceStyle(c *ui.Context, b *ui.Element, size float32, following bool) {
func presenceStyle(c *ui.Context, b ui.Element, size float32, following bool) {
k := core.Tokens(c)
b.Shrink(0).Background(k.Surface).FocusRing(false)
if b.Hovered() {
+4 -4
View File
@@ -33,7 +33,7 @@ type ReactionsOptions struct {
// ReactionsResult exposes reactions and requested changes.
type ReactionsResult struct {
Element *ui.Element
Element ui.Element
// Toggled identifies a reaction; Added identifies a picker choice.
Toggled, Added string
}
@@ -65,7 +65,7 @@ func Reactions(c *ui.Context, reactions []Reaction, opts ReactionsOptions) React
overlay.Popover(c, add, open, overlay.PopoverOptions{Label: core.Msg(c, "reactions.pick")}, func() {
ui.Grid(c).Columns(min(len(opts.Choices), 6)).Gap(u).FocusGroup(ui.Horizontal).Children(func() {
for _, e := range opts.Choices {
b := ui.ButtonBase(c).Key(e).Size(36, 36).Radius(theme.ControlRadius).Label(e)
b := ui.ButtonBase(c.Key(e)).Size(36, 36).Radius(theme.ControlRadius).Label(e)
internal.Ring(b, core.Tokens(c).Focus, theme.ControlRadius)
if b.Hovered() {
b.Background(core.Tokens(c).SurfaceHover)
@@ -85,14 +85,14 @@ func Reactions(c *ui.Context, reactions []Reaction, opts ReactionsOptions) React
}
// reactionChip frames and marks the current user's reaction.
func reactionChip(c *ui.Context, r Reaction) *ui.Element {
func reactionChip(c *ui.Context, r Reaction) ui.Element {
k := core.Tokens(c)
u := core.Density(c).Unit()
label := fmt.Sprintf(core.Msg(c, "reactions.count"), r.Emoji, r.Count)
if r.Mine {
label += ", " + core.Msg(c, "reactions.mine")
}
b := ui.ButtonBase(c).Key(r.Emoji).MinHeight(theme.MinHitSize).MinWidth(theme.MinHitSize).Padding(0, u*2).Gap(u).
b := ui.ButtonBase(c.Key(r.Emoji)).MinHeight(theme.MinHitSize).MinWidth(theme.MinHitSize).Padding(0, u*2).Gap(u).
Radius(theme.MinHitSize / 2).Role(ui.RoleToggleButton).Selected(r.Mine).Label(label).Transition(core.ColorFade)
if len(r.People) > 0 {
b.Tooltip(strings.Join(r.People, ", "))
+1 -1
View File
@@ -49,7 +49,7 @@ type recentRow struct{ group, file int }
// RecentFilesResult is the list and what happened this frame.
type RecentFilesResult struct {
Element *ui.Element
Element ui.Element
Opened *RecentFile
// Removed identifies one file; Cleared requests clearing all files.
Removed string
+1 -1
View File
@@ -30,7 +30,7 @@ type RemoteCursorOptions struct {
// RemoteCursor overlays collaborator carets, selections and names; input passes through.
// Place it filling the editor's box, after the editor.
func RemoteCursor(c *ui.Context, carets []RemoteCaret, opts RemoteCursorOptions) *ui.Element {
func RemoteCursor(c *ui.Context, carets []RemoteCaret, opts RemoteCursorOptions) ui.Element {
if opts.Caret == nil {
panic("mujica: RemoteCursor needs Caret")
}
+6 -5
View File
@@ -23,7 +23,7 @@ type RenameInlineOptions struct {
// RenameInlineResult is a RenameInline and its outcome in this frame.
type RenameInlineResult struct {
Element *ui.Element
Element ui.Element
// Renamed reports a saved name; Canceled reports an abandoned edit.
Renamed, Canceled bool
}
@@ -104,10 +104,10 @@ func RenameInline(c *ui.Context, name *string, editing *bool, opts RenameInlineO
*editing, st.active = false, false
slog.Debug("mujica: rename_inline end", "renamed", res.Renamed)
}
ui.Box(c).Key(st.gen).Grow(1).MinWidth(0).Children(func() {
ui.Box(c.Key(st.gen)).Grow(1).MinWidth(0).Children(func() {
in := input.TextInput(c, &st.draft, input.TextInputOptions{Label: core.Msg(c, "rename_inline.label"), Error: st.err})
in.Element.FillWidth()
in.Input.AutoFocus()
core.AutoFocus(c, in.Input)
if in.Changed() {
st.err = ""
renameTyped(st, *name, opts.Folder)
@@ -140,7 +140,7 @@ func renameCheck(c *ui.Context, name string, validate func(string) string) strin
}
// renameSelection marks the stem for replacement on initial input.
func renameSelection(c *ui.Context, in *ui.Element, st *renameState) {
func renameSelection(c *ui.Context, in ui.Element, st *renameState) {
k := core.Tokens(c)
stem := renameStem(st.draft, st.folder)
if st.stemSel && st.tail == "" {
@@ -149,8 +149,9 @@ func renameSelection(c *ui.Context, in *ui.Element, st *renameState) {
p.Fill(ui.Rect{X: r.X, Y: r.Y + 2, W: w, H: r.H - 4}, k.Selection, 2)
})
}
disabled := in.IsDisabled()
in.HandleInput(func(ev ui.InputEvent) bool {
if !st.stemSel || in.IsDisabled() {
if !st.stemSel || disabled {
return false
}
switch {
+2 -2
View File
@@ -43,7 +43,7 @@ type ShareDialogOptions struct {
// ShareDialogResult is what happened in the dialog this frame.
type ShareDialogResult struct {
Element *ui.Element
Element ui.Element
// Invited requests membership; Changed and Removed identify applied changes.
Invited *ShareInvite
Changed, Removed string
@@ -119,7 +119,7 @@ func shareMembers(c *ui.Context, members *[]ShareMember, res *ShareDialogResult,
if m.ID == "" {
panic("mujica: ShareMember needs an ID")
}
ui.Row(c).Key(m.ID).Gap(u * 2).AlignItems(ui.Center).MinHeight(core.Density(c).ControlHeight()).Children(func() {
ui.Row(c.Key(m.ID)).Gap(u * 2).AlignItems(ui.Center).MinHeight(core.Density(c).ControlHeight()).Children(func() {
display.Avatar(c, m.Name, display.AvatarOptions{Size: 28})
ui.Column(c).Grow(1).MinWidth(0).Children(func() {
ui.Text(c, m.Name).SingleLine()
+1 -1
View File
@@ -39,7 +39,7 @@ type storageUsageState struct {
}
// StorageUsage displays segmented capacity with keyboard readouts and details.
func StorageUsage(c *ui.Context, segments []StorageSegment, opts StorageUsageOptions) *ui.Element {
func StorageUsage(c *ui.Context, segments []StorageSegment, opts StorageUsageOptions) ui.Element {
if opts.Capacity <= 0 {
panic("mujica: StorageUsage needs a positive Capacity")
}
+1 -1
View File
@@ -54,7 +54,7 @@ type TransferQueueOptions struct {
// TransferQueueResult exposes the queue and requested item action.
type TransferQueueResult struct {
Element *ui.Element
Element ui.Element
Action TransferAction
// ID is the transfer the action is for.
ID string