diff --git a/pkg/display/FEATURES.md b/pkg/display/FEATURES.md index f336a61..a19dab5 100644 --- a/pkg/display/FEATURES.md +++ b/pkg/display/FEATURES.md @@ -156,7 +156,6 @@ This document tracks the implementation of display server features that enable A ### Focus Management - [x] `window_focused` - Get currently focused window -- [x] `focus_set` - Set focus to specific window (alias for window_focus) ### Event Subscriptions (WebSocket) - [x] `event_subscribe` - Subscribe to events (via WebSocket /events endpoint) @@ -215,7 +214,7 @@ This document tracks the implementation of display server features that enable A - [x] WebSocket event subscriptions (/events endpoint) - [x] Real-time window tracking (focus, blur, move, resize, close, create) - [x] Theme change events -- [x] focus_set, screen_get, screen_primary, screen_at_point, screen_for_window +- [x] screen_get, screen_primary, screen_at_point, screen_for_window ### Phase 7 - Advanced Features (DONE) - [x] `window_background_colour` - Window transparency via RGBA alpha diff --git a/pkg/display/README.md b/pkg/display/README.md index 740e5ef..570b2a0 100644 --- a/pkg/display/README.md +++ b/pkg/display/README.md @@ -21,10 +21,8 @@ ## Declarative Windows -Windows are created from a `window.Window` spec instead of a fluent option chain: +Windows are created from a `window.Window` spec instead of a fluent option chain. -```go -svc.OpenWindow(window.Window{Name: "editor", Title: "Editor", URL: "/#/editor"}) -``` +Use `OpenWindow(window.Window{})` for the default app window, or `CreateWindow(window.Window{Name: "editor", Title: "Editor", URL: "/#/editor"})` when you need a named window and the returned `WindowInfo`. -The same spec is used by `CreateWindow`, layout restore, tiling, snapping, and workflow presets. +The same spec shape is used by layout restore, tiling, snapping, and workflow presets. diff --git a/pkg/display/display.go b/pkg/display/display.go index 6703f33..2124571 100644 --- a/pkg/display/display.go +++ b/pkg/display/display.go @@ -588,7 +588,8 @@ func (s *Service) performWindowTask(operation string, task core.Task) (any, erro // --- Window Management (delegates via IPC) --- -// OpenWindow creates a new window from a declarative Window spec. +// OpenWindow opens a window using manager defaults. +// Example: s.OpenWindow(window.Window{}) func (s *Service) OpenWindow(spec window.Window) error { _, err := s.performWindowTask("display.OpenWindow", window.TaskOpenWindow{Window: spec}) return err @@ -746,7 +747,8 @@ func (s *Service) GetSavedWindowStates() map[string]window.WindowState { return out } -// CreateWindow creates a new named window from a declarative Window spec. +// CreateWindow opens a named window and returns its info. +// Example: s.CreateWindow(window.Window{Name: "editor", Title: "Editor", URL: "/#/editor"}) func (s *Service) CreateWindow(spec window.Window) (*window.WindowInfo, error) { if spec.Name == "" { return nil, coreerr.E("display.CreateWindow", "window name is required", nil) @@ -877,14 +879,12 @@ func ptr[T any](v T) *T { return &v } // --- Menu handler methods --- func (s *Service) handleNewWorkspace() { - _, _, _ = s.Core().PERFORM(window.TaskOpenWindow{ - Window: window.Window{ - Name: "workspace-new", - Title: "New Workspace", - URL: "/workspace/new", - Width: 500, - Height: 400, - }, + _, _ = s.CreateWindow(window.Window{ + Name: "workspace-new", + Title: "New Workspace", + URL: "/workspace/new", + Width: 500, + Height: 400, }) } @@ -901,14 +901,12 @@ func (s *Service) handleListWorkspaces() { } func (s *Service) handleNewFile() { - _, _, _ = s.Core().PERFORM(window.TaskOpenWindow{ - Window: window.Window{ - Name: "editor", - Title: "New File - Editor", - URL: "/#/developer/editor?new=true", - Width: 1200, - Height: 800, - }, + _, _ = s.CreateWindow(window.Window{ + Name: "editor", + Title: "New File - Editor", + URL: "/#/developer/editor?new=true", + Width: 1200, + Height: 800, }) } @@ -926,38 +924,32 @@ func (s *Service) handleOpenFile() { if !ok || len(paths) == 0 { return } - _, _, _ = s.Core().PERFORM(window.TaskOpenWindow{ - Window: window.Window{ - Name: "editor", - Title: paths[0] + " - Editor", - URL: "/#/developer/editor?file=" + paths[0], - Width: 1200, - Height: 800, - }, + _, _ = s.CreateWindow(window.Window{ + Name: "editor", + Title: paths[0] + " - Editor", + URL: "/#/developer/editor?file=" + paths[0], + Width: 1200, + Height: 800, }) } func (s *Service) handleSaveFile() { _ = s.Core().ACTION(ActionIDECommand{Command: "save"}) } func (s *Service) handleOpenEditor() { - _, _, _ = s.Core().PERFORM(window.TaskOpenWindow{ - Window: window.Window{ - Name: "editor", - Title: "Editor", - URL: "/#/developer/editor", - Width: 1200, - Height: 800, - }, + _, _ = s.CreateWindow(window.Window{ + Name: "editor", + Title: "Editor", + URL: "/#/developer/editor", + Width: 1200, + Height: 800, }) } func (s *Service) handleOpenTerminal() { - _, _, _ = s.Core().PERFORM(window.TaskOpenWindow{ - Window: window.Window{ - Name: "terminal", - Title: "Terminal", - URL: "/#/developer/terminal", - Width: 800, - Height: 500, - }, + _, _ = s.CreateWindow(window.Window{ + Name: "terminal", + Title: "Terminal", + URL: "/#/developer/terminal", + Width: 800, + Height: 500, }) } func (s *Service) handleRun() { _ = s.Core().ACTION(ActionIDECommand{Command: "run"}) } diff --git a/pkg/display/display_test.go b/pkg/display/display_test.go index ac7efe8..d3fb2d6 100644 --- a/pkg/display/display_test.go +++ b/pkg/display/display_test.go @@ -163,33 +163,20 @@ func TestServiceConclave_Bad(t *testing.T) { // --- IPC delegation tests (full conclave) --- -func TestOpenWindow_Good(t *testing.T) { +func TestOpenWindow_Defaults_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") - t.Run("creates window with default spec", func(t *testing.T) { - err := svc.OpenWindow(window.Window{}) - assert.NoError(t, err) + err := svc.OpenWindow(window.Window{}) + assert.NoError(t, err) - // Verify via IPC query - infos := svc.ListWindowInfos() - assert.GreaterOrEqual(t, len(infos), 1) - }) - - t.Run("creates window with custom spec", func(t *testing.T) { - err := svc.OpenWindow(window.Window{ - Name: "custom-window", - Title: "Custom Title", - Width: 640, - Height: 480, - URL: "/custom", - }) - assert.NoError(t, err) - - result, _, _ := c.QUERY(window.QueryWindowByName{Name: "custom-window"}) - info := result.(*window.WindowInfo) - assert.Equal(t, "custom-window", info.Name) - }) + info, err := svc.GetWindowInfo("main") + require.NoError(t, err) + require.NotNil(t, info) + assert.Equal(t, "main", info.Name) + assert.Equal(t, "Core", info.Title) + assert.Greater(t, info.Width, 0) + assert.Greater(t, info.Height, 0) } func TestGetWindowInfo_Good(t *testing.T) { diff --git a/pkg/display/docs/backend.md b/pkg/display/docs/backend.md index fce152c..927846c 100644 --- a/pkg/display/docs/backend.md +++ b/pkg/display/docs/backend.md @@ -13,8 +13,8 @@ The `Service` struct is the main entry point for the display logic. - `OnStartup(ctx context.Context) error`: Loads config and registers IPC handlers. - **Window Management:** - - `OpenWindow(spec window.Window) error`: Opens a new window from a declarative spec. - - `CreateWindow(spec window.Window) (*window.WindowInfo, error)`: Opens a window and returns its info. + - `OpenWindow(spec window.Window) error`: Opens the default window using manager defaults. + - `CreateWindow(spec window.Window) (*window.WindowInfo, error)`: Opens a named window and returns its info. - `GetWindowInfo(name string) (*window.WindowInfo, error)`: Queries a single window. - `ListWindowInfos() []window.WindowInfo`: Queries all tracked windows. - `SetWindowPosition(name string, x, y int) error` @@ -36,7 +36,7 @@ The `Service` struct is the main entry point for the display logic. Example: ```go -svc.OpenWindow(window.Window{ +svc.CreateWindow(window.Window{ Name: "editor", Title: "Editor", URL: "/#/editor", diff --git a/pkg/display/docs/overview.md b/pkg/display/docs/overview.md index e236d73..9d423ad 100644 --- a/pkg/display/docs/overview.md +++ b/pkg/display/docs/overview.md @@ -13,7 +13,8 @@ The project consists of two main parts: ### Display Service (`display`) The core service manages the application lifecycle and exposes declarative operations such as: -- `OpenWindow(window.Window{Name: "editor", URL: "/#/editor"})` +- `OpenWindow(window.Window{})` +- `CreateWindow(window.Window{Name: "editor", URL: "/#/editor"})` - `SaveLayout("coding")` - `TileWindows(window.TileModeLeftRight, []string{"editor", "terminal"})` - `ApplyWorkflowLayout(window.WorkflowCoding)` diff --git a/pkg/window/state.go b/pkg/window/state.go index 044a0f2..f300456 100644 --- a/pkg/window/state.go +++ b/pkg/window/state.go @@ -13,7 +13,7 @@ import ( ) // WindowState holds the persisted position/size of a window. -// JSON tags match existing window_state.json format for backward compat. +// JSON tags match the existing window_state.json format. type WindowState struct { X int `json:"x,omitempty"` Y int `json:"y,omitempty"` diff --git a/pkg/window/window.go b/pkg/window/window.go index 31d38cd..c09e4f2 100644 --- a/pkg/window/window.go +++ b/pkg/window/window.go @@ -6,7 +6,7 @@ import ( "sync" ) -// Window is CoreGUI's own window descriptor — NOT a Wails type alias. +// Window is CoreGUI's own window descriptor, not a Wails type alias. type Window struct { Name string `json:"name,omitempty"` Title string `json:"title,omitempty"` @@ -84,13 +84,8 @@ func (m *Manager) SetDefaultHeight(height int) { } } -// Open creates a window from a declarative Window spec, for example: -// m.Open(Window{Name: "editor", Title: "Editor", URL: "/"}) -func (m *Manager) Open(spec Window) (PlatformWindow, error) { - return m.Create(spec) -} - -// Create creates a window from a Window descriptor. +// Create opens a window from a declarative spec. +// Example: m.Create(Window{Name: "editor", Title: "Editor", URL: "/"}) func (m *Manager) Create(spec Window) (PlatformWindow, error) { w := spec if w.Name == "" { diff --git a/pkg/window/window_test.go b/pkg/window/window_test.go index 442d8a6..28431cb 100644 --- a/pkg/window/window_test.go +++ b/pkg/window/window_test.go @@ -45,18 +45,18 @@ func newTestManager() (*Manager, *mockPlatform) { return m, p } -func TestManager_Open_Good(t *testing.T) { +func TestManager_Create_Good(t *testing.T) { m, p := newTestManager() - pw, err := m.Open(Window{Name: "test", Title: "Test", URL: "/test", Width: 800, Height: 600}) + pw, err := m.Create(Window{Name: "test", Title: "Test", URL: "/test", Width: 800, Height: 600}) require.NoError(t, err) assert.NotNil(t, pw) assert.Equal(t, "test", pw.Name()) assert.Len(t, p.windows, 1) } -func TestManager_Open_Defaults_Good(t *testing.T) { +func TestManager_Create_Defaults_Good(t *testing.T) { m, _ := newTestManager() - pw, err := m.Open(Window{}) + pw, err := m.Create(Window{}) require.NoError(t, err) assert.Equal(t, "main", pw.Name()) w, h := pw.Size() @@ -64,12 +64,12 @@ func TestManager_Open_Defaults_Good(t *testing.T) { assert.Equal(t, 800, h) } -func TestManager_Open_CustomDefaults_Good(t *testing.T) { +func TestManager_Create_CustomDefaults_Good(t *testing.T) { m, _ := newTestManager() m.SetDefaultWidth(1440) m.SetDefaultHeight(900) - pw, err := m.Open(Window{}) + pw, err := m.Create(Window{}) require.NoError(t, err) w, h := pw.Size() @@ -79,7 +79,7 @@ func TestManager_Open_CustomDefaults_Good(t *testing.T) { func TestManager_Get_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(Window{Name: "findme"}) + _, _ = m.Create(Window{Name: "findme"}) pw, ok := m.Get("findme") assert.True(t, ok) assert.Equal(t, "findme", pw.Name()) @@ -93,8 +93,8 @@ func TestManager_Get_Bad(t *testing.T) { func TestManager_List_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(Window{Name: "a"}) - _, _ = m.Open(Window{Name: "b"}) + _, _ = m.Create(Window{Name: "a"}) + _, _ = m.Create(Window{Name: "b"}) names := m.List() assert.Len(t, names, 2) assert.Contains(t, names, "a") @@ -103,7 +103,7 @@ func TestManager_List_Good(t *testing.T) { func TestManager_Remove_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(Window{Name: "temp"}) + _, _ = m.Create(Window{Name: "temp"}) m.Remove("temp") _, ok := m.Get("temp") assert.False(t, ok) @@ -118,8 +118,8 @@ func TestTileMode_String_Good(t *testing.T) { func TestManager_TileWindows_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(Window{Name: "a", Width: 800, Height: 600}) - _, _ = m.Open(Window{Name: "b", Width: 800, Height: 600}) + _, _ = m.Create(Window{Name: "a", Width: 800, Height: 600}) + _, _ = m.Create(Window{Name: "b", Width: 800, Height: 600}) err := m.TileWindows(TileModeLeftRight, []string{"a", "b"}, 1920, 1080) require.NoError(t, err) a, _ := m.Get("a") @@ -138,7 +138,7 @@ func TestManager_TileWindows_Bad(t *testing.T) { func TestManager_SnapWindow_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(Window{Name: "snap", Width: 800, Height: 600}) + _, _ = m.Create(Window{Name: "snap", Width: 800, Height: 600}) err := m.SnapWindow("snap", SnapLeft, 1920, 1080) require.NoError(t, err) w, _ := m.Get("snap") @@ -150,8 +150,8 @@ func TestManager_SnapWindow_Good(t *testing.T) { func TestManager_StackWindows_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(Window{Name: "s1", Width: 800, Height: 600}) - _, _ = m.Open(Window{Name: "s2", Width: 800, Height: 600}) + _, _ = m.Create(Window{Name: "s1", Width: 800, Height: 600}) + _, _ = m.Create(Window{Name: "s2", Width: 800, Height: 600}) err := m.StackWindows([]string{"s1", "s2"}, 30, 30) require.NoError(t, err) s2, _ := m.Get("s2") @@ -192,7 +192,7 @@ func TestTileWindows_AllModes_Good(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { m, _ := newTestManager() - _, err := m.Open(Window{Name: "win", Width: 800, Height: 600}) + _, err := m.Create(Window{Name: "win", Width: 800, Height: 600}) require.NoError(t, err) err = m.TileWindows(tc.mode, []string{"win"}, screenW, screenH) @@ -238,7 +238,7 @@ func TestSnapWindow_AllPositions_Good(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { m, _ := newTestManager() - _, err := m.Open(Window{Name: "snap", Width: tc.initW, Height: tc.initH}) + _, err := m.Create(Window{Name: "snap", Width: tc.initW, Height: tc.initH}) require.NoError(t, err) err = m.SnapWindow("snap", tc.pos, screenW, screenH) @@ -261,7 +261,7 @@ func TestStackWindows_ThreeWindows_Good(t *testing.T) { m, _ := newTestManager() names := []string{"s1", "s2", "s3"} for _, name := range names { - _, err := m.Open(Window{Name: name, Width: 800, Height: 600}) + _, err := m.Create(Window{Name: name, Width: 800, Height: 600}) require.NoError(t, err) } @@ -317,9 +317,9 @@ func TestApplyWorkflow_AllLayouts_Good(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { m, _ := newTestManager() - _, err := m.Open(Window{Name: "editor", Width: 800, Height: 600}) + _, err := m.Create(Window{Name: "editor", Width: 800, Height: 600}) require.NoError(t, err) - _, err = m.Open(Window{Name: "terminal", Width: 800, Height: 600}) + _, err = m.Create(Window{Name: "terminal", Width: 800, Height: 600}) require.NoError(t, err) err = m.ApplyWorkflow(tc.workflow, []string{"editor", "terminal"}, screenW, screenH)