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

+4 -4
View File
@@ -74,7 +74,7 @@ func AssigneePicker(c *ui.Context, ids *[]string, members []ProjectMember, opts
st.shown = nil
}
before := *ids
var trigger *ui.Element
var trigger ui.Element
row.Children(func() {
if len(*ids) > 0 {
items := make([]display.AvatarItem, min(3, len(*ids)))
@@ -100,7 +100,7 @@ func AssigneePicker(c *ui.Context, ids *[]string, members []ProjectMember, opts
}
input.MultiSelectTags(c, ids, options, input.MultiSelectOptions{MaxTags: 2, Disabled: trigger.IsDisabled(), Placeholder: core.Msg(c, "assignee_picker.placeholder")})
})
ui.PopoverBase(c, trigger, &st.open, func(panel *ui.Element) {
ui.PopoverBase(c, trigger, &st.open, func(panel ui.Element) {
input.SelectPanel(c, panel)
ww, wh := c.Size()
panel.MinWidth(min(ww-8, trigger.Bounds().W)).MaxWidth(max(1, ww-8)).MaxHeight(max(1, wh-8))
@@ -147,7 +147,7 @@ func AssigneePicker(c *ui.Context, ids *[]string, members []ProjectMember, opts
} else if st.count > 0 {
mark = internal.Mixed
}
var all *ui.Element
var all ui.Element
ui.Row(c).MinHeight(d.ControlHeight()).Children(func() {
all = input.MultiSelectCheck(c, k, &on, mark, core.Msg(c, "multi_select.all"), full && st.count == 0)
})
@@ -176,7 +176,7 @@ func AssigneePicker(c *ui.Context, ids *[]string, members []ProjectMember, opts
if selected {
state = internal.Checked
}
var cb *ui.Element
var cb ui.Element
ui.Row(c).MinHeight(d.ControlHeight()).Children(func() { cb = input.MultiSelectCheck(c, k, &selected, state, o.Label, full && !selected) })
if st.focus == o.Value && !cb.IsDisabled() {
cb.Focus()
+1 -1
View File
@@ -23,7 +23,7 @@ type IssueCardOptions struct {
}
// IssueCard displays task fields in a selectable card.
func IssueCard(c *ui.Context, task Task, opts IssueCardOptions) *ui.Element {
func IssueCard(c *ui.Context, task Task, opts IssueCardOptions) ui.Element {
projectCheckPriority(task.Priority)
st := projectStatus(opts.Statuses, task.Status)
k := core.Tokens(c)
+1 -2
View File
@@ -54,8 +54,7 @@ func TestIssueIdBadgeCopiedFades(t *testing.T) {
if !tt.HasText("Copied") {
t.Fatal("no Copied note")
}
time.Sleep(issueCopiedFor + 50*time.Millisecond)
tt.Frame()
tt.Advance(issueCopiedFor + 50*time.Millisecond)
if tt.HasText("Copied") {
t.Fatal("Copied should go away")
}
+2 -2
View File
@@ -39,7 +39,7 @@ type KanbanMove struct {
// KanbanResult is the board and what the user did.
type KanbanResult struct {
Element *ui.Element
Element ui.Element
move *KanbanMove
opened string
}
@@ -183,7 +183,7 @@ func kanbanColumn(c *ui.Context, st *kanbanState, ci int, col KanbanColumn, cols
status := projectStatus(opts.Statuses, col.Status)
over := col.WIP > 0 && len(idx) > col.WIP
full := col.WIP > 0 && len(idx) >= col.WIP
box := ui.Column(c).Key(col.Status).Width(d.ControlHeight()*7.5).Shrink(0).Gap(d.Unit()*2).Padding(d.Unit()*2).
box := ui.Column(c.Key(col.Status)).Width(d.ControlHeight()*7.5).Shrink(0).Gap(d.Unit()*2).Padding(d.Unit()*2).
Radius(theme.CardRadius).Background(k.Background).Border(theme.BorderWidth, k.Border).Role(ui.RoleGroup).Label(status.Name)
if id, ok := ui.Drop[kanbanDrag](box); ok && !box.IsDisabled() {
move(string(id), col.Status, "", "drag")
+2 -2
View File
@@ -166,8 +166,8 @@ func TestKanbanBoardForeignDrop(t *testing.T) {
tt := ui.NewTester(func(c *ui.Context) {
core.Use(c, core.Settings{})
ui.Row(c).Height(400).Children(func() {
ui.Box(c).Key("left").Width(300).Height(400).Children(func() { KanbanBoard(c, &left, opts) })
ui.Box(c).Key("right").Width(300).Height(400).Children(func() { KanbanBoard(c, &right, opts) })
ui.Box(c.Key("left")).Width(300).Height(400).Children(func() { KanbanBoard(c, &left, opts) })
ui.Box(c.Key("right")).Width(300).Height(400).Children(func() { KanbanBoard(c, &right, opts) })
})
}, 640, 420)
src, _ := tt.Find("A-1 First")
+2 -2
View File
@@ -81,7 +81,7 @@ func LabelManager(c *ui.Context, labels *[]TaskLabel, selected *[]string, opts L
removed := -1
ui.List(c, state, len(*labels), func(i int) {
l := &(*labels)[i]
ui.Row(c).Key(l.ID).MinHeight(d.ControlHeight()).Gap(d.Unit() * 2).AlignItems(ui.Center).Children(func() {
ui.Row(c.Key(l.ID)).MinHeight(d.ControlHeight()).Gap(d.Unit() * 2).AlignItems(ui.Center).Children(func() {
on := cache.selected[l.ID]
box := ui.CheckboxBase(c, &on).Size(theme.MinHitSize, theme.MinHitSize).Center().Label(l.Name)
internal.Ring(box, k.Focus, theme.ControlRadius)
@@ -146,7 +146,7 @@ func labelEditRow(c *ui.Context, s *labelManagerState, l *TaskLabel, taken func(
d := core.Density(c)
ui.Column(c).Grow(1).Gap(d.Unit()).Children(func() {
in := input.TextInput(c, &s.name, input.TextInputOptions{Label: core.Msg(c, "label_manager.name"), Error: s.problem})
in.Input.AutoFocus()
core.AutoFocus(c, in.Input)
ui.Row(c).Gap(d.Unit()).Role(ui.RoleRadioGroup).Label(core.Msg(c, "label_manager.color")).Children(func() {
for i := range 6 {
col := datetime.EventColor(i)
+1 -1
View File
@@ -25,7 +25,7 @@ type MilestoneProgressOptions struct {
}
// MilestoneProgress shows completion counts, progress and due dates.
func MilestoneProgress(c *ui.Context, m Milestone, opts MilestoneProgressOptions) *ui.Element {
func MilestoneProgress(c *ui.Context, m Milestone, opts MilestoneProgressOptions) ui.Element {
core.DatetimeCheckDate("MilestoneProgress Due", m.Due)
if m.Total < 0 || m.Done < 0 || m.Done > m.Total {
panic(fmt.Sprintf("mujica: MilestoneProgress %d of %d is not valid", m.Done, m.Total))
+1 -1
View File
@@ -64,7 +64,7 @@ type PomodoroTimerOptions struct {
// PomodoroResult is the timer and what changed this frame.
type PomodoroResult struct {
Element *ui.Element
Element ui.Element
changed bool
finished PomodoroPhase
done bool
+1 -1
View File
@@ -23,7 +23,7 @@ func PriorityName(c *ui.Context, p Priority) string {
}
// PriorityIndicator displays urgency with shapes and localized text.
func PriorityIndicator(c *ui.Context, p Priority, opts PriorityIndicatorOptions) *ui.Element {
func PriorityIndicator(c *ui.Context, p Priority, opts PriorityIndicatorOptions) ui.Element {
name := PriorityName(c, p)
k := core.Tokens(c)
d := core.Density(c)
+2 -2
View File
@@ -75,7 +75,7 @@ type WorkflowStatus struct {
// ProjectResult exposes the element and task events.
type ProjectResult struct {
Element *ui.Element
Element ui.Element
changed string
opened string
}
@@ -139,7 +139,7 @@ func projectDueText(c *ui.Context, due, today core.Date, done bool) (string, boo
}
// projectBar draws a named completion bar.
func projectBar(c *ui.Context, frac float64, label string) *ui.Element {
func projectBar(c *ui.Context, frac float64, label string) ui.Element {
k := core.Tokens(c)
return ui.Box(c).Height(6).FillWidth().Role(ui.RoleProgress).Label(label).Description(fmt.Sprintf("%d%%", int(frac*100))).
Draw(func(p *ui.Painter, r ui.Rect) {
+3 -3
View File
@@ -41,7 +41,7 @@ func StatusSelect(c *ui.Context, value *string, statuses []WorkflowStatus, opts
k := core.Tokens(c)
old := *value
pick := old
var s *ui.SelectParts[string]
var s ui.SelectParts[string]
root := ui.Row(c).Shrink(0).Disabled(opts.Disabled)
epoch := ui.Local(root, "status-epoch", func() struct {
disabled bool
@@ -57,7 +57,7 @@ func StatusSelect(c *ui.Context, value *string, statuses []WorkflowStatus, opts
epoch.generation++
}
root.Children(func() {
ui.Row(c).Key(epoch.generation).Children(func() { s = ui.SelectBase(c, &pick) })
ui.Row(c.Key(epoch.generation)).Children(func() { s = ui.SelectBase(c, &pick) })
})
tr := s.Trigger
label := opts.Label
@@ -70,7 +70,7 @@ func StatusSelect(c *ui.Context, value *string, statuses []WorkflowStatus, opts
ui.Text(c, cur.Name).SingleLine().Grow(1).Shrink(1).TextColor(k.Text)
})
if !tr.IsDisabled() {
s.Popup(func(panel *ui.Element) {
s.Popup(func(panel ui.Element) {
input.SelectPanel(c, panel)
_, wh := c.Size()
ui.Scroll(c).MaxHeight(min(320, wh/2)).Children(func() {
+1 -1
View File
@@ -88,7 +88,7 @@ func SubtaskList(c *ui.Context, subs *[]Subtask, opts SubtaskListOptions) core.C
removed := -1
ui.List(c, state, len(*subs), func(i int) {
s := &(*subs)[i]
row := ui.Row(c).Key(s.ID).MinHeight(d.ControlHeight()).Gap(d.Unit() * 2).AlignItems(ui.Center).Role(ui.RoleListItem)
row := ui.Row(c.Key(s.ID)).MinHeight(d.ControlHeight()).Gap(d.Unit() * 2).AlignItems(ui.Center).Role(ui.RoleListItem)
if id, ok := ui.Drop[subtaskDrag](row); ok {
if from := slices.IndexFunc(*subs, func(o Subtask) bool { return o.ID == string(id) }); from >= 0 {
move(from, i, "drag")
+1 -1
View File
@@ -29,7 +29,7 @@ type TaskDetailPanelOptions struct {
// TaskDetailResult exposes the panel, edits and close events.
type TaskDetailResult struct {
Element *ui.Element
Element ui.Element
changed bool
closed bool
}
+1 -1
View File
@@ -53,7 +53,7 @@ func TaskItem(c *ui.Context, task *Task, opts TaskItemOptions) ProjectResult {
if edit.on && !row.IsDisabled() {
in := input.TextInput(c, &edit.draft, input.TextInputOptions{Label: core.Msg(c, "task_item.rename")})
in.Element.Grow(1)
in.Input.AutoFocus()
core.AutoFocus(c, in.Input)
switch {
case in.Submitted() && strings.TrimSpace(edit.draft) != "":
if t := strings.TrimSpace(edit.draft); t != task.Title {
+1 -1
View File
@@ -30,7 +30,7 @@ type WorkloadViewOptions struct {
}
// WorkloadView virtualizes member loads with capacity and overload marks.
func WorkloadView(c *ui.Context, loads []Workload, opts WorkloadViewOptions) *ui.Element {
func WorkloadView(c *ui.Context, loads []Workload, opts WorkloadViewOptions) ui.Element {
k := core.Tokens(c)
d := core.Density(c)
unit := opts.Unit