refactor(ax): clarify default and named window creation
Some checks failed
Security Scan / security (push) Failing after 28s
Test / test (push) Successful in 1m19s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-31 07:43:28 +00:00
parent 450d04411a
commit 78a2a33ed2
9 changed files with 77 additions and 105 deletions

View file

@ -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

View file

@ -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.

View file

@ -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"}) }

View file

@ -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) {

View file

@ -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",

View file

@ -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)`

View file

@ -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"`

View file

@ -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 == "" {

View file

@ -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)