diff --git a/pkg/mcp/mcp_test.go b/pkg/mcp/mcp_test.go index a06c280..48aa538 100644 --- a/pkg/mcp/mcp_test.go +++ b/pkg/mcp/mcp_test.go @@ -7,6 +7,7 @@ import ( "forge.lthn.ai/core/go/pkg/core" "forge.lthn.ai/core/gui/pkg/clipboard" + "forge.lthn.ai/core/gui/pkg/window" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -53,6 +54,15 @@ func TestMCP_Good_ClipboardRoundTrip(t *testing.T) { assert.Equal(t, "hello", content.Text) } +func TestMCP_Bad_WindowCreateRequiresName(t *testing.T) { + c, _ := core.New(core.WithServiceLock()) + sub := NewService(c) + + _, _, err := sub.windowCreate(context.Background(), nil, window.Window{}) + require.Error(t, err) + assert.Contains(t, err.Error(), "window name is required") +} + func TestMCP_Bad_NoServices(t *testing.T) { c, _ := core.New(core.WithServiceLock()) // Without any services, QUERY should return handled=false diff --git a/pkg/mcp/tools_window.go b/pkg/mcp/tools_window.go index 1d44ae0..88313c8 100644 --- a/pkg/mcp/tools_window.go +++ b/pkg/mcp/tools_window.go @@ -80,6 +80,9 @@ type WindowCreateOutput struct { } func (s *Subsystem) windowCreate(_ context.Context, _ *mcp.CallToolRequest, input window.Window) (*mcp.CallToolResult, WindowCreateOutput, error) { + if input.Name == "" { + return nil, WindowCreateOutput{}, coreerr.E("mcp.windowCreate", "window name is required", nil) + } result, _, err := s.core.PERFORM(window.TaskOpenWindow{ Window: input, }) @@ -363,7 +366,7 @@ func (s *Subsystem) registerWindowTools(server *mcp.Server) { mcp.AddTool(server, &mcp.Tool{Name: "window_list", Description: "List all application windows"}, s.windowList) mcp.AddTool(server, &mcp.Tool{Name: "window_get", Description: "Get information about a specific window"}, s.windowGet) mcp.AddTool(server, &mcp.Tool{Name: "window_focused", Description: "Get the currently focused window"}, s.windowFocused) - mcp.AddTool(server, &mcp.Tool{Name: "window_create", Description: "Create a new application window"}, s.windowCreate) + mcp.AddTool(server, &mcp.Tool{Name: "window_create", Description: "Create a new named application window"}, s.windowCreate) mcp.AddTool(server, &mcp.Tool{Name: "window_close", Description: "Close an application window"}, s.windowClose) mcp.AddTool(server, &mcp.Tool{Name: "window_position", Description: "Set the position of a window"}, s.windowPosition) mcp.AddTool(server, &mcp.Tool{Name: "window_size", Description: "Set the size of a window"}, s.windowSize) diff --git a/pkg/window/window.go b/pkg/window/window.go index c09e4f2..71455c9 100644 --- a/pkg/window/window.go +++ b/pkg/window/window.go @@ -86,6 +86,7 @@ func (m *Manager) SetDefaultHeight(height int) { // Create opens a window from a declarative spec. // Example: m.Create(Window{Name: "editor", Title: "Editor", URL: "/"}) +// Saved position, size, and maximized state are restored when available. func (m *Manager) Create(spec Window) (PlatformWindow, error) { w := spec if w.Name == "" { @@ -112,10 +113,17 @@ func (m *Manager) Create(spec Window) (PlatformWindow, error) { w.URL = "/" } - // Apply saved state if available - m.state.ApplyState(&w) + // Apply saved state if available. + if m.state != nil { + m.state.ApplyState(&w) + } pw := m.platform.CreateWindow(w.ToPlatformOptions()) + if m.state != nil { + if state, ok := m.state.GetState(w.Name); ok && state.Maximized { + pw.Maximise() + } + } m.mu.Lock() m.windows[w.Name] = pw diff --git a/pkg/window/window_test.go b/pkg/window/window_test.go index 28431cb..ed241c7 100644 --- a/pkg/window/window_test.go +++ b/pkg/window/window_test.go @@ -77,6 +77,15 @@ func TestManager_Create_CustomDefaults_Good(t *testing.T) { assert.Equal(t, 900, h) } +func TestManager_Create_RestoresMaximizedState_Good(t *testing.T) { + m, _ := newTestManager() + m.state.states["restored"] = WindowState{Maximized: true} + + pw, err := m.Create(Window{Name: "restored"}) + require.NoError(t, err) + assert.True(t, pw.IsMaximised()) +} + func TestManager_Get_Good(t *testing.T) { m, _ := newTestManager() _, _ = m.Create(Window{Name: "findme"})