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

+2 -2
View File
@@ -41,7 +41,7 @@ type AttendeeListOptions struct {
// AttendeeListResult is the list and the attendee the user removed.
type AttendeeListResult struct {
Element *ui.Element
Element ui.Element
removed int
}
@@ -73,7 +73,7 @@ func AttendeeList(c *ui.Context, attendees []Attendee, opts AttendeeListOptions)
ui.Text(c, core.Msg(c, "attendee_list.empty")).TextColor(k.TextMuted)
}
for i, a := range attendees {
ui.Row(c).Key(i).MinHeight(d.ControlHeight()).Gap(d.Unit() * 2).AlignItems(ui.Center).Role(ui.RoleListItem).Children(func() {
ui.Row(c.Key(i)).MinHeight(d.ControlHeight()).Gap(d.Unit() * 2).AlignItems(ui.Center).Role(ui.RoleListItem).Children(func() {
display.Avatar(c, a.Name, display.AvatarOptions{Size: 28})
ui.Column(c).Grow(1).Shrink(1).Children(func() {
name := a.Name
+1 -1
View File
@@ -223,7 +223,7 @@ func calendarDay(c *ui.Context, day, cur core.Date, cfg calendarConfig, size flo
if focus {
cell.Focus()
} else if cfg.autoFocus {
cell.AutoFocus()
core.AutoFocus(c, cell)
}
}
band := cfg.inRange != nil && cfg.inRange(day)
+2 -2
View File
@@ -57,7 +57,7 @@ type CalendarChange struct {
// CalendarViewResult is a view and what the user asked.
type CalendarViewResult struct {
Element *ui.Element
Element ui.Element
opened string
change *CalendarChange
day core.Date
@@ -112,7 +112,7 @@ type eventsCache[T any] struct {
}
// eventsDerived rebuilds build(hits) only when events or span change.
func eventsDerived[T any](e *ui.Element, key string, events []CalendarEvent, version int, from, to time.Time, build func(hits []int) T) T {
func eventsDerived[T any](e ui.Element, key string, events []CalendarEvent, version int, from, to time.Time, build func(hits []int) T) T {
cache := ui.Local(e, key, func() *eventsCache[T] { return &eventsCache[T]{} })
var first *CalendarEvent
if len(events) > 0 {
+1 -1
View File
@@ -204,7 +204,7 @@ func monthCell(c *ui.Context, day core.Date, month YearMonth, today core.Date, l
*more = day
}
open := *more == day
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))
_, wh := c.Size()
panel.Padding(d.Unit() * 2).Gap(d.Unit()).MinWidth(d.ControlHeight() * 6).MaxHeight(wh / 2)
+3 -3
View File
@@ -60,7 +60,7 @@ func timeGridSlot(name string, slot int) int {
}
// timeGrid draws day columns and turns gestures into changes.
func timeGrid(c *ui.Context, root *ui.Element, days []core.Date, events []CalendarEvent, cfg timeGridConfig, r *CalendarViewResult) {
func timeGrid(c *ui.Context, root ui.Element, days []core.Date, events []CalendarEvent, cfg timeGridConfig, r *CalendarViewResult) {
k := core.Tokens(c)
d := core.Density(c)
hourH := d.ControlHeight() * 2
@@ -266,7 +266,7 @@ func timeChip(c *ui.Context, i int, pl timePlaced, ev CalendarEvent, cfg timeGri
if mine && g.kind == CalendarResize {
growY = float32(snap(g.dy)) * hourH / 60
}
box := ui.Box(c).Key(ev.ID).Absolute().Top(from + shiftY).Height(max(to-from+growY, theme.MinHitSize)).
box := ui.Box(c.Key(ev.ID)).Absolute().Top(from + shiftY).Height(max(to-from+growY, theme.MinHitSize)).
LeftPercent(100 * float32(pl.col) / float32(pl.cols)).WidthPercent(100 / float32(pl.cols)).Padding(1)
box.Children(func() {
chip := EventChip(c, ev, EventChipOptions{Location: cfg.loc, Hour12: cfg.hour12, Selected: mine, Inline: to-from < hourH*0.75}).Fill()
@@ -291,7 +291,7 @@ func timeChip(c *ui.Context, i int, pl timePlaced, ev CalendarEvent, cfg timeGri
}
}
for _, part := range []struct {
e *ui.Element
e ui.Element
kind CalendarChangeKind
}{{chip, CalendarMove}, {handle, CalendarResize}} {
dx, dy, ok := part.e.Dragged()
+1 -1
View File
@@ -31,7 +31,7 @@ type CountdownOptions struct {
// CountdownResult is the countdown and its finish.
type CountdownResult struct {
Element *ui.Element
Element ui.Element
finished bool
}
+1 -1
View File
@@ -22,7 +22,7 @@ type CurrentTimeIndicatorOptions struct {
}
// CurrentTimeIndicator rules the current time across its parent.
func CurrentTimeIndicator(c *ui.Context, opts CurrentTimeIndicatorOptions) *ui.Element {
func CurrentTimeIndicator(c *ui.Context, opts CurrentTimeIndicatorOptions) ui.Element {
loc := eventLocation("CurrentTimeIndicator", opts.Location)
k := core.Tokens(c)
now := opts.Now
+1 -1
View File
@@ -106,7 +106,7 @@ func DatePicker(c *ui.Context, value *core.Date, opts DatePickerOptions) DateTim
if placeholder == "" {
placeholder = core.Msg(c, "date_picker.placeholder")
}
var field, inputEl *ui.Element
var field, inputEl ui.Element
col.Children(func() {
field = core.DatetimeField(c, ui.Row(c), *problem != "", opts.ReadOnly)
field.Children(func() {
+1 -1
View File
@@ -85,7 +85,7 @@ func DateRangePicker(c *ui.Context, value *DateRange, opts DateRangePickerOption
if !value.IsZero() {
text = value.Start.Format(layout) + " – " + value.End.Format(layout)
}
var field *ui.Element
var field ui.Element
col.Children(func() {
field = core.DatetimeField(c, ui.Row(c), *problem != "", opts.ReadOnly)
field.Children(func() {
+2 -2
View File
@@ -105,7 +105,7 @@ func datetimeGridCell(c *ui.Context, o, start, cursor int, cfg datetimeGridConfi
if focus {
cell.Focus()
} else if cfg.autoFocus {
cell.AutoFocus()
core.AutoFocus(c, cell)
}
}
fg := k.Text
@@ -172,7 +172,7 @@ type datetimeGridField struct {
}
// datetimeGridPicker draws f and its popup; reports a pick or clear.
func datetimeGridPicker(c *ui.Context, f datetimeGridField, cfg datetimeGridConfig) (col *ui.Element, picked int, ok, cleared bool) {
func datetimeGridPicker(c *ui.Context, f datetimeGridField, cfg datetimeGridConfig) (col ui.Element, picked int, ok, cleared bool) {
d := core.Density(c)
col = ui.Column(c).Width(d.ControlHeight() * 6).Gap(d.Unit()).AlignItems(ui.Stretch).Disabled(f.disabled)
open := ui.Local(col, "datetime-grid-open", func() bool { return false })
+3 -3
View File
@@ -6,9 +6,9 @@ import (
// DateTimeResult is a date or time component and what happened to it this frame.
type DateTimeResult struct {
Element *ui.Element
Element ui.Element
// Input is the text field inside, for labels and focus; nil without one.
Input *ui.Element
Input ui.Element
changed bool
canceled bool
}
@@ -20,6 +20,6 @@ func (r DateTimeResult) Changed() bool { return r.changed }
func (r DateTimeResult) Canceled() bool { return r.canceled }
// NewDateTimeResult reports a date or time control built from parts in another component.
func NewDateTimeResult(e, input *ui.Element, changed, canceled bool) DateTimeResult {
func NewDateTimeResult(e, input ui.Element, changed, canceled bool) DateTimeResult {
return DateTimeResult{e, input, changed, canceled}
}
+5 -3
View File
@@ -167,21 +167,23 @@ func TestTimePickerPopupPickClearsSegmentError(t *testing.T) {
func TestFormFocusesDatePickerInput(t *testing.T) {
var st input.FormState
day := core.Date{}
var field *ui.Element
focused := false
tt := ui.NewTester(func(c *ui.Context) {
core.Use(c, core.Settings{})
var field ui.Element
input.Form(c, &st, input.FormOptions{}, func(f *input.FormScope) {
input.FormItem(c, f, "debut", &day, input.FormItemOptions[core.Date]{Label: "Debut date", Required: true}, func() *ui.Element {
input.FormItem(c, f, "debut", &day, input.FormItemOptions[core.Date]{Label: "Debut date", Required: true}, func() ui.Element {
r := DatePicker(c, &day, DatePickerOptions{Today: calendarToday})
field = r.Input
return r.Input
})
})
focused = field.Valid() && field.Focused()
}, 600, 500)
if err := tt.Click("Submit"); err != nil {
t.Fatal(err)
}
if field == nil || !field.Focused() || !tt.Focused("Debut date") {
if !focused || !tt.Focused("Debut date") {
t.Fatal("a failed submit should focus the DatePicker's text box")
}
tt.Type("2026-10-12")
+1 -1
View File
@@ -22,7 +22,7 @@ type EventChipOptions struct {
}
// EventChip is a clickable, truncating event block.
func EventChip(c *ui.Context, ev CalendarEvent, opts EventChipOptions) *ui.Element {
func EventChip(c *ui.Context, ev CalendarEvent, opts EventChipOptions) ui.Element {
loc := eventLocation("EventChip", opts.Location)
k := core.Tokens(c)
d := core.Density(c)
+7 -7
View File
@@ -24,7 +24,7 @@ type EventEditorOptions struct {
// EventEditorResult is the editor and its save or cancel.
type EventEditorResult struct {
Element *ui.Element
Element ui.Element
saved bool
canceled bool
}
@@ -61,7 +61,7 @@ func EventEditor(c *ui.Context, ev *CalendarEvent, opts EventEditorOptions) Even
dr := &s.draft
problems := eventEditorProblems(c, *dr, s.tried)
root.Children(func() {
input.FormField(c, core.Msg(c, "event_editor.title"), input.FormFieldOptions{Required: true, Error: problems["title"]}, func() *ui.Element {
input.FormField(c, core.Msg(c, "event_editor.title"), input.FormFieldOptions{Required: true, Error: problems["title"]}, func() ui.Element {
return input.TextInput(c, &dr.Title, input.TextInputOptions{Label: core.Msg(c, "event_editor.title"), Disabled: opts.Disabled, Error: problems["title"]}).Input
})
input.Checkbox(c, &dr.AllDay, core.Msg(c, "event_editor.all_day"), input.CheckboxOptions{Disabled: opts.Disabled})
@@ -70,7 +70,7 @@ func EventEditor(c *ui.Context, ev *CalendarEvent, opts EventEditorOptions) Even
key string
t *time.Time
}{{"event_editor.starts", &dr.Start}, {"event_editor.ends", &dr.End}} {
input.FormField(c, core.Msg(c, f.key), input.FormFieldOptions{Error: problems[f.key]}, func() *ui.Element {
input.FormField(c, core.Msg(c, f.key), input.FormFieldOptions{Error: problems[f.key]}, func() ui.Element {
if dr.AllDay {
day := core.DateOf(f.t.In(loc))
r := DatePicker(c, &day, DatePickerOptions{Label: core.Msg(c, f.key), WeekStart: opts.WeekStart, Disabled: opts.Disabled})
@@ -84,10 +84,10 @@ func EventEditor(c *ui.Context, ev *CalendarEvent, opts EventEditorOptions) Even
})
}
})
input.FormField(c, core.Msg(c, "event_editor.where"), input.FormFieldOptions{}, func() *ui.Element {
input.FormField(c, core.Msg(c, "event_editor.where"), input.FormFieldOptions{}, func() ui.Element {
return input.TextInput(c, &dr.Location, input.TextInputOptions{Label: core.Msg(c, "event_editor.where"), Disabled: opts.Disabled}).Input
})
input.FormField(c, core.Msg(c, "event_editor.guests"), input.FormFieldOptions{Description: core.Msg(c, "event_editor.guests_help")}, func() *ui.Element {
input.FormField(c, core.Msg(c, "event_editor.guests"), input.FormFieldOptions{Description: core.Msg(c, "event_editor.guests_help")}, func() ui.Element {
names := make([]string, len(dr.Attendees))
for i, a := range dr.Attendees {
names[i] = a.Name
@@ -101,11 +101,11 @@ func EventEditor(c *ui.Context, ev *CalendarEvent, opts EventEditorOptions) Even
if len(dr.Attendees) > 0 {
AttendeeList(c, dr.Attendees, AttendeeListOptions{Disabled: opts.Disabled})
}
input.FormField(c, core.Msg(c, "event_editor.reminders"), input.FormFieldOptions{}, func() *ui.Element {
input.FormField(c, core.Msg(c, "event_editor.reminders"), input.FormFieldOptions{}, func() ui.Element {
return ReminderPicker(c, &dr.Reminders, ReminderPickerOptions{Disabled: opts.Disabled}).Element
})
var rec RecurrenceResult
input.FormField(c, core.Msg(c, "recurrence_editor.repeat"), input.FormFieldOptions{}, func() *ui.Element {
input.FormField(c, core.Msg(c, "recurrence_editor.repeat"), input.FormFieldOptions{}, func() ui.Element {
rec = RecurrenceEditor(c, &dr.Repeat, RecurrenceEditorOptions{Start: core.DateOf(dr.Start.In(loc)), WeekStart: opts.WeekStart, Disabled: opts.Disabled})
return rec.Element
})
+3 -3
View File
@@ -26,7 +26,7 @@ type EventPopoverOptions struct {
// EventPopoverResult is the popover and the action the user chose.
type EventPopoverResult struct {
Element *ui.Element
Element ui.Element
edit bool
del bool
}
@@ -38,7 +38,7 @@ func (r EventPopoverResult) Edit() bool { return r.edit }
func (r EventPopoverResult) Delete() bool { return r.del }
// EventPopover summarizes an event beside anchor while *open.
func EventPopover(c *ui.Context, anchor *ui.Element, open *bool, ev CalendarEvent, opts EventPopoverOptions) EventPopoverResult {
func EventPopover(c *ui.Context, anchor ui.Element, open *bool, ev CalendarEvent, opts EventPopoverOptions) EventPopoverResult {
loc := eventLocation("EventPopover", opts.Location)
k := core.Tokens(c)
d := core.Density(c)
@@ -47,7 +47,7 @@ func EventPopover(c *ui.Context, anchor *ui.Element, open *bool, ev CalendarEven
slog.Debug("mujica: event_popover close", "disabled", true)
*open = false
}
r.Element = ui.PopoverBase(c, anchor, open, func(panel *ui.Element) {
r.Element = ui.PopoverBase(c, anchor, open, func(panel ui.Element) {
core.OverlayStyle(c, panel, core.OverlayEnter(c, panel))
ww, wh := c.Size()
panel.Margin(d.Unit(), 0, 0, 0).Padding(d.Unit() * 4).Gap(d.Unit() * 2).Width(min(d.ControlHeight()*9, ww-16)).MaxHeight(wh - 16).
+1 -1
View File
@@ -55,7 +55,7 @@ type RecurrenceEditorOptions struct {
// RecurrenceResult is the editor, its change and its problem.
type RecurrenceResult struct {
Element *ui.Element
Element ui.Element
changed bool
problem string
}
+1 -1
View File
@@ -19,7 +19,7 @@ type RelativeTimeOptions struct {
}
// RelativeTime shows t as "3 minutes ago", refreshing while shown.
func RelativeTime(c *ui.Context, t time.Time, opts RelativeTimeOptions) *ui.Element {
func RelativeTime(c *ui.Context, t time.Time, opts RelativeTimeOptions) ui.Element {
loc := eventLocation("RelativeTime", opts.Location)
now := opts.Now
if now.IsZero() {
+2 -2
View File
@@ -192,7 +192,7 @@ func timeParts(cfg timeConfig) []timePart {
}
// timeEditor draws segments and popup; reports edits and invalid entry.
func timeEditor(c *ui.Context, draft *TimeOfDay, cfg timeConfig) (field *ui.Element, edited bool, bad string) {
func timeEditor(c *ui.Context, draft *TimeOfDay, cfg timeConfig) (field ui.Element, edited bool, bad string) {
k := core.Tokens(c)
d := core.Density(c)
field = core.DatetimeField(c, ui.Row(c), cfg.problem != "", cfg.readOnly).Gap(d.Unit() * 2)
@@ -268,7 +268,7 @@ func timeLimits(part timePart, cfg timeConfig) (lo, hi int) {
}
// timeSegment draws one segment; done reports a complete valid entry, bad an invalid one.
func timeSegment(c *ui.Context, draft *TimeOfDay, part timePart, cfg timeConfig, bad *timeBad, edited *bool) (seg *ui.Element, done bool) {
func timeSegment(c *ui.Context, draft *TimeOfDay, part timePart, cfg timeConfig, bad *timeBad, edited *bool) (seg ui.Element, done bool) {
k := core.Tokens(c)
d := core.Density(c)
name := core.Msg(c, timePartNames[part])
+1 -1
View File
@@ -114,7 +114,7 @@ func TimezoneSelect(c *ui.Context, zone *string, opts TimezoneSelectOptions) cor
core.DatetimePopup(c, field, open, false, func() {
list := timezoneFilter(zones, common, *query, now)
in := input.TextInput(c, query, input.TextInputOptions{Placeholder: core.Msg(c, "timezone_select.search"), Label: core.Msg(c, "timezone_select.search")})
in.Input.AutoFocus()
core.AutoFocus(c, in.Input)
if in.Changed() {
*hi = 0
}