diff --git a/go.sum b/go.sum index f38e4fac..d4799104 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +dappco.re/go/core v0.3.3 h1:TaE7/SObQ3YEZj4ov1BAXWwLJEIttkrxzncVGewR3Bs= +dappco.re/go/core v0.3.3/go.mod h1:Cp4ac25pghvO2iqOu59t1GyngTKVOzKB5/VPdhRi9CQ= forge.lthn.ai/Snider/Borg v0.3.1/go.mod h1:Z7DJD0yHXsxSyM7Mjl6/g4gH1NBsIz44Bf5AFlV76Wg= forge.lthn.ai/core/config v0.1.8 h1:xP2hys7T94QGVF/OTh84/Zr5Dm/dL/0vzjht8zi+LOg= forge.lthn.ai/core/config v0.1.8/go.mod h1:8epZrkwoCt+5ayrqdinOUU/+w6UoxOyv9ZrdgVOgYfQ= diff --git a/pkg/contextmenu/messages.go b/pkg/contextmenu/messages.go index cb62e17a..c596a94b 100644 --- a/pkg/contextmenu/messages.go +++ b/pkg/contextmenu/messages.go @@ -1,11 +1,11 @@ // pkg/contextmenu/messages.go package contextmenu -import "errors" +import corego "dappco.re/go/core" // ErrMenuNotFound is returned when attempting to remove or get a menu // that does not exist in the registry. -var ErrMenuNotFound = errors.New("contextmenu: menu not found") +var ErrMenuNotFound = corego.NewError("contextmenu: menu not found") // --- Queries --- diff --git a/pkg/contextmenu/service.go b/pkg/contextmenu/service.go index 973346d2..8ff3a507 100644 --- a/pkg/contextmenu/service.go +++ b/pkg/contextmenu/service.go @@ -3,8 +3,8 @@ package contextmenu import ( "context" - "fmt" + corego "dappco.re/go/core" "forge.lthn.ai/core/go/pkg/core" ) @@ -92,7 +92,7 @@ func (s *Service) taskAdd(t TaskAdd) error { }) }) if err != nil { - return fmt.Errorf("contextmenu: platform add failed: %w", err) + return corego.Wrap(err, "contextmenu.add", "platform add failed") } s.menus[t.Name] = t.Menu @@ -106,7 +106,7 @@ func (s *Service) taskRemove(t TaskRemove) error { err := s.platform.Remove(t.Name) if err != nil { - return fmt.Errorf("contextmenu: platform remove failed: %w", err) + return corego.Wrap(err, "contextmenu.remove", "platform remove failed") } delete(s.menus, t.Name) diff --git a/pkg/display/display.go b/pkg/display/display.go index 75da55c2..0b8d5eaf 100644 --- a/pkg/display/display.go +++ b/pkg/display/display.go @@ -4,29 +4,27 @@ package display import ( "context" "encoding/base64" - "fmt" "os" - "path/filepath" "runtime" - "encoding/json" + corego "dappco.re/go/core" "forge.lthn.ai/core/config" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/browser" - "forge.lthn.ai/core/gui/pkg/clipboard" - "forge.lthn.ai/core/gui/pkg/contextmenu" - "forge.lthn.ai/core/gui/pkg/dialog" - "forge.lthn.ai/core/gui/pkg/dock" - "forge.lthn.ai/core/gui/pkg/environment" - "forge.lthn.ai/core/gui/pkg/keybinding" - "forge.lthn.ai/core/gui/pkg/lifecycle" - "forge.lthn.ai/core/gui/pkg/menu" - "forge.lthn.ai/core/gui/pkg/notification" - "forge.lthn.ai/core/gui/pkg/screen" - "forge.lthn.ai/core/gui/pkg/systray" - "forge.lthn.ai/core/gui/pkg/webview" - "forge.lthn.ai/core/gui/pkg/window" + "dappco.re/go/core/gui/pkg/browser" + "dappco.re/go/core/gui/pkg/clipboard" + "dappco.re/go/core/gui/pkg/contextmenu" + "dappco.re/go/core/gui/pkg/dialog" + "dappco.re/go/core/gui/pkg/dock" + "dappco.re/go/core/gui/pkg/environment" + "dappco.re/go/core/gui/pkg/keybinding" + "dappco.re/go/core/gui/pkg/lifecycle" + "dappco.re/go/core/gui/pkg/menu" + "dappco.re/go/core/gui/pkg/notification" + "dappco.re/go/core/gui/pkg/screen" + "dappco.re/go/core/gui/pkg/systray" + "dappco.re/go/core/gui/pkg/webview" + "dappco.re/go/core/gui/pkg/window" "github.com/wailsapp/wails/v3/pkg/application" ) @@ -259,7 +257,7 @@ type WSMessage struct { func wsRequire(data map[string]any, key string) (string, error) { v, _ := data[key].(string) if v == "" { - return "", fmt.Errorf("ws: missing required field %q", key) + return "", corego.NewError(corego.Sprintf("ws: missing required field %q", key)) } return v, nil } @@ -302,10 +300,11 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) { result, handled, err = s.GetFocusedWindow(), true, nil case "window:create": var opts CreateWindowOptions - encoded, _ := json.Marshal(msg.Data) - if err := json.Unmarshal(encoded, &opts); err != nil { - return nil, false, fmt.Errorf("ws: invalid window create options: %w", err) + encodedR := corego.JSONMarshal(msg.Data) + if encodedR.OK { + _ = corego.JSONUnmarshal(encodedR.Value.([]byte), &opts) } + return nil, false, corego.Wrap(err, "display.ws", "ws: invalid window create options") info, createErr := s.CreateWindow(opts) if createErr != nil { return nil, false, createErr @@ -444,9 +443,11 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) { result, handled, err = s.Core().QUERY(dock.QueryVisible{}) case "contextmenu:add": name, _ := msg.Data["name"].(string) - menuJSON, _ := json.Marshal(msg.Data["menu"]) + menuR := corego.JSONMarshal(msg.Data["menu"]) var menuDef contextmenu.ContextMenuDef - _ = json.Unmarshal(menuJSON, &menuDef) + if menuR.OK { + _ = corego.JSONUnmarshal(menuR.Value.([]byte), &menuDef) + } result, handled, err = s.Core().PERFORM(contextmenu.TaskAdd{ Name: name, Menu: menuDef, }) @@ -768,7 +769,7 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) { } workflow, ok := window.ParseWorkflowLayout(workflowName) if !ok { - return nil, false, fmt.Errorf("ws: unknown workflow %q", workflowName) + return nil, false, corego.NewError(corego.Sprintf("ws: unknown workflow %q", workflowName)) } var names []string if raw, ok := msg.Data["windows"].([]any); ok { @@ -899,17 +900,19 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) { case "clipboard:write-image": data, ok := msg.Data["data"].(string) if !ok || data == "" { - return nil, false, fmt.Errorf("ws: missing required field %q", "data") + return nil, false, corego.NewError(corego.Sprintf("ws: missing required field %q", "data")) } decoded, decodeErr := base64.StdEncoding.DecodeString(data) if decodeErr != nil { - return nil, false, fmt.Errorf("ws: invalid base64 image data: %w", decodeErr) + return nil, false, corego.Wrap(decodeErr, "display.ws", "ws: invalid base64 image data") } result, handled, err = s.Core().PERFORM(clipboard.TaskSetImage{Data: decoded}) case "notification:show": var opts notification.NotificationOptions - encoded, _ := json.Marshal(msg.Data) - _ = json.Unmarshal(encoded, &opts) + encodedR := corego.JSONMarshal(msg.Data) + if encodedR.OK { + _ = corego.JSONUnmarshal(encodedR.Value.([]byte), &opts) + } result, handled, err = s.Core().PERFORM(notification.TaskSend{Opts: opts}) case "notification:info": title, e := wsRequire(msg.Data, "title") @@ -965,8 +968,10 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) { subtitle, _ := msg.Data["subtitle"].(string) actions := make([]notification.NotificationAction, 0) if raw, ok := msg.Data["actions"]; ok { - encoded, _ := json.Marshal(raw) - _ = json.Unmarshal(encoded, &actions) + encodedR := corego.JSONMarshal(raw) + if encodedR.OK { + _ = corego.JSONUnmarshal(encodedR.Value.([]byte), &actions) + } } result, handled, err = s.Core().PERFORM(notification.TaskSend{ Opts: notification.NotificationOptions{ @@ -1014,18 +1019,20 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) { } decoded, decodeErr := base64.StdEncoding.DecodeString(data) if decodeErr != nil { - return nil, false, fmt.Errorf("ws: invalid base64 tray icon data: %w", decodeErr) + return nil, false, corego.Wrap(decodeErr, "display.ws", "ws: invalid base64 tray icon data") } result, handled, err = s.Core().PERFORM(systray.TaskSetTrayIcon{Data: decoded}) case "tray:set-menu": raw, ok := msg.Data["items"] if !ok { - return nil, false, fmt.Errorf("ws: missing required field %q", "items") + return nil, false, corego.NewError(corego.Sprintf("ws: missing required field %q", "items")) } - encoded, _ := json.Marshal(raw) + encodedItemsR := corego.JSONMarshal(raw) var items []systray.TrayMenuItem - if err := json.Unmarshal(encoded, &items); err != nil { - return nil, false, fmt.Errorf("ws: invalid tray menu items: %w", err) + if encodedItemsR.OK { + if r := corego.JSONUnmarshal(encodedItemsR.Value.([]byte), &items); !r.OK { + return nil, false, corego.E("display.ws", "invalid tray menu items", nil) + } } result, handled, err = s.Core().PERFORM(systray.TaskSetTrayMenu{Items: items}) case "tray:info": @@ -1045,10 +1052,11 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) { result, handled, err = nil, true, s.SetTheme(isDark) case "dialog:open-file": var opts dialog.OpenFileOptions - encoded, _ := json.Marshal(msg.Data) - if err := json.Unmarshal(encoded, &opts); err != nil { - return nil, false, fmt.Errorf("ws: invalid open file options: %w", err) + encodedR := corego.JSONMarshal(msg.Data) + if encodedR.OK { + _ = corego.JSONUnmarshal(encodedR.Value.([]byte), &opts) } + return nil, false, corego.Wrap(err, "display.ws", "ws: invalid open file options") paths, openErr := s.OpenFileDialog(opts) if openErr != nil { return nil, false, openErr @@ -1056,10 +1064,11 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) { result, handled, err = paths, true, nil case "dialog:save-file": var opts dialog.SaveFileOptions - encoded, _ := json.Marshal(msg.Data) - if err := json.Unmarshal(encoded, &opts); err != nil { - return nil, false, fmt.Errorf("ws: invalid save file options: %w", err) + encodedR := corego.JSONMarshal(msg.Data) + if encodedR.OK { + _ = corego.JSONUnmarshal(encodedR.Value.([]byte), &opts) } + return nil, false, corego.Wrap(err, "display.ws", "ws: invalid save file options") path, saveErr := s.SaveFileDialog(opts) if saveErr != nil { return nil, false, saveErr @@ -1067,10 +1076,11 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) { result, handled, err = path, true, nil case "dialog:open-directory": var opts dialog.OpenDirectoryOptions - encoded, _ := json.Marshal(msg.Data) - if err := json.Unmarshal(encoded, &opts); err != nil { - return nil, false, fmt.Errorf("ws: invalid open directory options: %w", err) + encodedR := corego.JSONMarshal(msg.Data) + if encodedR.OK { + _ = corego.JSONUnmarshal(encodedR.Value.([]byte), &opts) } + return nil, false, corego.Wrap(err, "display.ws", "ws: invalid open directory options") path, dirErr := s.OpenDirectoryDialog(opts) if dirErr != nil { return nil, false, dirErr @@ -1135,7 +1145,7 @@ func (s *Service) handleTrayAction(actionID string) { result, handled, _ := s.Core().QUERY(environment.QueryInfo{}) if handled { info := result.(environment.EnvironmentInfo) - details := fmt.Sprintf("OS: %s\nArch: %s\nPlatform: %s %s", + details := corego.Sprintf("OS: %s\nArch: %s\nPlatform: %s %s", info.OS, info.Arch, info.Platform.Name, info.Platform.Version) _, _, _ = s.Core().PERFORM(dialog.TaskMessageDialog{ Opts: dialog.MessageDialogOptions{ @@ -1154,9 +1164,9 @@ func (s *Service) handleTrayAction(actionID string) { func guiConfigPath() string { home, err := os.UserHomeDir() if err != nil { - return filepath.Join(".core", "gui", "config.yaml") + return corego.JoinPath(".core", "gui", "config.yaml") } - return filepath.Join(home, ".core", "gui", "config.yaml") + return corego.JoinPath(home, ".core", "gui", "config.yaml") } func (s *Service) loadConfig() { @@ -1250,7 +1260,7 @@ func (s *Service) GetWindowInfo(name string) (*window.WindowInfo, error) { return nil, err } if !handled { - return nil, fmt.Errorf("window service not available") + return nil, corego.NewError(corego.Sprintf("window service not available")) } info, _ := result.(*window.WindowInfo) return info, nil @@ -1410,7 +1420,7 @@ func (s *Service) GetWindowTitle(name string) (string, error) { return "", err } if info == nil { - return "", fmt.Errorf("window not found: %s", name) + return "", corego.NewError(corego.Sprintf("window not found: %s", name)) } return info.Title, nil } @@ -1459,7 +1469,7 @@ type CreateWindowOptions struct { // Use: info, err := svc.CreateWindow(display.CreateWindowOptions{Name: "editor", URL: "/editor"}) func (s *Service) CreateWindow(opts CreateWindowOptions) (*window.WindowInfo, error) { if opts.Name == "" { - return nil, fmt.Errorf("window name is required") + return nil, corego.NewError(corego.Sprintf("window name is required")) } result, _, err := s.Core().PERFORM(window.TaskOpenWindow{ Window: &window.Window{ @@ -1488,7 +1498,7 @@ func (s *Service) CreateWindow(opts CreateWindowOptions) (*window.WindowInfo, er func (s *Service) SaveLayout(name string) error { ws := s.windowService() if ws == nil { - return fmt.Errorf("window service not available") + return corego.NewError(corego.Sprintf("window service not available")) } states := make(map[string]window.WindowState) for _, n := range ws.Manager().List() { @@ -1506,11 +1516,11 @@ func (s *Service) SaveLayout(name string) error { func (s *Service) RestoreLayout(name string) error { ws := s.windowService() if ws == nil { - return fmt.Errorf("window service not available") + return corego.NewError(corego.Sprintf("window service not available")) } layout, ok := ws.Manager().Layout().GetLayout(name) if !ok { - return fmt.Errorf("layout not found: %s", name) + return corego.NewError(corego.Sprintf("layout not found: %s", name)) } for wName, state := range layout.Windows { if pw, ok := ws.Manager().Get(wName); ok { @@ -1541,7 +1551,7 @@ func (s *Service) ListLayouts() []window.LayoutInfo { func (s *Service) DeleteLayout(name string) error { ws := s.windowService() if ws == nil { - return fmt.Errorf("window service not available") + return corego.NewError(corego.Sprintf("window service not available")) } ws.Manager().Layout().DeleteLayout(name) return nil @@ -1568,7 +1578,7 @@ func (s *Service) GetLayout(name string) *window.Layout { func (s *Service) TileWindows(mode window.TileMode, windowNames []string) error { ws := s.windowService() if ws == nil { - return fmt.Errorf("window service not available") + return corego.NewError(corego.Sprintf("window service not available")) } screenWidth, screenHeight := s.primaryScreenSize() return ws.Manager().TileWindows(mode, windowNames, screenWidth, screenHeight) @@ -1579,7 +1589,7 @@ func (s *Service) TileWindows(mode window.TileMode, windowNames []string) error func (s *Service) SnapWindow(name string, position window.SnapPosition) error { ws := s.windowService() if ws == nil { - return fmt.Errorf("window service not available") + return corego.NewError(corego.Sprintf("window service not available")) } screenWidth, screenHeight := s.primaryScreenSize() return ws.Manager().SnapWindow(name, position, screenWidth, screenHeight) @@ -1617,7 +1627,7 @@ func (s *Service) primaryScreenSize() (int, int) { func (s *Service) StackWindows(windowNames []string, offsetX, offsetY int) error { ws := s.windowService() if ws == nil { - return fmt.Errorf("window service not available") + return corego.NewError(corego.Sprintf("window service not available")) } return ws.Manager().StackWindows(windowNames, offsetX, offsetY) } @@ -1627,7 +1637,7 @@ func (s *Service) StackWindows(windowNames []string, offsetX, offsetY int) error func (s *Service) ApplyWorkflowLayout(workflow window.WorkflowLayout) error { ws := s.windowService() if ws == nil { - return fmt.Errorf("window service not available") + return corego.NewError(corego.Sprintf("window service not available")) } screenWidth, screenHeight := s.primaryScreenSize() return ws.Manager().ApplyWorkflow(workflow, ws.Manager().List(), screenWidth, screenHeight) @@ -1638,7 +1648,7 @@ func (s *Service) ApplyWorkflowLayout(workflow window.WorkflowLayout) error { func (s *Service) ArrangeWindowPair(first, second string) error { ws := s.windowService() if ws == nil { - return fmt.Errorf("window service not available") + return corego.NewError(corego.Sprintf("window service not available")) } screenWidth, screenHeight := s.primaryScreenSize() return ws.Manager().ArrangePair(first, second, screenWidth, screenHeight) @@ -1649,7 +1659,7 @@ func (s *Service) ArrangeWindowPair(first, second string) error { func (s *Service) FindSpace(width, height int) (window.SpaceInfo, error) { ws := s.windowService() if ws == nil { - return window.SpaceInfo{}, fmt.Errorf("window service not available") + return window.SpaceInfo{}, corego.NewError(corego.Sprintf("window service not available")) } screenWidth, screenHeight := s.primaryScreenSize() if width <= 0 { @@ -1673,7 +1683,7 @@ func (s *Service) SuggestLayout(windowCount, screenWidth, screenHeight int) (win return window.LayoutSuggestion{}, err } if !handled { - return window.LayoutSuggestion{}, fmt.Errorf("window service not available") + return window.LayoutSuggestion{}, corego.NewError(corego.Sprintf("window service not available")) } suggestion, _ := result.(window.LayoutSuggestion) return suggestion, nil @@ -1710,7 +1720,7 @@ func (s *Service) GetScreen(id string) (*screen.Screen, error) { return nil, err } if !handled { - return nil, fmt.Errorf("screen service not available") + return nil, corego.NewError(corego.Sprintf("screen service not available")) } scr, _ := result.(*screen.Screen) return scr, nil @@ -1724,7 +1734,7 @@ func (s *Service) GetPrimaryScreen() (*screen.Screen, error) { return nil, err } if !handled { - return nil, fmt.Errorf("screen service not available") + return nil, corego.NewError(corego.Sprintf("screen service not available")) } scr, _ := result.(*screen.Screen) return scr, nil @@ -1738,7 +1748,7 @@ func (s *Service) GetScreenAtPoint(x, y int) (*screen.Screen, error) { return nil, err } if !handled { - return nil, fmt.Errorf("screen service not available") + return nil, corego.NewError(corego.Sprintf("screen service not available")) } scr, _ := result.(*screen.Screen) return scr, nil @@ -1923,7 +1933,7 @@ func (s *Service) RequestNotificationPermission() (bool, error) { return false, err } if !handled { - return false, fmt.Errorf("notification service not available") + return false, corego.NewError(corego.Sprintf("notification service not available")) } granted, _ := result.(bool) return granted, nil @@ -1937,7 +1947,7 @@ func (s *Service) CheckNotificationPermission() (bool, error) { return false, err } if !handled { - return false, fmt.Errorf("notification service not available") + return false, corego.NewError(corego.Sprintf("notification service not available")) } status, _ := result.(notification.PermissionStatus) return status.Granted, nil @@ -1951,7 +1961,7 @@ func (s *Service) ClearNotifications() error { return err } if !handled { - return fmt.Errorf("notification service not available") + return corego.NewError(corego.Sprintf("notification service not available")) } return nil } @@ -1966,7 +1976,7 @@ func (s *Service) OpenFileDialog(opts dialog.OpenFileOptions) ([]string, error) return nil, err } if !handled { - return nil, fmt.Errorf("dialog service not available") + return nil, corego.NewError(corego.Sprintf("dialog service not available")) } paths, _ := result.([]string) return paths, nil @@ -1993,7 +2003,7 @@ func (s *Service) SaveFileDialog(opts dialog.SaveFileOptions) (string, error) { return "", err } if !handled { - return "", fmt.Errorf("dialog service not available") + return "", corego.NewError(corego.Sprintf("dialog service not available")) } path, _ := result.(string) return path, nil @@ -2007,7 +2017,7 @@ func (s *Service) OpenDirectoryDialog(opts dialog.OpenDirectoryOptions) (string, return "", err } if !handled { - return "", fmt.Errorf("dialog service not available") + return "", corego.NewError(corego.Sprintf("dialog service not available")) } path, _ := result.(string) return path, nil @@ -2028,7 +2038,7 @@ func (s *Service) ConfirmDialog(title, message string) (bool, error) { return false, err } if !handled { - return false, fmt.Errorf("dialog service not available") + return false, corego.NewError(corego.Sprintf("dialog service not available")) } button, _ := result.(string) return button == "Yes" || button == "OK", nil @@ -2060,7 +2070,7 @@ func (s *Service) PromptDialog(title, message string) (string, bool, error) { return "", false, err } if !handled { - return "", false, fmt.Errorf("dialog service not available") + return "", false, corego.NewError(corego.Sprintf("dialog service not available")) } button, _ := result.(string) return button, button == "OK", nil @@ -2075,27 +2085,27 @@ func (s *Service) promptViaWebView(title, message string) (string, bool, error) } } if windowName == "" { - return "", false, fmt.Errorf("no webview window available") + return "", false, corego.NewError(corego.Sprintf("no webview window available")) } - encodedTitle, err := json.Marshal(title) - if err != nil { - return "", false, err + encodedTitleR := corego.JSONMarshal(title) + if !encodedTitleR.OK { + return "", false, corego.E("display.showDialog", "failed to marshal title", nil) } - encodedMessage, err := json.Marshal(message) - if err != nil { - return "", false, err + encodedMessageR := corego.JSONMarshal(message) + if !encodedMessageR.OK { + return "", false, corego.E("display.showDialog", "failed to marshal message", nil) } result, handled, err := s.Core().PERFORM(webview.TaskEvaluate{ Window: windowName, - Script: "window.prompt(" + string(encodedTitle) + "," + string(encodedMessage) + ")", + Script: "window.prompt(" + string(encodedTitleR.Value.([]byte)) + "," + string(encodedMessageR.Value.([]byte)) + ")", }) if err != nil { return "", false, err } if !handled { - return "", false, fmt.Errorf("webview service not available") + return "", false, corego.NewError(corego.Sprintf("webview service not available")) } if result == nil { return "", false, nil @@ -2103,7 +2113,7 @@ func (s *Service) promptViaWebView(title, message string) (string, bool, error) if text, ok := result.(string); ok { return text, true, nil } - return fmt.Sprint(result), true, nil + return corego.Sprint(result), true, nil } // DialogMessage shows an informational, warning, or error message via the notification pipeline. @@ -2178,7 +2188,7 @@ func (s *Service) SetThemeMode(theme string) error { return err } if !handled { - return fmt.Errorf("environment service not available") + return corego.NewError(corego.Sprintf("environment service not available")) } return nil } @@ -2193,7 +2203,7 @@ func (s *Service) SetTrayIcon(data []byte) error { return err } if !handled { - return fmt.Errorf("systray service not available") + return corego.NewError(corego.Sprintf("systray service not available")) } return nil } @@ -2206,7 +2216,7 @@ func (s *Service) SetTrayTooltip(tooltip string) error { return err } if !handled { - return fmt.Errorf("systray service not available") + return corego.NewError(corego.Sprintf("systray service not available")) } return nil } @@ -2219,7 +2229,7 @@ func (s *Service) SetTrayLabel(label string) error { return err } if !handled { - return fmt.Errorf("systray service not available") + return corego.NewError(corego.Sprintf("systray service not available")) } return nil } @@ -2232,7 +2242,7 @@ func (s *Service) SetTrayMenu(items []systray.TrayMenuItem) error { return err } if !handled { - return fmt.Errorf("systray service not available") + return corego.NewError(corego.Sprintf("systray service not available")) } return nil } @@ -2255,7 +2265,7 @@ func (s *Service) ShowTrayMessage(title, message string) error { return err } if !handled { - return fmt.Errorf("systray service not available") + return corego.NewError(corego.Sprintf("systray service not available")) } return nil } diff --git a/pkg/display/display_test.go b/pkg/display/display_test.go index 84c8b693..384f345a 100644 --- a/pkg/display/display_test.go +++ b/pkg/display/display_test.go @@ -8,15 +8,15 @@ import ( "testing" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/clipboard" - "forge.lthn.ai/core/gui/pkg/dialog" - "forge.lthn.ai/core/gui/pkg/environment" - "forge.lthn.ai/core/gui/pkg/menu" - "forge.lthn.ai/core/gui/pkg/notification" - "forge.lthn.ai/core/gui/pkg/screen" - "forge.lthn.ai/core/gui/pkg/systray" - "forge.lthn.ai/core/gui/pkg/webview" - "forge.lthn.ai/core/gui/pkg/window" + "dappco.re/go/core/gui/pkg/clipboard" + "dappco.re/go/core/gui/pkg/dialog" + "dappco.re/go/core/gui/pkg/environment" + "dappco.re/go/core/gui/pkg/menu" + "dappco.re/go/core/gui/pkg/notification" + "dappco.re/go/core/gui/pkg/screen" + "dappco.re/go/core/gui/pkg/systray" + "dappco.re/go/core/gui/pkg/webview" + "dappco.re/go/core/gui/pkg/window" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/display/events.go b/pkg/display/events.go index bc5ba8e6..9c468a82 100644 --- a/pkg/display/events.go +++ b/pkg/display/events.go @@ -2,13 +2,12 @@ package display import ( - "encoding/json" - "fmt" "net/http" "sync" "time" - "forge.lthn.ai/core/gui/pkg/window" + "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/window" "github.com/gorilla/websocket" ) @@ -144,13 +143,13 @@ func (em *WSEventManager) sendEvent(conn *websocket.Conn, event Event) { return } - data, err := json.Marshal(event) - if err != nil { + r := core.JSONMarshal(event) + if !r.OK { return } conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) - if err := conn.WriteMessage(websocket.TextMessage, data); err != nil { + if err := conn.WriteMessage(websocket.TextMessage, r.Value.([]byte)); err != nil { em.removeClient(conn) } } @@ -188,7 +187,7 @@ func (em *WSEventManager) handleMessages(conn *websocket.Conn) { EventTypes []EventType `json:"eventTypes,omitempty"` } - if err := json.Unmarshal(message, &msg); err != nil { + if r := core.JSONUnmarshal(message, &msg); !r.OK { continue } @@ -217,7 +216,7 @@ func (em *WSEventManager) subscribe(conn *websocket.Conn, id string, eventTypes if id == "" { em.mu.Lock() em.nextSubID++ - id = fmt.Sprintf("sub-%d", em.nextSubID) + id = core.Sprintf("sub-%d", em.nextSubID) em.mu.Unlock() } @@ -234,8 +233,10 @@ func (em *WSEventManager) subscribe(conn *websocket.Conn, id string, eventTypes "id": id, "eventTypes": eventTypes, } - data, _ := json.Marshal(response) - conn.WriteMessage(websocket.TextMessage, data) + r := core.JSONMarshal(response) + if r.OK { + conn.WriteMessage(websocket.TextMessage, r.Value.([]byte)) + } } // unsubscribe removes a subscription for a client. @@ -257,8 +258,10 @@ func (em *WSEventManager) unsubscribe(conn *websocket.Conn, id string) { "type": "unsubscribed", "id": id, } - data, _ := json.Marshal(response) - conn.WriteMessage(websocket.TextMessage, data) + r := core.JSONMarshal(response) + if r.OK { + conn.WriteMessage(websocket.TextMessage, r.Value.([]byte)) + } } // listSubscriptions sends a list of active subscriptions to a client. @@ -282,8 +285,10 @@ func (em *WSEventManager) listSubscriptions(conn *websocket.Conn) { "type": "subscriptions", "subscriptions": subs, } - data, _ := json.Marshal(response) - conn.WriteMessage(websocket.TextMessage, data) + r := core.JSONMarshal(response) + if r.OK { + conn.WriteMessage(websocket.TextMessage, r.Value.([]byte)) + } } // removeClient removes a client and its subscriptions. diff --git a/pkg/environment/service.go b/pkg/environment/service.go index bfb162b6..9dfd2812 100644 --- a/pkg/environment/service.go +++ b/pkg/environment/service.go @@ -3,8 +3,8 @@ package environment import ( "context" - "fmt" + corego "dappco.re/go/core" "forge.lthn.ai/core/go/pkg/core" ) @@ -104,7 +104,7 @@ func (s *Service) taskSetTheme(task TaskSetTheme) error { s.overrideDark = &isDark shouldApplyTheme = true default: - return fmt.Errorf("invalid theme mode: %s", task.Theme) + return corego.E("environment.setTheme", corego.Sprintf("invalid theme mode: %s", task.Theme), nil) } if shouldApplyTheme { diff --git a/pkg/keybinding/messages.go b/pkg/keybinding/messages.go index 7f037f3f..1bfc2c5c 100644 --- a/pkg/keybinding/messages.go +++ b/pkg/keybinding/messages.go @@ -1,11 +1,11 @@ // pkg/keybinding/messages.go package keybinding -import "errors" +import corego "dappco.re/go/core" // ErrAlreadyRegistered is returned when attempting to add a binding // that already exists. Callers must TaskRemove first to rebind. -var ErrAlreadyRegistered = errors.New("keybinding: accelerator already registered") +var ErrAlreadyRegistered = corego.NewError("keybinding: accelerator already registered") // BindingInfo describes a registered keyboard shortcut. type BindingInfo struct { diff --git a/pkg/keybinding/service.go b/pkg/keybinding/service.go index 048c259d..44fb4d32 100644 --- a/pkg/keybinding/service.go +++ b/pkg/keybinding/service.go @@ -3,8 +3,8 @@ package keybinding import ( "context" - "fmt" + corego "dappco.re/go/core" "forge.lthn.ai/core/go/pkg/core" ) @@ -75,7 +75,7 @@ func (s *Service) taskAdd(t TaskAdd) error { _ = s.Core().ACTION(ActionTriggered{Accelerator: t.Accelerator}) }) if err != nil { - return fmt.Errorf("keybinding: platform add failed: %w", err) + return corego.Wrap(err, "keybinding.add", "platform add failed") } s.bindings[t.Accelerator] = BindingInfo{ @@ -87,12 +87,12 @@ func (s *Service) taskAdd(t TaskAdd) error { func (s *Service) taskRemove(t TaskRemove) error { if _, exists := s.bindings[t.Accelerator]; !exists { - return fmt.Errorf("keybinding: not registered: %s", t.Accelerator) + return corego.E("keybinding.remove", corego.Sprintf("not registered: %s", t.Accelerator), nil) } err := s.platform.Remove(t.Accelerator) if err != nil { - return fmt.Errorf("keybinding: platform remove failed: %w", err) + return corego.Wrap(err, "keybinding.remove", "platform remove failed") } delete(s.bindings, t.Accelerator) diff --git a/pkg/mcp/mcp_test.go b/pkg/mcp/mcp_test.go index 0bf04324..5862d188 100644 --- a/pkg/mcp/mcp_test.go +++ b/pkg/mcp/mcp_test.go @@ -6,13 +6,13 @@ import ( "testing" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/clipboard" - "forge.lthn.ai/core/gui/pkg/display" - "forge.lthn.ai/core/gui/pkg/environment" - "forge.lthn.ai/core/gui/pkg/notification" - "forge.lthn.ai/core/gui/pkg/screen" - "forge.lthn.ai/core/gui/pkg/webview" - "forge.lthn.ai/core/gui/pkg/window" + "dappco.re/go/core/gui/pkg/clipboard" + "dappco.re/go/core/gui/pkg/display" + "dappco.re/go/core/gui/pkg/environment" + "dappco.re/go/core/gui/pkg/notification" + "dappco.re/go/core/gui/pkg/screen" + "dappco.re/go/core/gui/pkg/webview" + "dappco.re/go/core/gui/pkg/window" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/mcp/tools_browser.go b/pkg/mcp/tools_browser.go index 18f670c0..a14803ed 100644 --- a/pkg/mcp/tools_browser.go +++ b/pkg/mcp/tools_browser.go @@ -4,7 +4,7 @@ package mcp import ( "context" - "forge.lthn.ai/core/gui/pkg/browser" + "dappco.re/go/core/gui/pkg/browser" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/mcp/tools_clipboard.go b/pkg/mcp/tools_clipboard.go index 01e4b58f..f5732bbf 100644 --- a/pkg/mcp/tools_clipboard.go +++ b/pkg/mcp/tools_clipboard.go @@ -3,9 +3,9 @@ package mcp import ( "context" - "fmt" - "forge.lthn.ai/core/gui/pkg/clipboard" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/clipboard" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -23,7 +23,7 @@ func (s *Subsystem) clipboardRead(_ context.Context, _ *mcp.CallToolRequest, _ C } content, ok := result.(clipboard.ClipboardContent) if !ok { - return nil, ClipboardReadOutput{}, fmt.Errorf("unexpected result type from clipboard read query") + return nil, ClipboardReadOutput{}, corego.E("mcp.clipboard", "unexpected result type from clipboard read query", nil) } return nil, ClipboardReadOutput{Content: content.Text}, nil } @@ -44,7 +44,7 @@ func (s *Subsystem) clipboardWrite(_ context.Context, _ *mcp.CallToolRequest, in } success, ok := result.(bool) if !ok { - return nil, ClipboardWriteOutput{}, fmt.Errorf("unexpected result type from clipboard write task") + return nil, ClipboardWriteOutput{}, corego.E("mcp.clipboard", "unexpected result type from clipboard write task", nil) } return nil, ClipboardWriteOutput{Success: success}, nil } @@ -63,7 +63,7 @@ func (s *Subsystem) clipboardHas(_ context.Context, _ *mcp.CallToolRequest, _ Cl } content, ok := result.(clipboard.ClipboardContent) if !ok { - return nil, ClipboardHasOutput{}, fmt.Errorf("unexpected result type from clipboard has query") + return nil, ClipboardHasOutput{}, corego.E("mcp.clipboard", "unexpected result type from clipboard has query", nil) } return nil, ClipboardHasOutput{HasContent: content.HasContent}, nil } @@ -82,7 +82,7 @@ func (s *Subsystem) clipboardClear(_ context.Context, _ *mcp.CallToolRequest, _ } success, ok := result.(bool) if !ok { - return nil, ClipboardClearOutput{}, fmt.Errorf("unexpected result type from clipboard clear task") + return nil, ClipboardClearOutput{}, corego.E("mcp.clipboard", "unexpected result type from clipboard clear task", nil) } return nil, ClipboardClearOutput{Success: success}, nil } @@ -101,7 +101,7 @@ func (s *Subsystem) clipboardReadImage(_ context.Context, _ *mcp.CallToolRequest } image, ok := result.(clipboard.ClipboardImageContent) if !ok { - return nil, ClipboardReadImageOutput{}, fmt.Errorf("unexpected result type from clipboard image query") + return nil, ClipboardReadImageOutput{}, corego.E("mcp.clipboard", "unexpected result type from clipboard image query", nil) } return nil, ClipboardReadImageOutput{Image: image}, nil } diff --git a/pkg/mcp/tools_contextmenu.go b/pkg/mcp/tools_contextmenu.go index 74e9f8b5..c1112459 100644 --- a/pkg/mcp/tools_contextmenu.go +++ b/pkg/mcp/tools_contextmenu.go @@ -3,10 +3,9 @@ package mcp import ( "context" - "encoding/json" - "fmt" - "forge.lthn.ai/core/gui/pkg/contextmenu" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/contextmenu" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -25,13 +24,13 @@ type ContextMenuAddOutput struct { func (s *Subsystem) contextMenuAdd(_ context.Context, _ *mcp.CallToolRequest, input ContextMenuAddInput) (*mcp.CallToolResult, ContextMenuAddOutput, error) { // Convert map[string]any to ContextMenuDef via JSON round-trip - menuJSON, err := json.Marshal(input.Menu) - if err != nil { - return nil, ContextMenuAddOutput{}, fmt.Errorf("failed to marshal menu definition: %w", err) + r := corego.JSONMarshal(input.Menu) + if !r.OK { + return nil, ContextMenuAddOutput{}, corego.Wrap(r.Value.(error), "mcp.contextmenu", "failed to marshal menu definition") } var menuDef contextmenu.ContextMenuDef - if err := json.Unmarshal(menuJSON, &menuDef); err != nil { - return nil, ContextMenuAddOutput{}, fmt.Errorf("failed to unmarshal menu definition: %w", err) + if r2 := corego.JSONUnmarshal(r.Value.([]byte), &menuDef); !r2.OK { + return nil, ContextMenuAddOutput{}, corego.Wrap(r2.Value.(error), "mcp.contextmenu", "failed to unmarshal menu definition") } _, _, err = s.core.PERFORM(contextmenu.TaskAdd{Name: input.Name, Menu: menuDef}) if err != nil { @@ -73,19 +72,19 @@ func (s *Subsystem) contextMenuGet(_ context.Context, _ *mcp.CallToolRequest, in } menu, ok := result.(*contextmenu.ContextMenuDef) if !ok { - return nil, ContextMenuGetOutput{}, fmt.Errorf("unexpected result type from context menu get query") + return nil, ContextMenuGetOutput{}, corego.E("mcp.contextmenu", "unexpected result type from context menu get query", nil) } if menu == nil { return nil, ContextMenuGetOutput{}, nil } // Convert to map[string]any via JSON round-trip to avoid cyclic type in schema - menuJSON, err := json.Marshal(menu) - if err != nil { - return nil, ContextMenuGetOutput{}, fmt.Errorf("failed to marshal context menu: %w", err) + r := corego.JSONMarshal(menu) + if !r.OK { + return nil, ContextMenuGetOutput{}, corego.Wrap(r.Value.(error), "mcp.contextmenu", "failed to marshal context menu") } var menuMap map[string]any - if err := json.Unmarshal(menuJSON, &menuMap); err != nil { - return nil, ContextMenuGetOutput{}, fmt.Errorf("failed to unmarshal context menu: %w", err) + if r2 := corego.JSONUnmarshal(r.Value.([]byte), &menuMap); !r2.OK { + return nil, ContextMenuGetOutput{}, corego.Wrap(r2.Value.(error), "mcp.contextmenu", "failed to unmarshal context menu") } return nil, ContextMenuGetOutput{Menu: menuMap}, nil } @@ -104,16 +103,16 @@ func (s *Subsystem) contextMenuList(_ context.Context, _ *mcp.CallToolRequest, _ } menus, ok := result.(map[string]contextmenu.ContextMenuDef) if !ok { - return nil, ContextMenuListOutput{}, fmt.Errorf("unexpected result type from context menu list query") + return nil, ContextMenuListOutput{}, corego.E("mcp.contextmenu", "unexpected result type from context menu list query", nil) } // Convert to map[string]any via JSON round-trip to avoid cyclic type in schema - menusJSON, err := json.Marshal(menus) - if err != nil { - return nil, ContextMenuListOutput{}, fmt.Errorf("failed to marshal context menus: %w", err) + r := corego.JSONMarshal(menus) + if !r.OK { + return nil, ContextMenuListOutput{}, corego.Wrap(r.Value.(error), "mcp.contextmenu", "failed to marshal context menus") } var menusMap map[string]any - if err := json.Unmarshal(menusJSON, &menusMap); err != nil { - return nil, ContextMenuListOutput{}, fmt.Errorf("failed to unmarshal context menus: %w", err) + if r2 := corego.JSONUnmarshal(r.Value.([]byte), &menusMap); !r2.OK { + return nil, ContextMenuListOutput{}, corego.Wrap(r2.Value.(error), "mcp.contextmenu", "failed to unmarshal context menus") } return nil, ContextMenuListOutput{Menus: menusMap}, nil } diff --git a/pkg/mcp/tools_dialog.go b/pkg/mcp/tools_dialog.go index 06bdf668..a4d80b39 100644 --- a/pkg/mcp/tools_dialog.go +++ b/pkg/mcp/tools_dialog.go @@ -3,9 +3,9 @@ package mcp import ( "context" - "fmt" - "forge.lthn.ai/core/gui/pkg/dialog" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/dialog" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -33,7 +33,7 @@ func (s *Subsystem) dialogOpenFile(_ context.Context, _ *mcp.CallToolRequest, in } paths, ok := result.([]string) if !ok { - return nil, DialogOpenFileOutput{}, fmt.Errorf("unexpected result type from open file dialog") + return nil, DialogOpenFileOutput{}, corego.E("mcp.dialog", "unexpected result type from open file dialog", nil) } return nil, DialogOpenFileOutput{Paths: paths}, nil } @@ -62,7 +62,7 @@ func (s *Subsystem) dialogSaveFile(_ context.Context, _ *mcp.CallToolRequest, in } path, ok := result.(string) if !ok { - return nil, DialogSaveFileOutput{}, fmt.Errorf("unexpected result type from save file dialog") + return nil, DialogSaveFileOutput{}, corego.E("mcp.dialog", "unexpected result type from save file dialog", nil) } return nil, DialogSaveFileOutput{Path: path}, nil } @@ -87,7 +87,7 @@ func (s *Subsystem) dialogOpenDirectory(_ context.Context, _ *mcp.CallToolReques } path, ok := result.(string) if !ok { - return nil, DialogOpenDirectoryOutput{}, fmt.Errorf("unexpected result type from open directory dialog") + return nil, DialogOpenDirectoryOutput{}, corego.E("mcp.dialog", "unexpected result type from open directory dialog", nil) } return nil, DialogOpenDirectoryOutput{Path: path}, nil } @@ -115,7 +115,7 @@ func (s *Subsystem) dialogConfirm(_ context.Context, _ *mcp.CallToolRequest, inp } button, ok := result.(string) if !ok { - return nil, DialogConfirmOutput{}, fmt.Errorf("unexpected result type from confirm dialog") + return nil, DialogConfirmOutput{}, corego.E("mcp.dialog", "unexpected result type from confirm dialog", nil) } return nil, DialogConfirmOutput{Button: button}, nil } @@ -142,7 +142,7 @@ func (s *Subsystem) dialogPrompt(_ context.Context, _ *mcp.CallToolRequest, inpu } button, ok := result.(string) if !ok { - return nil, DialogPromptOutput{}, fmt.Errorf("unexpected result type from prompt dialog") + return nil, DialogPromptOutput{}, corego.E("mcp.dialog", "unexpected result type from prompt dialog", nil) } return nil, DialogPromptOutput{Button: button}, nil } diff --git a/pkg/mcp/tools_dock.go b/pkg/mcp/tools_dock.go index bff74fce..ef5c9c00 100644 --- a/pkg/mcp/tools_dock.go +++ b/pkg/mcp/tools_dock.go @@ -4,7 +4,7 @@ package mcp import ( "context" - "forge.lthn.ai/core/gui/pkg/dock" + "dappco.re/go/core/gui/pkg/dock" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/mcp/tools_environment.go b/pkg/mcp/tools_environment.go index 8853f473..09b7fcea 100644 --- a/pkg/mcp/tools_environment.go +++ b/pkg/mcp/tools_environment.go @@ -3,9 +3,9 @@ package mcp import ( "context" - "fmt" - "forge.lthn.ai/core/gui/pkg/environment" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/environment" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -23,7 +23,7 @@ func (s *Subsystem) themeGet(_ context.Context, _ *mcp.CallToolRequest, _ ThemeG } theme, ok := result.(environment.ThemeInfo) if !ok { - return nil, ThemeGetOutput{}, fmt.Errorf("unexpected result type from theme query") + return nil, ThemeGetOutput{}, corego.E("mcp.environment", "unexpected result type from theme query", nil) } return nil, ThemeGetOutput{Theme: theme}, nil } @@ -42,7 +42,7 @@ func (s *Subsystem) themeSystem(_ context.Context, _ *mcp.CallToolRequest, _ The } info, ok := result.(environment.EnvironmentInfo) if !ok { - return nil, ThemeSystemOutput{}, fmt.Errorf("unexpected result type from environment info query") + return nil, ThemeSystemOutput{}, corego.E("mcp.environment", "unexpected result type from environment info query", nil) } return nil, ThemeSystemOutput{Info: info}, nil } @@ -67,7 +67,7 @@ func (s *Subsystem) themeSet(_ context.Context, _ *mcp.CallToolRequest, input Th } theme, ok := result.(environment.ThemeInfo) if !ok { - return nil, ThemeSetOutput{}, fmt.Errorf("unexpected result type from theme query") + return nil, ThemeSetOutput{}, corego.E("mcp.environment", "unexpected result type from theme query", nil) } return nil, ThemeSetOutput{Theme: theme}, nil } diff --git a/pkg/mcp/tools_keybinding.go b/pkg/mcp/tools_keybinding.go index f8a5bf62..3ab84d85 100644 --- a/pkg/mcp/tools_keybinding.go +++ b/pkg/mcp/tools_keybinding.go @@ -4,7 +4,7 @@ package mcp import ( "context" - "forge.lthn.ai/core/gui/pkg/keybinding" + "dappco.re/go/core/gui/pkg/keybinding" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/mcp/tools_layout.go b/pkg/mcp/tools_layout.go index d53de75d..7942fd5b 100644 --- a/pkg/mcp/tools_layout.go +++ b/pkg/mcp/tools_layout.go @@ -3,11 +3,11 @@ package mcp import ( "context" - "fmt" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/screen" + "dappco.re/go/core/gui/pkg/window" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/screen" - "forge.lthn.ai/core/gui/pkg/window" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -59,7 +59,7 @@ func (s *Subsystem) layoutList(_ context.Context, _ *mcp.CallToolRequest, _ Layo } layouts, ok := result.([]window.LayoutInfo) if !ok { - return nil, LayoutListOutput{}, fmt.Errorf("unexpected result type from layout list query") + return nil, LayoutListOutput{}, corego.E("mcp.layout", "unexpected result type from layout list query", nil) } return nil, LayoutListOutput{Layouts: layouts}, nil } @@ -97,7 +97,7 @@ func (s *Subsystem) layoutGet(_ context.Context, _ *mcp.CallToolRequest, input L } layout, ok := result.(*window.Layout) if !ok { - return nil, LayoutGetOutput{}, fmt.Errorf("unexpected result type from layout get query") + return nil, LayoutGetOutput{}, corego.E("mcp.layout", "unexpected result type from layout get query", nil) } return nil, LayoutGetOutput{Layout: layout}, nil } @@ -176,7 +176,7 @@ func (s *Subsystem) layoutSuggest(_ context.Context, _ *mcp.CallToolRequest, inp } windows, ok := result.([]window.WindowInfo) if !ok { - return nil, LayoutSuggestOutput{}, fmt.Errorf("unexpected result type from window list query") + return nil, LayoutSuggestOutput{}, corego.E("mcp.layout", "unexpected result type from window list query", nil) } windowCount = len(windows) } @@ -193,11 +193,11 @@ func (s *Subsystem) layoutSuggest(_ context.Context, _ *mcp.CallToolRequest, inp return nil, LayoutSuggestOutput{}, err } if !handled { - return nil, LayoutSuggestOutput{}, fmt.Errorf("window service not available") + return nil, LayoutSuggestOutput{}, corego.E("mcp.layout", "window service not available", nil) } suggestion, ok := result.(window.LayoutSuggestion) if !ok { - return nil, LayoutSuggestOutput{}, fmt.Errorf("unexpected result type from layout suggestion query") + return nil, LayoutSuggestOutput{}, corego.E("mcp.layout", "unexpected result type from layout suggestion query", nil) } return nil, LayoutSuggestOutput{Suggestion: suggestion}, nil } @@ -227,11 +227,11 @@ func (s *Subsystem) screenFindSpace(_ context.Context, _ *mcp.CallToolRequest, i return nil, ScreenFindSpaceOutput{}, err } if !handled { - return nil, ScreenFindSpaceOutput{}, fmt.Errorf("window service not available") + return nil, ScreenFindSpaceOutput{}, corego.E("mcp.layout", "window service not available", nil) } space, ok := result.(window.SpaceInfo) if !ok { - return nil, ScreenFindSpaceOutput{}, fmt.Errorf("unexpected result type from find space query") + return nil, ScreenFindSpaceOutput{}, corego.E("mcp.layout", "unexpected result type from find space query", nil) } if space.ScreenWidth == 0 { space.ScreenWidth = screenW @@ -296,7 +296,7 @@ type LayoutWorkflowOutput struct { func (s *Subsystem) layoutWorkflow(_ context.Context, _ *mcp.CallToolRequest, input LayoutWorkflowInput) (*mcp.CallToolResult, LayoutWorkflowOutput, error) { workflow, ok := window.ParseWorkflowLayout(input.Workflow) if !ok { - return nil, LayoutWorkflowOutput{}, fmt.Errorf("unknown workflow: %s", input.Workflow) + return nil, LayoutWorkflowOutput{}, corego.E("mcp.layout", corego.Sprintf("unknown workflow: %s", input.Workflow), nil) } _, _, err := s.core.PERFORM(window.TaskApplyWorkflow{ Workflow: workflow, diff --git a/pkg/mcp/tools_lifecycle.go b/pkg/mcp/tools_lifecycle.go index 26715bdb..43dc62cb 100644 --- a/pkg/mcp/tools_lifecycle.go +++ b/pkg/mcp/tools_lifecycle.go @@ -4,7 +4,7 @@ package mcp import ( "context" - "forge.lthn.ai/core/gui/pkg/lifecycle" + "dappco.re/go/core/gui/pkg/lifecycle" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/mcp/tools_notification.go b/pkg/mcp/tools_notification.go index 5683efe6..f6c32c75 100644 --- a/pkg/mcp/tools_notification.go +++ b/pkg/mcp/tools_notification.go @@ -3,9 +3,9 @@ package mcp import ( "context" - "fmt" - "forge.lthn.ai/core/gui/pkg/notification" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/notification" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -71,7 +71,7 @@ func (s *Subsystem) notificationPermissionRequest(_ context.Context, _ *mcp.Call } granted, ok := result.(bool) if !ok { - return nil, NotificationPermissionRequestOutput{}, fmt.Errorf("unexpected result type from notification permission request") + return nil, NotificationPermissionRequestOutput{}, corego.E("mcp.notification", "unexpected result type from notification permission request", nil) } return nil, NotificationPermissionRequestOutput{Granted: granted}, nil } @@ -90,7 +90,7 @@ func (s *Subsystem) notificationPermissionCheck(_ context.Context, _ *mcp.CallTo } status, ok := result.(notification.PermissionStatus) if !ok { - return nil, NotificationPermissionCheckOutput{}, fmt.Errorf("unexpected result type from notification permission check") + return nil, NotificationPermissionCheckOutput{}, corego.E("mcp.notification", "unexpected result type from notification permission check", nil) } return nil, NotificationPermissionCheckOutput{Granted: status.Granted}, nil } diff --git a/pkg/mcp/tools_screen.go b/pkg/mcp/tools_screen.go index d8a06f73..5d076bf8 100644 --- a/pkg/mcp/tools_screen.go +++ b/pkg/mcp/tools_screen.go @@ -3,11 +3,11 @@ package mcp import ( "context" - "fmt" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/display" + "dappco.re/go/core/gui/pkg/screen" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/display" - "forge.lthn.ai/core/gui/pkg/screen" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -25,7 +25,7 @@ func (s *Subsystem) screenList(_ context.Context, _ *mcp.CallToolRequest, _ Scre } screens, ok := result.([]screen.Screen) if !ok { - return nil, ScreenListOutput{}, fmt.Errorf("unexpected result type from screen list query") + return nil, ScreenListOutput{}, corego.E("mcp.screen", "unexpected result type from screen list query", nil) } return nil, ScreenListOutput{Screens: screens}, nil } @@ -46,7 +46,7 @@ func (s *Subsystem) screenGet(_ context.Context, _ *mcp.CallToolRequest, input S } scr, ok := result.(*screen.Screen) if !ok { - return nil, ScreenGetOutput{}, fmt.Errorf("unexpected result type from screen get query") + return nil, ScreenGetOutput{}, corego.E("mcp.screen", "unexpected result type from screen get query", nil) } return nil, ScreenGetOutput{Screen: scr}, nil } @@ -65,7 +65,7 @@ func (s *Subsystem) screenPrimary(_ context.Context, _ *mcp.CallToolRequest, _ S } scr, ok := result.(*screen.Screen) if !ok { - return nil, ScreenPrimaryOutput{}, fmt.Errorf("unexpected result type from screen primary query") + return nil, ScreenPrimaryOutput{}, corego.E("mcp.screen", "unexpected result type from screen primary query", nil) } return nil, ScreenPrimaryOutput{Screen: scr}, nil } @@ -87,7 +87,7 @@ func (s *Subsystem) screenAtPoint(_ context.Context, _ *mcp.CallToolRequest, inp } scr, ok := result.(*screen.Screen) if !ok { - return nil, ScreenAtPointOutput{}, fmt.Errorf("unexpected result type from screen at point query") + return nil, ScreenAtPointOutput{}, corego.E("mcp.screen", "unexpected result type from screen at point query", nil) } return nil, ScreenAtPointOutput{Screen: scr}, nil } @@ -106,7 +106,7 @@ func (s *Subsystem) screenWorkAreas(_ context.Context, _ *mcp.CallToolRequest, _ } areas, ok := result.([]screen.Rect) if !ok { - return nil, ScreenWorkAreasOutput{}, fmt.Errorf("unexpected result type from screen work areas query") + return nil, ScreenWorkAreasOutput{}, corego.E("mcp.screen", "unexpected result type from screen work areas query", nil) } return nil, ScreenWorkAreasOutput{WorkAreas: areas}, nil } diff --git a/pkg/mcp/tools_tray.go b/pkg/mcp/tools_tray.go index f11f11d9..e0c95fa2 100644 --- a/pkg/mcp/tools_tray.go +++ b/pkg/mcp/tools_tray.go @@ -3,9 +3,9 @@ package mcp import ( "context" - "fmt" - "forge.lthn.ai/core/gui/pkg/systray" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/systray" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -74,7 +74,7 @@ func (s *Subsystem) trayInfo(_ context.Context, _ *mcp.CallToolRequest, _ TrayIn } config, ok := result.(map[string]any) if !ok { - return nil, TrayInfoOutput{}, fmt.Errorf("unexpected result type from tray config query") + return nil, TrayInfoOutput{}, corego.E("mcp.trayInfo", "unexpected result type from tray config query", nil) } return nil, TrayInfoOutput{Config: config}, nil } diff --git a/pkg/mcp/tools_webview.go b/pkg/mcp/tools_webview.go index 6da3dc3a..26b43611 100644 --- a/pkg/mcp/tools_webview.go +++ b/pkg/mcp/tools_webview.go @@ -3,9 +3,9 @@ package mcp import ( "context" - "fmt" - "forge.lthn.ai/core/gui/pkg/webview" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/webview" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -105,7 +105,7 @@ func (s *Subsystem) webviewScreenshot(_ context.Context, _ *mcp.CallToolRequest, } sr, ok := result.(webview.ScreenshotResult) if !ok { - return nil, WebviewScreenshotOutput{}, fmt.Errorf("unexpected result type from webview screenshot") + return nil, WebviewScreenshotOutput{}, corego.E("mcp.webview", "unexpected result type from webview screenshot", nil) } return nil, WebviewScreenshotOutput{Base64: sr.Base64, MimeType: sr.MimeType}, nil } @@ -129,7 +129,7 @@ func (s *Subsystem) webviewScreenshotElement(_ context.Context, _ *mcp.CallToolR } sr, ok := result.(webview.ScreenshotResult) if !ok { - return nil, WebviewScreenshotElementOutput{}, fmt.Errorf("unexpected result type from webview element screenshot") + return nil, WebviewScreenshotElementOutput{}, corego.E("mcp.webview", "unexpected result type from webview element screenshot", nil) } return nil, WebviewScreenshotElementOutput{Base64: sr.Base64, MimeType: sr.MimeType}, nil } @@ -272,7 +272,7 @@ func (s *Subsystem) webviewConsole(_ context.Context, _ *mcp.CallToolRequest, in } msgs, ok := result.([]webview.ConsoleMessage) if !ok { - return nil, WebviewConsoleOutput{}, fmt.Errorf("unexpected result type from webview console query") + return nil, WebviewConsoleOutput{}, corego.E("mcp.webview", "unexpected result type from webview console query", nil) } return nil, WebviewConsoleOutput{Messages: msgs}, nil } @@ -313,7 +313,7 @@ func (s *Subsystem) webviewErrors(_ context.Context, _ *mcp.CallToolRequest, inp } errors, ok := result.([]webview.ExceptionInfo) if !ok { - return nil, WebviewErrorsOutput{}, fmt.Errorf("unexpected result type from webview errors query") + return nil, WebviewErrorsOutput{}, corego.E("mcp.webview", "unexpected result type from webview errors query", nil) } return nil, WebviewErrorsOutput{Errors: errors}, nil } @@ -370,7 +370,7 @@ func (s *Subsystem) webviewQuery(_ context.Context, _ *mcp.CallToolRequest, inpu } el, ok := result.(*webview.ElementInfo) if !ok { - return nil, WebviewQueryOutput{}, fmt.Errorf("unexpected result type from webview query") + return nil, WebviewQueryOutput{}, corego.E("mcp.webview", "unexpected result type from webview query", nil) } return nil, WebviewQueryOutput{Element: el}, nil } @@ -399,7 +399,7 @@ func (s *Subsystem) webviewQueryAll(_ context.Context, _ *mcp.CallToolRequest, i } els, ok := result.([]*webview.ElementInfo) if !ok { - return nil, WebviewQueryAllOutput{}, fmt.Errorf("unexpected result type from webview query all") + return nil, WebviewQueryAllOutput{}, corego.E("mcp.webview", "unexpected result type from webview query all", nil) } return nil, WebviewQueryAllOutput{Elements: els}, nil } @@ -422,7 +422,7 @@ func (s *Subsystem) webviewDOMTree(_ context.Context, _ *mcp.CallToolRequest, in } html, ok := result.(string) if !ok { - return nil, WebviewDOMTreeOutput{}, fmt.Errorf("unexpected result type from webview DOM tree query") + return nil, WebviewDOMTreeOutput{}, corego.E("mcp.webview", "unexpected result type from webview DOM tree query", nil) } return nil, WebviewDOMTreeOutput{HTML: html}, nil } @@ -451,7 +451,7 @@ func (s *Subsystem) webviewComputedStyle(_ context.Context, _ *mcp.CallToolReque } style, ok := result.(map[string]string) if !ok { - return nil, WebviewComputedStyleOutput{}, fmt.Errorf("unexpected result type from webview computed style query") + return nil, WebviewComputedStyleOutput{}, corego.E("mcp.webview", "unexpected result type from webview computed style query", nil) } return nil, WebviewComputedStyleOutput{Style: style}, nil } @@ -473,7 +473,7 @@ func (s *Subsystem) webviewPerformance(_ context.Context, _ *mcp.CallToolRequest } metrics, ok := result.(webview.PerformanceMetrics) if !ok { - return nil, WebviewPerformanceOutput{}, fmt.Errorf("unexpected result type from webview performance query") + return nil, WebviewPerformanceOutput{}, corego.E("mcp.webview", "unexpected result type from webview performance query", nil) } return nil, WebviewPerformanceOutput{Metrics: metrics}, nil } @@ -495,7 +495,7 @@ func (s *Subsystem) webviewResources(_ context.Context, _ *mcp.CallToolRequest, } resources, ok := result.([]webview.ResourceEntry) if !ok { - return nil, WebviewResourcesOutput{}, fmt.Errorf("unexpected result type from webview resources query") + return nil, WebviewResourcesOutput{}, corego.E("mcp.webview", "unexpected result type from webview resources query", nil) } return nil, WebviewResourcesOutput{Resources: resources}, nil } @@ -518,7 +518,7 @@ func (s *Subsystem) webviewNetwork(_ context.Context, _ *mcp.CallToolRequest, in } requests, ok := result.([]webview.NetworkEntry) if !ok { - return nil, WebviewNetworkOutput{}, fmt.Errorf("unexpected result type from webview network query") + return nil, WebviewNetworkOutput{}, corego.E("mcp.webview", "unexpected result type from webview network query", nil) } return nil, WebviewNetworkOutput{Requests: requests}, nil } @@ -615,7 +615,7 @@ func (s *Subsystem) webviewPDF(_ context.Context, _ *mcp.CallToolRequest, input } pdf, ok := result.(webview.PDFResult) if !ok { - return nil, WebviewPDFOutput{}, fmt.Errorf("unexpected result type from webview pdf task") + return nil, WebviewPDFOutput{}, corego.E("mcp.webview", "unexpected result type from webview pdf task", nil) } return nil, WebviewPDFOutput{Base64: pdf.Base64, MimeType: pdf.MimeType}, nil } @@ -637,7 +637,7 @@ func (s *Subsystem) webviewURL(_ context.Context, _ *mcp.CallToolRequest, input } url, ok := result.(string) if !ok { - return nil, WebviewURLOutput{}, fmt.Errorf("unexpected result type from webview URL query") + return nil, WebviewURLOutput{}, corego.E("mcp.webview", "unexpected result type from webview URL query", nil) } return nil, WebviewURLOutput{URL: url}, nil } @@ -659,7 +659,7 @@ func (s *Subsystem) webviewTitle(_ context.Context, _ *mcp.CallToolRequest, inpu } title, ok := result.(string) if !ok { - return nil, WebviewTitleOutput{}, fmt.Errorf("unexpected result type from webview title query") + return nil, WebviewTitleOutput{}, corego.E("mcp.webview", "unexpected result type from webview title query", nil) } return nil, WebviewTitleOutput{Title: title}, nil } diff --git a/pkg/mcp/tools_window.go b/pkg/mcp/tools_window.go index a602a1f1..748eaadb 100644 --- a/pkg/mcp/tools_window.go +++ b/pkg/mcp/tools_window.go @@ -3,9 +3,9 @@ package mcp import ( "context" - "fmt" - "forge.lthn.ai/core/gui/pkg/window" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/window" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -23,7 +23,7 @@ func (s *Subsystem) windowList(_ context.Context, _ *mcp.CallToolRequest, _ Wind } windows, ok := result.([]window.WindowInfo) if !ok { - return nil, WindowListOutput{}, fmt.Errorf("unexpected result type from window list query") + return nil, WindowListOutput{}, corego.E("mcp.window", "unexpected result type from window list query", nil) } return nil, WindowListOutput{Windows: windows}, nil } @@ -44,7 +44,7 @@ func (s *Subsystem) windowGet(_ context.Context, _ *mcp.CallToolRequest, input W } info, ok := result.(*window.WindowInfo) if !ok { - return nil, WindowGetOutput{}, fmt.Errorf("unexpected result type from window get query") + return nil, WindowGetOutput{}, corego.E("mcp.window", "unexpected result type from window get query", nil) } return nil, WindowGetOutput{Window: info}, nil } @@ -63,7 +63,7 @@ func (s *Subsystem) windowFocused(_ context.Context, _ *mcp.CallToolRequest, _ W } windows, ok := result.([]window.WindowInfo) if !ok { - return nil, WindowFocusedOutput{}, fmt.Errorf("unexpected result type from window list query") + return nil, WindowFocusedOutput{}, corego.E("mcp.window", "unexpected result type from window list query", nil) } for _, w := range windows { if w.Focused { @@ -105,7 +105,7 @@ func (s *Subsystem) windowCreate(_ context.Context, _ *mcp.CallToolRequest, inpu } info, ok := result.(window.WindowInfo) if !ok { - return nil, WindowCreateOutput{}, fmt.Errorf("unexpected result type from window create task") + return nil, WindowCreateOutput{}, corego.E("mcp.window", "unexpected result type from window create task", nil) } return nil, WindowCreateOutput{Window: info}, nil } diff --git a/pkg/notification/service.go b/pkg/notification/service.go index 4941c50b..b15afe5d 100644 --- a/pkg/notification/service.go +++ b/pkg/notification/service.go @@ -3,11 +3,11 @@ package notification import ( "context" - "fmt" "time" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/dialog" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/dialog" ) // Options configures the notification service. @@ -76,7 +76,7 @@ func (s *Service) handleTask(c *core.Core, t core.Task) (any, bool, error) { func (s *Service) sendNotification(opts NotificationOptions) error { // Generate an ID when the caller does not provide one. if opts.ID == "" { - opts.ID = fmt.Sprintf("core-%d", time.Now().UnixNano()) + opts.ID = corego.Sprintf("core-%d", time.Now().UnixNano()) } if len(opts.Actions) > 0 { diff --git a/pkg/notification/service_test.go b/pkg/notification/service_test.go index cd51ab60..5cab891d 100644 --- a/pkg/notification/service_test.go +++ b/pkg/notification/service_test.go @@ -7,7 +7,7 @@ import ( "testing" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/dialog" + "dappco.re/go/core/gui/pkg/dialog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/systray/service.go b/pkg/systray/service.go index 47d71f21..a690c58d 100644 --- a/pkg/systray/service.go +++ b/pkg/systray/service.go @@ -5,7 +5,7 @@ import ( "context" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/notification" + "dappco.re/go/core/gui/pkg/notification" ) // Options configures the systray service. diff --git a/pkg/webview/diagnostics.go b/pkg/webview/diagnostics.go index 55c01137..2330df72 100644 --- a/pkg/webview/diagnostics.go +++ b/pkg/webview/diagnostics.go @@ -2,19 +2,16 @@ package webview import ( - "encoding/json" - "fmt" - "strings" + corego "dappco.re/go/core" ) func jsQuote(v string) string { - b, _ := json.Marshal(v) - return string(b) + return corego.JSONMarshalString(v) } func computedStyleScript(selector string) string { sel := jsQuote(selector) - return fmt.Sprintf(`(function(){ + return corego.Sprintf(`(function(){ const el = document.querySelector(%s); if (!el) return null; const style = window.getComputedStyle(el); @@ -33,7 +30,7 @@ func highlightScript(selector, colour string) string { colour = "#ff9800" } col := jsQuote(colour) - return fmt.Sprintf(`(function(){ + return corego.Sprintf(`(function(){ const el = document.querySelector(%s); if (!el) return false; if (el.__coreHighlightOrigOutline === undefined) { @@ -150,9 +147,9 @@ func networkLogScript(limit int) string { if limit <= 0 { return `(window.__coreNetworkLog || [])` } - return fmt.Sprintf(`(window.__coreNetworkLog || []).slice(-%d)`, limit) + return corego.Sprintf(`(window.__coreNetworkLog || []).slice(-%d)`, limit) } func normalizeWhitespace(s string) string { - return strings.TrimSpace(s) + return corego.Trim(s) } diff --git a/pkg/webview/service.go b/pkg/webview/service.go index 2ddc593c..0e8bb4d6 100644 --- a/pkg/webview/service.go +++ b/pkg/webview/service.go @@ -5,22 +5,20 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" - "fmt" "image" "image/draw" "image/png" "math" "reflect" "strconv" - "strings" "sync" "time" "unsafe" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/window" gowebview "forge.lthn.ai/core/go-webview" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/window" ) // connector abstracts go-webview for testing. The real implementation wraps @@ -108,7 +106,7 @@ func defaultNewConn(opts Options) func(string, string) (connector, error) { } var wsURL string for _, t := range targets { - if t.Type == "page" && (strings.Contains(t.Title, windowName) || strings.Contains(t.URL, windowName)) { + if t.Type == "page" && (corego.Contains(t.Title, windowName) || corego.Contains(t.URL, windowName)) { wsURL = t.WebSocketDebuggerURL break } @@ -473,7 +471,7 @@ func (s *Service) handleTask(_ *core.Core, t core.Task) (any, bool, error) { } pw, ok := ws.Manager().Get(t.Window) if !ok { - return nil, true, fmt.Errorf("window not found: %s", t.Window) + return nil, true, corego.E("webview", corego.Sprintf("window not found: %s", t.Window), nil) } pw.OpenDevTools() return nil, true, nil @@ -484,7 +482,7 @@ func (s *Service) handleTask(_ *core.Core, t core.Task) (any, bool, error) { } pw, ok := ws.Manager().Get(t.Window) if !ok { - return nil, true, fmt.Errorf("window not found: %s", t.Window) + return nil, true, corego.E("webview", corego.Sprintf("window not found: %s", t.Window), nil) } pw.CloseDevTools() return nil, true, nil @@ -550,12 +548,13 @@ func (s *Service) queryExceptions(windowName string, limit int) []ExceptionInfo func coerceJSON[T any](v any) (T, error) { var out T - raw, err := json.Marshal(v) - if err != nil { - return out, err + r := corego.JSONMarshal(v) + if !r.OK { + return out, r.Value.(error) } - if err := json.Unmarshal(raw, &out); err != nil { - return out, err + r2 := corego.JSONUnmarshal(r.Value.([]byte), &out) + if !r2.OK { + return out, r2.Value.(error) } return out, nil } @@ -586,7 +585,7 @@ type elementScreenshotBounds struct { func elementScreenshotScript(selector string) string { sel := jsQuote(selector) - return fmt.Sprintf(`(function(){ + return corego.Sprintf(`(function(){ const el = document.querySelector(%s); if (!el) return null; try { el.scrollIntoView({block: "center", inline: "center"}); } catch (e) {} @@ -607,14 +606,14 @@ func captureElementScreenshot(conn connector, selector string) ([]byte, error) { return nil, err } if result == nil { - return nil, fmt.Errorf("webview: element not found: %s", selector) + return nil, corego.E("webview", corego.Sprintf("element not found: %s", selector), nil) } bounds, err := coerceJSON[elementScreenshotBounds](result) if err != nil { return nil, err } if bounds.Width <= 0 || bounds.Height <= 0 { - return nil, fmt.Errorf("webview: element has no measurable bounds: %s", selector) + return nil, corego.E("webview", corego.Sprintf("element has no measurable bounds: %s", selector), nil) } raw, err := conn.Screenshot() if err != nil { @@ -648,7 +647,7 @@ func captureElementScreenshot(conn connector, selector string) ([]byte, error) { bottom = srcBounds.Max.Y } if right <= left || bottom <= top { - return nil, fmt.Errorf("webview: element is outside the captured screenshot: %s", selector) + return nil, corego.E("webview", corego.Sprintf("element is outside the captured screenshot: %s", selector), nil) } crop := image.NewRGBA(image.Rect(0, 0, right-left, bottom-top)) @@ -695,7 +694,7 @@ func (r *realConnector) PrintToPDF() ([]byte, error) { } data, ok := result["data"].(string) if !ok || data == "" { - return nil, fmt.Errorf("webview: missing PDF data") + return nil, corego.E("webview", "missing PDF data", nil) } return base64.StdEncoding.DecodeString(data) } @@ -703,17 +702,17 @@ func (r *realConnector) PrintToPDF() ([]byte, error) { func (r *realConnector) cdpClient() (*gowebview.CDPClient, error) { rv := reflect.ValueOf(r.wv) if rv.Kind() != reflect.Ptr || rv.IsNil() { - return nil, fmt.Errorf("webview: invalid connector") + return nil, corego.E("webview", "invalid connector", nil) } elem := rv.Elem() field := elem.FieldByName("client") if !field.IsValid() || field.IsNil() { - return nil, fmt.Errorf("webview: CDP client not available") + return nil, corego.E("webview", "CDP client not available", nil) } ptr := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() client, ok := ptr.(*gowebview.CDPClient) if !ok || client == nil { - return nil, fmt.Errorf("webview: unexpected CDP client type") + return nil, corego.E("webview", "unexpected CDP client type", nil) } return client, nil } diff --git a/pkg/webview/service_test.go b/pkg/webview/service_test.go index f3338333..23e93f78 100644 --- a/pkg/webview/service_test.go +++ b/pkg/webview/service_test.go @@ -12,7 +12,7 @@ import ( "testing" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/window" + "dappco.re/go/core/gui/pkg/window" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/window/layout.go b/pkg/window/layout.go index 9d3e1235..ca1f74ea 100644 --- a/pkg/window/layout.go +++ b/pkg/window/layout.go @@ -2,12 +2,11 @@ package window import ( - "encoding/json" - "fmt" "os" - "path/filepath" "sync" "time" + + corego "dappco.re/go/core" ) // Layout is a named window arrangement. @@ -44,7 +43,7 @@ func NewLayoutManager() *LayoutManager { } configDir, err := os.UserConfigDir() if err == nil { - lm.configDir = filepath.Join(configDir, "Core") + lm.configDir = corego.JoinPath(configDir, "Core") } lm.loadLayouts() return lm @@ -63,7 +62,7 @@ func NewLayoutManagerWithDir(configDir string) *LayoutManager { } func (lm *LayoutManager) layoutsFilePath() string { - return filepath.Join(lm.configDir, "layouts.json") + return corego.JoinPath(lm.configDir, "layouts.json") } func (lm *LayoutManager) loadLayouts() { @@ -76,7 +75,7 @@ func (lm *LayoutManager) loadLayouts() { } lm.mu.Lock() defer lm.mu.Unlock() - _ = json.Unmarshal(data, &lm.layouts) + _ = corego.JSONUnmarshal(data, &lm.layouts) } func (lm *LayoutManager) saveLayouts() { @@ -84,20 +83,20 @@ func (lm *LayoutManager) saveLayouts() { return } lm.mu.RLock() - data, err := json.MarshalIndent(lm.layouts, "", " ") + r := corego.JSONMarshal(lm.layouts) lm.mu.RUnlock() - if err != nil { + if !r.OK { return } _ = os.MkdirAll(lm.configDir, 0o755) - _ = os.WriteFile(lm.layoutsFilePath(), data, 0o644) + _ = os.WriteFile(lm.layoutsFilePath(), r.Value.([]byte), 0o644) } // SaveLayout creates or updates a named layout. // Use: _ = lm.SaveLayout("coding", windowStates) func (lm *LayoutManager) SaveLayout(name string, windowStates map[string]WindowState) error { if name == "" { - return fmt.Errorf("layout name cannot be empty") + return corego.E("layout.save", "layout name cannot be empty", nil) } now := time.Now().UnixMilli() lm.mu.Lock() diff --git a/pkg/window/service.go b/pkg/window/service.go index b6dbe1df..f41019f0 100644 --- a/pkg/window/service.go +++ b/pkg/window/service.go @@ -3,11 +3,10 @@ package window import ( "context" - "fmt" - "strings" + corego "dappco.re/go/core" + "dappco.re/go/core/gui/pkg/screen" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/screen" ) // Options holds configuration for the window service. @@ -280,7 +279,7 @@ func (s *Service) trackWindow(pw PlatformWindow) { func (s *Service) taskCloseWindow(name string) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } // Persist state BEFORE closing (spec requirement) s.manager.State().CaptureState(pw) @@ -292,7 +291,7 @@ func (s *Service) taskCloseWindow(name string) error { func (s *Service) taskSetPosition(name string, x, y int) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } pw.SetPosition(x, y) s.manager.State().UpdatePosition(name, x, y) @@ -302,7 +301,7 @@ func (s *Service) taskSetPosition(name string, x, y int) error { func (s *Service) taskSetSize(name string, width, height, fallbackWidth, fallbackHeight int) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } if width == 0 && height == 0 { width, height = fallbackWidth, fallbackHeight @@ -322,7 +321,7 @@ func (s *Service) taskSetSize(name string, width, height, fallbackWidth, fallbac func (s *Service) taskMaximise(name string) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } pw.Maximise() s.manager.State().UpdateMaximized(name, true) @@ -332,7 +331,7 @@ func (s *Service) taskMaximise(name string) error { func (s *Service) taskMinimise(name string) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } pw.Minimise() return nil @@ -341,7 +340,7 @@ func (s *Service) taskMinimise(name string) error { func (s *Service) taskFocus(name string) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } pw.Focus() return nil @@ -350,7 +349,7 @@ func (s *Service) taskFocus(name string) error { func (s *Service) taskRestore(name string) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } pw.Restore() s.manager.State().UpdateMaximized(name, false) @@ -360,7 +359,7 @@ func (s *Service) taskRestore(name string) error { func (s *Service) taskSetTitle(name, title string) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } pw.SetTitle(title) return nil @@ -369,7 +368,7 @@ func (s *Service) taskSetTitle(name, title string) error { func (s *Service) taskSetAlwaysOnTop(name string, alwaysOnTop bool) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } pw.SetAlwaysOnTop(alwaysOnTop) return nil @@ -378,7 +377,7 @@ func (s *Service) taskSetAlwaysOnTop(name string, alwaysOnTop bool) error { func (s *Service) taskSetBackgroundColour(name string, red, green, blue, alpha uint8) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } pw.SetBackgroundColour(red, green, blue, alpha) return nil @@ -386,11 +385,11 @@ func (s *Service) taskSetBackgroundColour(name string, red, green, blue, alpha u func (s *Service) taskSetOpacity(name string, opacity float32) error { if opacity < 0 || opacity > 1 { - return fmt.Errorf("opacity must be between 0 and 1") + return corego.E("window.setOpacity", "opacity must be between 0 and 1", nil) } pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } pw.SetOpacity(opacity) return nil @@ -399,7 +398,7 @@ func (s *Service) taskSetOpacity(name string, opacity float32) error { func (s *Service) taskSetVisibility(name string, visible bool) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } pw.SetVisibility(visible) return nil @@ -408,7 +407,7 @@ func (s *Service) taskSetVisibility(name string, visible bool) error { func (s *Service) taskFullscreen(name string, fullscreen bool) error { pw, ok := s.manager.Get(name) if !ok { - return fmt.Errorf("window not found: %s", name) + return corego.E("window.service", corego.Sprintf("window not found: %s", name), nil) } if fullscreen { pw.Fullscreen() @@ -433,7 +432,7 @@ func (s *Service) taskSaveLayout(name string) error { func (s *Service) taskRestoreLayout(name string) error { layout, ok := s.manager.Layout().GetLayout(name) if !ok { - return fmt.Errorf("layout not found: %s", name) + return corego.E("window.restoreLayout", corego.Sprintf("layout not found: %s", name), nil) } for winName, state := range layout.Windows { pw, found := s.manager.Get(winName) @@ -465,7 +464,7 @@ var tileModeMap = map[string]TileMode{ func (s *Service) taskTileWindows(mode string, names []string) error { tm, ok := tileModeMap[mode] if !ok { - return fmt.Errorf("unknown tile mode: %s", mode) + return corego.E("window.tileWindows", corego.Sprintf("unknown tile mode: %s", mode), nil) } if len(names) == 0 { names = s.manager.List() @@ -485,7 +484,7 @@ var snapPosMap = map[string]SnapPosition{ func (s *Service) taskSnapWindow(name, position string) error { pos, ok := snapPosMap[position] if !ok { - return fmt.Errorf("unknown snap position: %s", position) + return corego.E("window.snapWindow", corego.Sprintf("unknown snap position: %s", position), nil) } screenW, screenH := s.primaryScreenSize() return s.manager.SnapWindow(name, pos, screenW, screenH) @@ -502,13 +501,13 @@ func (s *Service) taskBesideEditor(editorName, windowName string) error { editorName = s.detectEditorWindow() } if editorName == "" { - return fmt.Errorf("editor window not found") + return corego.E("window.besideEditor", "editor window not found", nil) } if windowName == "" { windowName = s.detectCompanionWindow(editorName) } if windowName == "" { - return fmt.Errorf("companion window not found") + return corego.E("window.besideEditor", "companion window not found", nil) } return s.manager.BesideEditor(editorName, windowName, screenW, screenH) } @@ -554,9 +553,9 @@ func looksLikeEditor(name, title string) bool { } func containsAny(value string, needles ...string) bool { - lower := strings.ToLower(value) + lower := corego.Lower(value) for _, needle := range needles { - if strings.Contains(lower, needle) { + if corego.Contains(lower, needle) { return true } } diff --git a/pkg/window/service_test.go b/pkg/window/service_test.go index 73de842e..36b10d8a 100644 --- a/pkg/window/service_test.go +++ b/pkg/window/service_test.go @@ -6,7 +6,7 @@ import ( "testing" "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/gui/pkg/screen" + "dappco.re/go/core/gui/pkg/screen" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/window/state.go b/pkg/window/state.go index f222f24d..08df4d4b 100644 --- a/pkg/window/state.go +++ b/pkg/window/state.go @@ -2,11 +2,11 @@ package window import ( - "encoding/json" "os" - "path/filepath" "sync" "time" + + corego "dappco.re/go/core" ) // WindowState holds the persisted position/size of a window. @@ -41,7 +41,7 @@ func NewStateManager() *StateManager { } configDir, err := os.UserConfigDir() if err == nil { - sm.configDir = filepath.Join(configDir, "Core") + sm.configDir = corego.JoinPath(configDir, "Core") } sm.load() return sm @@ -63,12 +63,12 @@ func (sm *StateManager) filePath() string { if sm.statePath != "" { return sm.statePath } - return filepath.Join(sm.configDir, "window_state.json") + return corego.JoinPath(sm.configDir, "window_state.json") } func (sm *StateManager) dataDir() string { if sm.statePath != "" { - return filepath.Dir(sm.statePath) + return corego.PathDir(sm.statePath) } return sm.configDir } @@ -100,7 +100,7 @@ func (sm *StateManager) load() { } sm.mu.Lock() defer sm.mu.Unlock() - _ = json.Unmarshal(data, &sm.states) + _ = corego.JSONUnmarshal(data, &sm.states) } func (sm *StateManager) save() { @@ -108,11 +108,12 @@ func (sm *StateManager) save() { return } sm.mu.RLock() - data, err := json.MarshalIndent(sm.states, "", " ") + r := corego.JSONMarshal(sm.states) sm.mu.RUnlock() - if err != nil { + if !r.OK { return } + data := r.Value.([]byte) _ = os.MkdirAll(sm.dataDir(), 0o755) _ = os.WriteFile(sm.filePath(), data, 0o644) } diff --git a/pkg/window/tiling.go b/pkg/window/tiling.go index caa845c6..186c868b 100644 --- a/pkg/window/tiling.go +++ b/pkg/window/tiling.go @@ -1,7 +1,7 @@ // pkg/window/tiling.go package window -import "fmt" +import corego "dappco.re/go/core" // normalizeWindowForLayout clears transient maximise/minimise state before // applying a new geometry. This keeps layout helpers effective even when a @@ -97,12 +97,12 @@ func (m *Manager) TileWindows(mode TileMode, names []string, screenW, screenH in for _, name := range names { pw, ok := m.Get(name) if !ok { - return fmt.Errorf("window %q not found", name) + return corego.E("window.tiling", corego.Sprintf("window %q not found", name), nil) } windows = append(windows, pw) } if len(windows) == 0 { - return fmt.Errorf("no windows to tile") + return corego.E("window.tiling", "no windows to tile", nil) } halfW, halfH := screenW/2, screenH/2 @@ -186,7 +186,7 @@ func (m *Manager) TileWindows(mode TileMode, names []string, screenW, screenH in func (m *Manager) SnapWindow(name string, pos SnapPosition, screenW, screenH int) error { pw, ok := m.Get(name) if !ok { - return fmt.Errorf("window %q not found", name) + return corego.E("window.tiling", corego.Sprintf("window %q not found", name), nil) } halfW, halfH := screenW/2, screenH/2 @@ -237,7 +237,7 @@ func (m *Manager) StackWindows(names []string, offsetX, offsetY int) error { for i, name := range names { pw, ok := m.Get(name) if !ok { - return fmt.Errorf("window %q not found", name) + return corego.E("window.tiling", corego.Sprintf("window %q not found", name), nil) } normalizeWindowForLayout(pw) pw.SetPosition(i*offsetX, i*offsetY) @@ -248,7 +248,7 @@ func (m *Manager) StackWindows(names []string, offsetX, offsetY int) error { // ApplyWorkflow arranges windows in a predefined workflow layout. func (m *Manager) ApplyWorkflow(workflow WorkflowLayout, names []string, screenW, screenH int) error { if len(names) == 0 { - return fmt.Errorf("no windows for workflow") + return corego.E("window.tiling", "no windows for workflow", nil) } switch workflow { diff --git a/pkg/window/window.go b/pkg/window/window.go index 65896463..3c26b43d 100644 --- a/pkg/window/window.go +++ b/pkg/window/window.go @@ -2,9 +2,10 @@ package window import ( - "fmt" "math" "sync" + + corego "dappco.re/go/core" ) // Window is CoreGUI's own window descriptor — NOT a Wails type alias. @@ -94,7 +95,7 @@ func (m *Manager) SetDefaultHeight(height int) { func (m *Manager) Open(opts ...WindowOption) (PlatformWindow, error) { w, err := ApplyOptions(opts...) if err != nil { - return nil, fmt.Errorf("window.Manager.Open: %w", err) + return nil, corego.Wrap(err, "window.Manager.Open", "failed to apply options") } return m.Create(w) } @@ -288,11 +289,11 @@ func (m *Manager) FindSpace(screenW, screenH, width, height int) SpaceInfo { func (m *Manager) ArrangePair(first, second string, screenW, screenH int) error { left, ok := m.Get(first) if !ok { - return fmt.Errorf("window %q not found", first) + return corego.E("window.ArrangePair", corego.Sprintf("window %q not found", first), nil) } right, ok := m.Get(second) if !ok { - return fmt.Errorf("window %q not found", second) + return corego.E("window.ArrangePair", corego.Sprintf("window %q not found", second), nil) } leftW := screenW / 2 @@ -309,11 +310,11 @@ func (m *Manager) ArrangePair(first, second string, screenW, screenH int) error func (m *Manager) BesideEditor(editorName, windowName string, screenW, screenH int) error { editor, ok := m.Get(editorName) if !ok { - return fmt.Errorf("window %q not found", editorName) + return corego.E("window.BesideEditor", corego.Sprintf("window %q not found", editorName), nil) } target, ok := m.Get(windowName) if !ok { - return fmt.Errorf("window %q not found", windowName) + return corego.E("window.BesideEditor", corego.Sprintf("window %q not found", windowName), nil) } editorW := screenW * 70 / 100