Wire notification categories and layout helpers
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run

This commit is contained in:
Snider 2026-04-15 22:18:35 +01:00
parent 0fb1ef5cfb
commit ba4780f2f8
3 changed files with 117 additions and 0 deletions

View file

@ -1464,6 +1464,82 @@ func (s *Service) ApplyWorkflowLayout(workflow window.WorkflowLayout) error {
return nil
}
// LayoutBesideEditor places a window beside a detected editor window.
//
// result, err := svc.LayoutBesideEditor("preview", "code", "right", 0.62)
func (s *Service) LayoutBesideEditor(name, editor, side string, ratio float64) (window.LayoutBesideEditorResult, error) {
r := s.Core().Action("window.layoutBesideEditor").Run(context.Background(), core.NewOptions(
core.Option{Key: "task", Value: window.TaskLayoutBesideEditor{
Name: name, Editor: editor, Side: side, Ratio: ratio,
}},
))
if !r.OK {
if e, ok := r.Value.(error); ok {
return window.LayoutBesideEditorResult{}, e
}
return window.LayoutBesideEditorResult{}, nil
}
result, _ := r.Value.(window.LayoutBesideEditorResult)
return result, nil
}
// LayoutSuggest returns a layout recommendation for the current screen.
//
// suggestion, err := svc.LayoutSuggest("", 2)
func (s *Service) LayoutSuggest(screenID string, windowCount int) (window.LayoutSuggestion, error) {
r := s.Core().Action("window.layoutSuggest").Run(context.Background(), core.NewOptions(
core.Option{Key: "task", Value: window.TaskLayoutSuggest{
ScreenID: screenID, WindowCount: windowCount,
}},
))
if !r.OK {
if e, ok := r.Value.(error); ok {
return window.LayoutSuggestion{}, e
}
return window.LayoutSuggestion{}, nil
}
result, _ := r.Value.(window.LayoutSuggestion)
return result, nil
}
// FindScreenSpace finds an unused rectangle for a new window.
//
// space, err := svc.FindScreenSpace("", 800, 600, 24)
func (s *Service) FindScreenSpace(screenID string, width, height, padding int) (window.ScreenSpace, error) {
r := s.Core().Action("window.findSpace").Run(context.Background(), core.NewOptions(
core.Option{Key: "task", Value: window.TaskScreenFindSpace{
ScreenID: screenID, Width: width, Height: height, Padding: padding,
}},
))
if !r.OK {
if e, ok := r.Value.(error); ok {
return window.ScreenSpace{}, e
}
return window.ScreenSpace{}, nil
}
result, _ := r.Value.(window.ScreenSpace)
return result, nil
}
// ArrangeWindowPair positions two windows in an optimal split.
//
// arrangement, err := svc.ArrangeWindowPair("editor", "preview", "", 0.55)
func (s *Service) ArrangeWindowPair(primary, secondary, screenID string, ratio float64) (window.PairArrangement, error) {
r := s.Core().Action("window.arrangePair").Run(context.Background(), core.NewOptions(
core.Option{Key: "task", Value: window.TaskWindowArrangePair{
Primary: primary, Secondary: secondary, ScreenID: screenID, Ratio: ratio,
}},
))
if !r.OK {
if e, ok := r.Value.(error); ok {
return window.PairArrangement{}, e
}
return window.PairArrangement{}, nil
}
result, _ := r.Value.(window.PairArrangement)
return result, nil
}
// GetEventManager returns the event manager for WebSocket event subscriptions.
func (s *Service) GetEventManager() *WSEventManager {
return s.events

View file

@ -102,6 +102,7 @@ func (s *Service) send(options NotificationOptions) error {
if options.ID == "" {
options.ID = "core-" + strconv.FormatInt(time.Now().UnixNano(), 10)
}
options = s.applyCategoryActions(options)
if err := s.platform.Send(options); err != nil {
// Fallback: show as dialog via IPC
@ -115,6 +116,22 @@ func (s *Service) send(options NotificationOptions) error {
return nil
}
func (s *Service) applyCategoryActions(options NotificationOptions) NotificationOptions {
if options.CategoryID == "" || len(options.Actions) > 0 {
return options
}
s.mu.Lock()
category, ok := s.categories[options.CategoryID]
s.mu.Unlock()
if !ok || len(category.Actions) == 0 {
return options
}
options.Actions = append([]NotificationAction(nil), category.Actions...)
return options
}
// fallbackDialog shows a dialog via IPC when native notifications fail.
func (s *Service) fallbackDialog(options NotificationOptions) error {
// Map severity to dialog type

View file

@ -216,6 +216,30 @@ func TestTaskSend_WithActions_Good(t *testing.T) {
assert.Equal(t, 2, len(mock.lastOpts.Actions))
}
func TestTaskSend_RegisteredCategoryActions_Good(t *testing.T) {
mock, c := newTestService(t)
require.True(t, taskRun(c, "notification.registerCategory", TaskRegisterCategory{
Category: NotificationCategory{
ID: "message",
Actions: []NotificationAction{
{ID: "reply", Title: "Reply"},
{ID: "dismiss", Title: "Dismiss"},
},
},
}).OK)
r := taskRun(c, "notification.send", TaskSend{
Options: NotificationOptions{
Title: "Team Chat",
Message: "New message from Alice",
CategoryID: "message",
},
})
require.True(t, r.OK)
assert.Equal(t, 2, len(mock.lastOpts.Actions))
assert.Equal(t, "reply", mock.lastOpts.Actions[0].ID)
}
func TestTaskClear_Good_Specific(t *testing.T) {
mock, c := newTestService(t)
require.True(t, taskRun(c, "notification.send", TaskSend{