From 5ce1d67f70d138d2aad44e38d6a83f387d0f4dfc Mon Sep 17 00:00:00 2001 From: Virgil Date: Tue, 31 Mar 2026 08:23:59 +0000 Subject: [PATCH] refactor(ax): add atomic window bounds task --- pkg/display/display.go | 7 +++---- pkg/display/display_test.go | 15 +++++++++++++++ pkg/mcp/tools_window.go | 8 +++----- pkg/window/messages.go | 6 ++++++ pkg/window/persistence_test.go | 14 ++++++++++++++ pkg/window/service.go | 13 +++++++++++++ pkg/window/service_test.go | 30 ++++++++++++++++++++++++++++++ pkg/window/state.go | 14 ++++++++++++++ 8 files changed, 98 insertions(+), 9 deletions(-) diff --git a/pkg/display/display.go b/pkg/display/display.go index 292141b..659ef19 100644 --- a/pkg/display/display.go +++ b/pkg/display/display.go @@ -632,10 +632,9 @@ func (s *Service) SetWindowSize(name string, width, height int) error { // SetWindowBounds sets both position and size of a window via IPC. func (s *Service) SetWindowBounds(name string, x, y, width, height int) error { - if _, err := s.performWindowTask("display.SetWindowBounds", window.TaskSetPosition{Name: name, X: x, Y: y}); err != nil { - return err - } - _, err := s.performWindowTask("display.SetWindowBounds", window.TaskSetSize{Name: name, Width: width, Height: height}) + _, err := s.performWindowTask("display.SetWindowBounds", window.TaskSetBounds{ + Name: name, X: x, Y: y, Width: width, Height: height, + }) return err } diff --git a/pkg/display/display_test.go b/pkg/display/display_test.go index d3fb2d6..00d14fe 100644 --- a/pkg/display/display_test.go +++ b/pkg/display/display_test.go @@ -252,6 +252,21 @@ func TestSetWindowSize_Good(t *testing.T) { assert.Equal(t, 768, info.Height) } +func TestSetWindowBounds_Good(t *testing.T) { + c := newTestConclave(t) + svc := core.MustServiceFor[*Service](c, "display") + _ = svc.OpenWindow(window.Window{Name: "bounds-win"}) + + err := svc.SetWindowBounds("bounds-win", 100, 200, 1024, 768) + assert.NoError(t, err) + + info, _ := svc.GetWindowInfo("bounds-win") + assert.Equal(t, 100, info.X) + assert.Equal(t, 200, info.Y) + assert.Equal(t, 1024, info.Width) + assert.Equal(t, 768, info.Height) +} + func TestMaximizeWindow_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") diff --git a/pkg/mcp/tools_window.go b/pkg/mcp/tools_window.go index 88313c8..0b53af0 100644 --- a/pkg/mcp/tools_window.go +++ b/pkg/mcp/tools_window.go @@ -165,11 +165,9 @@ type WindowBoundsOutput struct { } func (s *Subsystem) windowBounds(_ context.Context, _ *mcp.CallToolRequest, input WindowBoundsInput) (*mcp.CallToolResult, WindowBoundsOutput, error) { - _, _, err := s.core.PERFORM(window.TaskSetPosition{Name: input.Name, X: input.X, Y: input.Y}) - if err != nil { - return nil, WindowBoundsOutput{}, err - } - _, _, err = s.core.PERFORM(window.TaskSetSize{Name: input.Name, Width: input.Width, Height: input.Height}) + _, _, err := s.core.PERFORM(window.TaskSetBounds{ + Name: input.Name, X: input.X, Y: input.Y, Width: input.Width, Height: input.Height, + }) if err != nil { return nil, WindowBoundsOutput{}, err } diff --git a/pkg/window/messages.go b/pkg/window/messages.go index 3efe0c2..cc3a094 100644 --- a/pkg/window/messages.go +++ b/pkg/window/messages.go @@ -31,6 +31,12 @@ type TaskSetPosition struct { X, Y int } +type TaskSetBounds struct { + Name string + X, Y int + Width, Height int +} + type TaskSetSize struct { Name string Width, Height int diff --git a/pkg/window/persistence_test.go b/pkg/window/persistence_test.go index ba7eaee..abdde65 100644 --- a/pkg/window/persistence_test.go +++ b/pkg/window/persistence_test.go @@ -63,6 +63,20 @@ func TestStateManager_UpdateSize_Good(t *testing.T) { assert.Equal(t, 200, got.Y) } +func TestStateManager_UpdateBounds_Good(t *testing.T) { + sm := NewStateManagerWithDir(t.TempDir()) + sm.SetState("win", WindowState{X: 100, Y: 200, Width: 800, Height: 600}) + + sm.UpdateBounds("win", 300, 400, 1280, 720) + + got, ok := sm.GetState("win") + require.True(t, ok) + assert.Equal(t, 300, got.X) + assert.Equal(t, 400, got.Y) + assert.Equal(t, 1280, got.Width) + assert.Equal(t, 720, got.Height) +} + func TestStateManager_UpdateMaximized_Good(t *testing.T) { sm := NewStateManagerWithDir(t.TempDir()) sm.SetState("win", WindowState{Width: 800, Height: 600, Maximized: false}) diff --git a/pkg/window/service.go b/pkg/window/service.go index a72d18f..6e83f27 100644 --- a/pkg/window/service.go +++ b/pkg/window/service.go @@ -134,6 +134,8 @@ func (s *Service) handleTask(c *core.Core, t core.Task) (any, bool, error) { return nil, true, s.taskCloseWindow(t.Name) case TaskSetPosition: return nil, true, s.taskSetPosition(t.Name, t.X, t.Y) + case TaskSetBounds: + return nil, true, s.taskSetBounds(t.Name, t.X, t.Y, t.Width, t.Height) case TaskSetSize: return nil, true, s.taskSetSize(t.Name, t.Width, t.Height) case TaskMaximize: @@ -282,6 +284,17 @@ func (s *Service) taskSetPosition(name string, x, y int) error { return nil } +func (s *Service) taskSetBounds(name string, x, y, width, height int) error { + platformWindow, err := s.requireWindow(name, "window.Service.taskSetBounds") + if err != nil { + return err + } + platformWindow.SetPosition(x, y) + platformWindow.SetSize(width, height) + s.manager.State().UpdateBounds(name, x, y, width, height) + return nil +} + func (s *Service) taskSetSize(name string, width, height int) error { platformWindow, err := s.requireWindow(name, "window.Service.taskSetSize") if err != nil { diff --git a/pkg/window/service_test.go b/pkg/window/service_test.go index b36b530..3a50781 100644 --- a/pkg/window/service_test.go +++ b/pkg/window/service_test.go @@ -177,6 +177,36 @@ func TestTaskSetSize_Good(t *testing.T) { assert.Equal(t, 600, info.Height) } +func TestTaskSetBounds_Good(t *testing.T) { + _, c := newTestWindowService(t) + _, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}}) + + _, handled, err := c.PERFORM(TaskSetBounds{ + Name: "test", + X: 100, + Y: 200, + Width: 800, + Height: 600, + }) + require.NoError(t, err) + assert.True(t, handled) + + result, _, _ := c.QUERY(QueryWindowByName{Name: "test"}) + info := result.(*WindowInfo) + assert.Equal(t, 100, info.X) + assert.Equal(t, 200, info.Y) + assert.Equal(t, 800, info.Width) + assert.Equal(t, 600, info.Height) + + states, _, _ := c.QUERY(QuerySavedWindowStates{}) + saved := states.(map[string]WindowState) + require.Contains(t, saved, "test") + assert.Equal(t, 100, saved["test"].X) + assert.Equal(t, 200, saved["test"].Y) + assert.Equal(t, 800, saved["test"].Width) + assert.Equal(t, 600, saved["test"].Height) +} + func TestTaskMaximize_Good(t *testing.T) { _, c := newTestWindowService(t) _, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}}) diff --git a/pkg/window/state.go b/pkg/window/state.go index f300456..ef36cb2 100644 --- a/pkg/window/state.go +++ b/pkg/window/state.go @@ -164,6 +164,20 @@ func (sm *StateManager) UpdateSize(name string, width, height int) { sm.scheduleSave() } +// UpdateBounds updates position and size in one state write. +func (sm *StateManager) UpdateBounds(name string, x, y, width, height int) { + sm.mu.Lock() + s := sm.states[name] + s.X = x + s.Y = y + s.Width = width + s.Height = height + s.UpdatedAt = time.Now().UnixMilli() + sm.states[name] = s + sm.mu.Unlock() + sm.scheduleSave() +} + // UpdateMaximized updates the maximized flag. func (sm *StateManager) UpdateMaximized(name string, maximized bool) { sm.mu.Lock()