diff --git a/docs/ref/wails-v3/src/application/application.go b/docs/ref/wails-v3/src/application/application.go index 32a0d58f..bf18877b 100644 --- a/docs/ref/wails-v3/src/application/application.go +++ b/docs/ref/wails-v3/src/application/application.go @@ -282,15 +282,15 @@ type windowKeyEvent struct { acceleratorString string } -func (r *webViewAssetRequest) URL() (string, error) { +func (r *webViewAssetRequest) URL() (string, resultFailure) { return r.Request.URL() } -func (r *webViewAssetRequest) Method() (string, error) { +func (r *webViewAssetRequest) Method() (string, resultFailure) { return r.Request.Method() } -func (r *webViewAssetRequest) Header() (http.Header, error) { +func (r *webViewAssetRequest) Header() (http.Header, resultFailure) { h, err := r.Request.Header() if err != nil { return nil, err @@ -304,7 +304,7 @@ func (r *webViewAssetRequest) Header() (http.Header, error) { return hh, nil } -func (r *webViewAssetRequest) Body() (io.ReadCloser, error) { +func (r *webViewAssetRequest) Body() (io.ReadCloser, resultFailure) { return r.Request.Body() } @@ -312,7 +312,7 @@ func (r *webViewAssetRequest) Response() webview.ResponseWriter { return r.Request.Response() } -func (r *webViewAssetRequest) Close() error { +func (r *webViewAssetRequest) Close() resultFailure { return r.Request.Close() } @@ -433,7 +433,7 @@ func (a *App) handleWarning(msg string) { } } -func (a *App) handleError(err error) { +func (a *App) handleError(err resultFailure) { if a.options.ErrorHandler != nil { a.options.ErrorHandler(err) } else { @@ -463,7 +463,7 @@ func (a *App) RegisterService(service Service) { a.options.Services = append(a.options.Services, service) } -func (a *App) handleFatalError(err error) { +func (a *App) handleFatalError(err resultFailure) { a.handleError(&FatalError{err: err}) core.Exit(1) } @@ -533,7 +533,7 @@ func (a *App) error(message string, args ...any) { a.handleError(core.Errorf(message, args...)) } -func (a *App) Run() error { +func (a *App) Run() resultFailure { a.runLock.Lock() // Prevent double invocations. if a.starting || a.running { @@ -643,7 +643,7 @@ func (a *App) Run() error { return a.impl.run() } -func (a *App) startupService(service Service) error { +func (a *App) startupService(service Service) resultFailure { err := a.bindings.Add(service) if err != nil { return core.Errorf("cannot bind service methods: %w", err) diff --git a/docs/ref/wails-v3/src/application/browser_manager.go b/docs/ref/wails-v3/src/application/browser_manager.go index 3fe81720..d8f59a84 100644 --- a/docs/ref/wails-v3/src/application/browser_manager.go +++ b/docs/ref/wails-v3/src/application/browser_manager.go @@ -17,11 +17,11 @@ func newBrowserManager(app *App) *BrowserManager { } // OpenURL opens a URL in the default browser -func (bm *BrowserManager) OpenURL(url string) error { +func (bm *BrowserManager) OpenURL(url string) resultFailure { return browser.OpenURL(url) } // OpenFile opens a file in the default browser -func (bm *BrowserManager) OpenFile(path string) error { +func (bm *BrowserManager) OpenFile(path string) resultFailure { return browser.OpenFile(path) } diff --git a/docs/ref/wails-v3/src/application/core_helpers.go b/docs/ref/wails-v3/src/application/core_helpers.go index a11e5139..d6720fdd 100644 --- a/docs/ref/wails-v3/src/application/core_helpers.go +++ b/docs/ref/wails-v3/src/application/core_helpers.go @@ -2,7 +2,7 @@ package application import core "dappco.re/go" -func jsonMarshal(value any) ([]byte, error) { +func jsonMarshal(value any) ([]byte, resultFailure) { result := core.JSONMarshal(value) if !result.OK { if err, ok := result.Value.(error); ok { @@ -13,7 +13,7 @@ func jsonMarshal(value any) ([]byte, error) { return result.Value.([]byte), nil } -func jsonUnmarshal(data []byte, target any) error { +func jsonUnmarshal(data []byte, target any) resultFailure { result := core.JSONUnmarshal(data, target) if result.OK { return nil diff --git a/docs/ref/wails-v3/src/application/environment_manager.go b/docs/ref/wails-v3/src/application/environment_manager.go index f9e40093..ca2bbad2 100644 --- a/docs/ref/wails-v3/src/application/environment_manager.go +++ b/docs/ref/wails-v3/src/application/environment_manager.go @@ -49,7 +49,7 @@ func (em *EnvironmentManager) GetAccentColor() string { } // OpenFileManager opens the file manager at the specified path, optionally selecting the file -func (em *EnvironmentManager) OpenFileManager(path string, selectFile bool) error { +func (em *EnvironmentManager) OpenFileManager(path string, selectFile bool) resultFailure { return InvokeSyncWithError(func() error { return fileexplorer.OpenFileManager(path, selectFile) }) diff --git a/docs/ref/wails-v3/src/application/events.go b/docs/ref/wails-v3/src/application/events.go index 7657378b..30f72ac0 100644 --- a/docs/ref/wails-v3/src/application/events.go +++ b/docs/ref/wails-v3/src/application/events.go @@ -132,7 +132,7 @@ func (e *EventProcessor) Once(eventName string, callback func(event *CustomEvent // If the event is globally registered, it validates associated data // against the expected data type. In case of mismatches, // it cancels the event and returns an error. -func (e *EventProcessor) Emit(thisEvent *CustomEvent) error { +func (e *EventProcessor) Emit(thisEvent *CustomEvent) resultFailure { if thisEvent == nil { return nil } @@ -305,7 +305,7 @@ func RegisterEvent[Data any](name string) { eventRegistered(name) } -func validateCustomEvent(event *CustomEvent) error { +func validateCustomEvent(event *CustomEvent) resultFailure { r, ok := registeredEvents.Load(event.Name) if !ok { warnAboutUnregisteredEvent(event.Name) @@ -336,7 +336,7 @@ func validateCustomEvent(event *CustomEvent) error { ) } -func decodeEventData(name string, data []byte) (result any, err error) { +func decodeEventData(name string, data []byte) (result any, err resultFailure) { r, ok := registeredEvents.Load(name) if !ok { // Unregistered events unmarshal to any. diff --git a/docs/ref/wails-v3/src/application/result_failure.go b/docs/ref/wails-v3/src/application/result_failure.go new file mode 100644 index 00000000..b9f8dd16 --- /dev/null +++ b/docs/ref/wails-v3/src/application/result_failure.go @@ -0,0 +1,5 @@ +package application + +type resultFailure = interface { + Error() string +} diff --git a/docs/ref/wails-v3/src/application/screenmanager.go b/docs/ref/wails-v3/src/application/screenmanager.go index 9a782dea..d9aa7431 100644 --- a/docs/ref/wails-v3/src/application/screenmanager.go +++ b/docs/ref/wails-v3/src/application/screenmanager.go @@ -368,7 +368,7 @@ func (s *Screen) physicalToDipRect(physicalRect Rect) Rect { // Layout screens in the virtual space with DIP calculations and cache the screens // for future coordinate transformation between the physical and logical (DIP) space -func (m *ScreenManager) LayoutScreens(screens []*Screen) error { +func (m *ScreenManager) LayoutScreens(screens []*Screen) resultFailure { if screens == nil || len(screens) == 0 { return core.NewError("screens parameter is nil or empty") } @@ -391,7 +391,7 @@ func (m *ScreenManager) GetPrimary() *Screen { } // Reference: https://source.chromium.org/chromium/chromium/src/+/main:ui/display/win/screen_win.cc;l=317 -func (m *ScreenManager) calculateScreensDipCoordinates() error { +func (m *ScreenManager) calculateScreensDipCoordinates() resultFailure { remainingScreens := []*Screen{} // Find the primary screen diff --git a/docs/ref/wails-v3/src/application/systemtray.go b/docs/ref/wails-v3/src/application/systemtray.go index 6cf29ced..44cf1ab4 100644 --- a/docs/ref/wails-v3/src/application/systemtray.go +++ b/docs/ref/wails-v3/src/application/systemtray.go @@ -186,7 +186,7 @@ func (s *SystemTray) HideWindow() { s.attachedWindow.Window.Hide() } -func (s *SystemTray) PositionWindow(window Window, offset int) error { +func (s *SystemTray) PositionWindow(window Window, offset int) resultFailure { if s.impl == nil { return core.NewError("system tray not running") } diff --git a/docs/ref/wails-v3/src/application/webview_window.go b/docs/ref/wails-v3/src/application/webview_window.go index 97e3801e..86aaa074 100644 --- a/docs/ref/wails-v3/src/application/webview_window.go +++ b/docs/ref/wails-v3/src/application/webview_window.go @@ -766,7 +766,7 @@ func (w *WebviewWindow) HandleMessage(message string) { } } -func (w *WebviewWindow) startResize(border string) error { +func (w *WebviewWindow) startResize(border string) resultFailure { if w.impl == nil || w.isDestroyed() { return nil } @@ -1193,7 +1193,7 @@ func (w *WebviewWindow) EnableSizeConstraints() { } // GetScreen returns the screen that the window is on -func (w *WebviewWindow) GetScreen() (*Screen, error) { +func (w *WebviewWindow) GetScreen() (*Screen, resultFailure) { if w.impl == nil || w.isDestroyed() { return nil, nil } @@ -1317,14 +1317,14 @@ func (w *WebviewWindow) emit(eventType events.WindowEventType) { } } -func (w *WebviewWindow) startDrag() error { +func (w *WebviewWindow) startDrag() resultFailure { if w.impl == nil || w.isDestroyed() { return nil } return InvokeSyncWithError(w.impl.startDrag) } -func (w *WebviewWindow) Print() error { +func (w *WebviewWindow) Print() resultFailure { if w.impl == nil || w.isDestroyed() { return nil } diff --git a/go.work b/go.work index 9beffe88..7fe8bcee 100644 --- a/go.work +++ b/go.work @@ -6,7 +6,7 @@ go 1.26.2 use ( ./go ./external/go - ./external/config + ./external/config/go ./external/webview ./external/io ./external/log diff --git a/go/pkg/browser/platform.go b/go/pkg/browser/platform.go index 4d291229..cfe7a97b 100644 --- a/go/pkg/browser/platform.go +++ b/go/pkg/browser/platform.go @@ -4,8 +4,8 @@ package browser // Platform abstracts the system browser/file-opener backend. type Platform interface { // OpenURL opens the given URL in the default system browser. - OpenURL(url string) error + OpenURL(url string) resultFailure // OpenFile opens the given file path with the system default application. - OpenFile(path string) error + OpenFile(path string) resultFailure } diff --git a/go/pkg/browser/result_failure.go b/go/pkg/browser/result_failure.go new file mode 100644 index 00000000..b5447b20 --- /dev/null +++ b/go/pkg/browser/result_failure.go @@ -0,0 +1,5 @@ +package browser + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/browser/service.go b/go/pkg/browser/service.go index 7ac34c1f..4b2b28c5 100644 --- a/go/pkg/browser/service.go +++ b/go/pkg/browser/service.go @@ -46,7 +46,7 @@ func (s *Service) HandleIPCEvents(_ *core.Core, _ core.Message) core.Result { return core.Result{OK: true} } -func validatedOpenURL(raw string) (string, error) { +func validatedOpenURL(raw string) (string, resultFailure) { trimmed := core.Trim(raw) if trimmed == "" { return "", core.E("browser.openURL", "url is required", nil) @@ -67,7 +67,7 @@ func validatedOpenURL(raw string) (string, error) { return parsed.String(), nil } -func validatedOpenFilePath(raw string) (string, error) { +func validatedOpenFilePath(raw string) (string, resultFailure) { trimmed := core.Trim(raw) if trimmed == "" { return "", core.E("browser.openFile", "path is required", nil) diff --git a/go/pkg/browser/service_test.go b/go/pkg/browser/service_test.go index ddef42c4..e5a52450 100644 --- a/go/pkg/browser/service_test.go +++ b/go/pkg/browser/service_test.go @@ -9,16 +9,16 @@ import ( type mockPlatform struct { lastURL string lastPath string - urlErr error - fileErr error + urlErr resultFailure + fileErr resultFailure } -func (m *mockPlatform) OpenURL(url string) error { +func (m *mockPlatform) OpenURL(url string) resultFailure { m.lastURL = url return m.urlErr } -func (m *mockPlatform) OpenFile(path string) error { +func (m *mockPlatform) OpenFile(path string) resultFailure { m.lastPath = path return m.fileErr } diff --git a/go/pkg/chat/chat.go b/go/pkg/chat/chat.go index 346cc87e..6b0226e9 100644 --- a/go/pkg/chat/chat.go +++ b/go/pkg/chat/chat.go @@ -88,7 +88,7 @@ func NewStreamRenderer(callbacks StreamCallbacks) *StreamRenderer { } } -func (r *StreamRenderer) Render(reader io.Reader) error { +func (r *StreamRenderer) Render(reader io.Reader) resultFailure { scanner := bufio.NewScanner(reader) scanner.Buffer(make([]byte, 0, 4096), 1024*1024) @@ -133,7 +133,7 @@ func (r *StreamRenderer) Render(reader io.Reader) error { return nil } -func (r *StreamRenderer) handleData(payload string) error { +func (r *StreamRenderer) handleData(payload string) resultFailure { if payload == "" { return nil } diff --git a/go/pkg/chat/core_helpers.go b/go/pkg/chat/core_helpers.go index c4e6cca8..6ea7e644 100644 --- a/go/pkg/chat/core_helpers.go +++ b/go/pkg/chat/core_helpers.go @@ -2,7 +2,7 @@ package chat import core "dappco.re/go" -func coreResultError(result core.Result, fallback string) error { +func coreResultError(result core.Result, fallback string) resultFailure { if result.OK { return nil } @@ -15,7 +15,7 @@ func coreResultError(result core.Result, fallback string) error { return core.NewError(fallback) } -func coreReadFile(path string) ([]byte, error) { +func coreReadFile(path string) ([]byte, resultFailure) { result := core.ReadFile(path) if !result.OK { return nil, coreResultError(result, "failed to read file") @@ -23,19 +23,19 @@ func coreReadFile(path string) ([]byte, error) { return result.Value.([]byte), nil } -func coreWriteFile(path string, data []byte, mode core.FileMode) error { +func coreWriteFile(path string, data []byte, mode core.FileMode) resultFailure { return coreResultError(core.WriteFile(path, data, mode), "failed to write file") } -func coreWriteMode(path, content string, mode core.FileMode) error { +func coreWriteMode(path, content string, mode core.FileMode) resultFailure { return coreWriteFile(path, []byte(content), mode) } -func coreEnsureDir(path string) error { +func coreEnsureDir(path string) resultFailure { return coreResultError(core.MkdirAll(path, 0o755), "failed to create directory") } -func coreMkdirTemp(dir, pattern string) (string, error) { +func coreMkdirTemp(dir, pattern string) (string, resultFailure) { result := core.MkdirTemp(dir, pattern) if !result.OK { return "", coreResultError(result, "failed to create temporary directory") @@ -43,7 +43,7 @@ func coreMkdirTemp(dir, pattern string) (string, error) { return result.Value.(string), nil } -func coreRemoveAll(path string) error { +func coreRemoveAll(path string) resultFailure { return coreResultError(core.RemoveAll(path), "failed to remove path") } diff --git a/go/pkg/chat/result_failure.go b/go/pkg/chat/result_failure.go new file mode 100644 index 00000000..a51a9cf8 --- /dev/null +++ b/go/pkg/chat/result_failure.go @@ -0,0 +1,5 @@ +package chat + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/chat/service.go b/go/pkg/chat/service.go index b39309fd..16fea912 100644 --- a/go/pkg/chat/service.go +++ b/go/pkg/chat/service.go @@ -42,15 +42,15 @@ type Options struct { } type contract interface { - Send(context.Context, sendInput) (string, error) - History(string, int) ([]Message, error) + Send(context.Context, sendInput) (string, resultFailure) + History(string, int) ([]Message, resultFailure) Models() []ModelEntry - SelectModel(selectModelInput) (ChatSettings, error) - ListConversations() ([]Conversation, error) - LoadConversation(string) (Conversation, error) - DeleteConversation(string) error - StartThinking(thinkingInput) (ThinkingState, error) - StopThinking(thinkingInput) (ThinkingState, error) + SelectModel(selectModelInput) (ChatSettings, resultFailure) + ListConversations() ([]Conversation, resultFailure) + LoadConversation(string) (Conversation, resultFailure) + DeleteConversation(string) resultFailure + StartThinking(thinkingInput) (ThinkingState, resultFailure) + StopThinking(thinkingInput) (ThinkingState, resultFailure) } var _ contract = (*Service)(nil) @@ -225,7 +225,7 @@ func (s *Service) OnStartup(_ context.Context) core.Result { subsystem := guimcp.New(s.Core()) server := sdkmcp.NewServer(&sdkmcp.Implementation{Name: "coregui-chat", Version: "0.1.0"}, nil) subsystem.RegisterTools(server) - s.toolExecutor = subsystem + s.toolExecutor = adapter{subsystem: subsystem} } registerMCPToolActions(s.Core(), s.toolExecutor) s.toolExecutor = newActionToolExecutor(s.Core(), s.toolExecutor) @@ -452,7 +452,7 @@ func (s *Service) registerActions() { c.Action("gui.chat.thinking.end", stopThinking) } -func decodeInput[T any](opts core.Options) (T, error) { +func decodeInput[T any](opts core.Options) (T, resultFailure) { var input T if task := opts.Get("task"); task.OK { if typed, ok := task.Value.(T); ok { @@ -470,7 +470,7 @@ func decodeInput[T any](opts core.Options) (T, error) { result := core.JSONUnmarshalString(core.JSONMarshalString(items), &input) if !result.OK { - if err, ok := result.Value.(error); ok { + if err, ok := result.Value.(resultFailure); ok { return input, err } return input, core.E("chat.decodeInput", "failed to decode action input", nil) @@ -493,11 +493,11 @@ func (s *Service) now() time.Time { return time.Now() } -func (s *Service) Send(ctx context.Context, input sendInput) (string, error) { +func (s *Service) Send(ctx context.Context, input sendInput) (string, resultFailure) { return s.send(ctx, input) } -func (s *Service) History(conversationID string, limit int) ([]Message, error) { +func (s *Service) History(conversationID string, limit int) ([]Message, resultFailure) { if limit < 0 { return nil, core.E("chat.history", "limit must be greater than or equal to zero", nil) } @@ -538,7 +538,7 @@ func (s *Service) Models() []ModelEntry { return models } -func (s *Service) saveSettings(settings ChatSettings) error { +func (s *Service) saveSettings(settings ChatSettings) resultFailure { if err := s.validateSettings(settings); err != nil { return err } @@ -561,7 +561,7 @@ func (s *Service) loadSettings() ChatSettings { return settings } -func (s *Service) SelectModel(input selectModelInput) (ChatSettings, error) { +func (s *Service) SelectModel(input selectModelInput) (ChatSettings, resultFailure) { modelName := coalesce(input.Name, input.Model) if err := s.validateModelName(modelName); err != nil { return ChatSettings{}, err @@ -590,19 +590,19 @@ func (s *Service) SelectModel(input selectModelInput) (ChatSettings, error) { return settings, nil } -func (s *Service) ListConversations() ([]Conversation, error) { +func (s *Service) ListConversations() ([]Conversation, resultFailure) { return s.listConversations() } -func (s *Service) LoadConversation(id string) (Conversation, error) { +func (s *Service) LoadConversation(id string) (Conversation, resultFailure) { return s.getConversation(id, "") } -func (s *Service) DeleteConversation(id string) error { +func (s *Service) DeleteConversation(id string) resultFailure { return s.deleteConversation(id) } -func (s *Service) StartThinking(input thinkingInput) (ThinkingState, error) { +func (s *Service) StartThinking(input thinkingInput) (ThinkingState, resultFailure) { if core.Trim(input.ConversationID) == "" { return ThinkingState{}, core.E("chat.thinking.start", "conversation id is required", nil) } @@ -627,7 +627,7 @@ func (s *Service) StartThinking(input thinkingInput) (ThinkingState, error) { return state, nil } -func (s *Service) StopThinking(input thinkingInput) (ThinkingState, error) { +func (s *Service) StopThinking(input thinkingInput) (ThinkingState, resultFailure) { if core.Trim(input.ConversationID) == "" { return ThinkingState{}, core.E("chat.thinking.stop", "conversation id is required", nil) } @@ -681,7 +681,7 @@ func (s *Service) appendThinking(conversationID, content string) { s.thinkingStates[key] = state } -func (s *Service) saveConversation(conv Conversation) (Conversation, error) { +func (s *Service) saveConversation(conv Conversation) (Conversation, resultFailure) { if err := s.validateConversation(conv); err != nil { return Conversation{}, err } @@ -700,7 +700,7 @@ func (s *Service) saveConversation(conv Conversation) (Conversation, error) { return conv, s.store.set(conversationsGroup, conv.ID, payload) } -func (s *Service) loadConversation(id string) (Conversation, error) { +func (s *Service) loadConversation(id string) (Conversation, resultFailure) { payload, err := s.store.get(conversationsGroup, id) if err != nil { return Conversation{}, err @@ -708,7 +708,7 @@ func (s *Service) loadConversation(id string) (Conversation, error) { var conv Conversation result := core.JSONUnmarshalString(payload, &conv) if !result.OK { - if decodeErr, ok := result.Value.(error); ok { + if decodeErr, ok := result.Value.(resultFailure); ok { return Conversation{}, decodeErr } return Conversation{}, core.E("chat.loadConversation", "failed to decode conversation", nil) @@ -716,7 +716,7 @@ func (s *Service) loadConversation(id string) (Conversation, error) { return conv, nil } -func (s *Service) listConversations() ([]Conversation, error) { +func (s *Service) listConversations() ([]Conversation, resultFailure) { if s.store == nil { return nil, nil } @@ -738,7 +738,7 @@ func (s *Service) listConversations() ([]Conversation, error) { return conversations, nil } -func (s *Service) listConversationSummaries() ([]ConversationSummary, error) { +func (s *Service) listConversationSummaries() ([]ConversationSummary, resultFailure) { conversations, err := s.listConversations() if err != nil { return nil, err @@ -754,7 +754,7 @@ func (s *Service) listConversationSummaries() ([]ConversationSummary, error) { return summaries, nil } -func (s *Service) searchConversationSummaries(query string) ([]ConversationSummary, error) { +func (s *Service) searchConversationSummaries(query string) ([]ConversationSummary, resultFailure) { query = core.Trim(core.Lower(query)) summaries, err := s.listConversationSummaries() if err != nil || query == "" { @@ -826,7 +826,7 @@ func conversationSearchText(conv Conversation) string { return builder.String() } -func (s *Service) createConversation() (Conversation, error) { +func (s *Service) createConversation() (Conversation, resultFailure) { settings := s.loadSettings() now := s.now() conv := Conversation{ @@ -845,7 +845,7 @@ func (s *Service) createConversation() (Conversation, error) { return conv, nil } -func (s *Service) getConversation(id, conversationID string) (Conversation, error) { +func (s *Service) getConversation(id, conversationID string) (Conversation, resultFailure) { target := coalesce(id, conversationID) if target == "" { return Conversation{}, core.E("chat.getConversation", "conversation id is required", nil) @@ -853,7 +853,7 @@ func (s *Service) getConversation(id, conversationID string) (Conversation, erro return s.loadConversation(target) } -func (s *Service) renameConversation(id, title string) (Conversation, error) { +func (s *Service) renameConversation(id, title string) (Conversation, resultFailure) { conv, err := s.loadConversation(id) if err != nil { return Conversation{}, err @@ -870,7 +870,7 @@ func (s *Service) renameConversation(id, title string) (Conversation, error) { return conv, nil } -func (s *Service) clearConversation(id, conversationID string) (Conversation, error) { +func (s *Service) clearConversation(id, conversationID string) (Conversation, resultFailure) { conv, err := s.getConversation(id, conversationID) if err != nil { return Conversation{}, err @@ -887,7 +887,7 @@ func (s *Service) clearConversation(id, conversationID string) (Conversation, er return conv, nil } -func (s *Service) deleteConversation(id string) error { +func (s *Service) deleteConversation(id string) resultFailure { if id == "" { return core.E("chat.deleteConversation", "conversation id is required", nil) } @@ -899,7 +899,7 @@ func (s *Service) deleteConversation(id string) error { return nil } -func (s *Service) exportConversation(id string) (string, error) { +func (s *Service) exportConversation(id string) (string, resultFailure) { conv, err := s.loadConversation(id) if err != nil { return "", err @@ -956,7 +956,7 @@ func (s *Service) drainAttachments(conversationID string) []ImageAttachment { // removeAttachment removes a queued image by index from the pending attachment queue. // Use: removed, _ := service.removeAttachment("draft", 0) -func (s *Service) removeAttachment(conversationID string, index int) (ImageAttachment, error) { +func (s *Service) removeAttachment(conversationID string, index int) (ImageAttachment, resultFailure) { key := coalesce(conversationID, "draft") if index < 0 { return ImageAttachment{}, core.E("chat.removeAttachment", "attachment index must be non-negative", nil) @@ -1018,7 +1018,7 @@ func (s *Service) mergedSettings(global ChatSettings, override *ChatSettings) Ch return merged } -func (s *Service) send(ctx context.Context, input sendInput) (string, error) { +func (s *Service) send(ctx context.Context, input sendInput) (string, resultFailure) { if core.Trim(input.Content) == "" && !s.hasPendingAttachments(input.ConversationID) { return "", core.E("chat.send", "message content is required", nil) } @@ -1026,7 +1026,7 @@ func (s *Service) send(ctx context.Context, input sendInput) (string, error) { settings := s.loadSettings() var ( conv Conversation - err error + err resultFailure created bool lastAssistantMessageID string ) @@ -1150,7 +1150,7 @@ func (s *Service) hasPendingAttachments(conversationID string) bool { return false } -func (s *Service) streamAssistant(ctx context.Context, conv Conversation, settings ChatSettings) (ChatMessage, error) { +func (s *Service) streamAssistant(ctx context.Context, conv Conversation, settings ChatSettings) (ChatMessage, resultFailure) { messageID := "msg-" + strconv.FormatInt(s.now().UnixNano(), 36) requestBody := s.buildCompletionRequest(conv, settings) payload := core.JSONMarshalString(requestBody) @@ -1445,7 +1445,7 @@ func (s *Service) discoverModels() []ModelEntry { return results } -func (s *Service) validateSettings(settings ChatSettings) error { +func (s *Service) validateSettings(settings ChatSettings) resultFailure { if settings.Temperature < 0 || settings.Temperature > 2 { return core.E("chat.settings.save", "temperature must be between 0.0 and 2.0", nil) } @@ -1476,7 +1476,7 @@ func validContextWindow(value int) bool { } } -func (s *Service) validateConversation(conv Conversation) error { +func (s *Service) validateConversation(conv Conversation) resultFailure { if core.Trim(conv.ID) == "" { return core.E("chat.saveConversation", "conversation id is required", nil) } @@ -1499,7 +1499,7 @@ func (s *Service) validateConversation(conv Conversation) error { return nil } -func (s *Service) validateModelName(name string) error { +func (s *Service) validateModelName(name string) resultFailure { if core.Trim(name) == "" { return core.E("chat.selectModel", "model is required", nil) } @@ -1512,7 +1512,7 @@ func (s *Service) validateModelName(name string) error { return core.E("chat.selectModel", "model is not available: "+name, nil) } -func (s *Service) validateOptionalModelName(name string) error { +func (s *Service) validateOptionalModelName(name string) resultFailure { if core.Trim(name) == "" { return nil } @@ -1534,7 +1534,7 @@ func (s *Service) findModel(name string) (ModelEntry, bool) { return ModelEntry{}, false } -func (s *Service) validateAttachmentsForModel(modelName string, attachments []ImageAttachment) error { +func (s *Service) validateAttachmentsForModel(modelName string, attachments []ImageAttachment) resultFailure { if len(attachments) == 0 { return nil } @@ -1548,7 +1548,7 @@ func (s *Service) validateAttachmentsForModel(modelName string, attachments []Im return nil } -func validateMessageAttachments(message ChatMessage) error { +func validateMessageAttachments(message ChatMessage) resultFailure { for _, attachment := range message.Attachments { if err := validateImageAttachment(attachment); err != nil { return err @@ -1618,7 +1618,7 @@ func readModelConfig(modelPath string) (modelConfig, bool) { return config, true } -func validateImageAttachment(attachment ImageAttachment) error { +func validateImageAttachment(attachment ImageAttachment) resultFailure { if core.Trim(attachment.Filename) == "" { return core.E("chat.attachImage", "attachment filename is required", nil) } @@ -1632,7 +1632,7 @@ func validateImageAttachment(attachment ImageAttachment) error { return nil } -func imageAttachmentFromFile(rawPath string) (ImageAttachment, error) { +func imageAttachmentFromFile(rawPath string) (ImageAttachment, resultFailure) { path, err := validatedImageFilePath(rawPath) if err != nil { return ImageAttachment{}, err @@ -1660,7 +1660,7 @@ func imageAttachmentFromFile(rawPath string) (ImageAttachment, error) { return attachment, nil } -func validatedImageFilePath(rawPath string) (string, error) { +func validatedImageFilePath(rawPath string) (string, resultFailure) { trimmed := core.Trim(rawPath) if trimmed == "" { return "", core.E("chat.attachImageFile", "path is required", nil) @@ -1675,7 +1675,7 @@ func validatedImageFilePath(rawPath string) (string, error) { return cleaned, nil } -func detectImageMimeType(path string, data []byte) (string, error) { +func detectImageMimeType(path string, data []byte) (string, resultFailure) { mimeType := core.Lower(core.Trim(http.DetectContentType(data))) if _, ok := supportedImageMimeTypes[mimeType]; ok { return mimeType, nil diff --git a/go/pkg/chat/service_test.go b/go/pkg/chat/service_test.go index a15a9fcf..7bb8d442 100644 --- a/go/pkg/chat/service_test.go +++ b/go/pkg/chat/service_test.go @@ -27,7 +27,7 @@ func (m *mockToolExecutor) ManifestText() string { return "Available MCP tools:\n- layout_suggest: Suggest a layout" } -func (m *mockToolExecutor) CallTool(_ context.Context, name string, arguments map[string]any) (string, error) { +func (m *mockToolExecutor) CallTool(_ context.Context, name string, arguments map[string]any) (string, resultFailure) { m.calls = append(m.calls, ToolCall{Name: name, Arguments: arguments}) return `{"mode":"left-right"}`, nil } @@ -141,8 +141,8 @@ func TestActionSend_Bad_RejectsEmptyMessage(t *core.T) { result := c.Action("gui.chat.send").Run(context.Background(), core.NewOptions()) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "message content is required") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "message content is required") } func TestActionSend_Ugly_PropagatesUpstreamFailure(t *core.T) { @@ -154,8 +154,8 @@ func TestActionSend_Ugly_PropagatesUpstreamFailure(t *core.T) { core.Option{Key: "content", Value: "Hi"}, )) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "model unavailable") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "model unavailable") } func TestActionHistory_Good_HonoursLimit(t *core.T) { @@ -186,8 +186,8 @@ func TestActionHistory_Bad_RequiresConversationID(t *core.T) { result := c.Action("gui.chat.history").Run(context.Background(), core.NewOptions()) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "conversation id is required") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "conversation id is required") } func TestActionHistory_Ugly_UnknownConversationFails(t *core.T) { @@ -199,7 +199,7 @@ func TestActionHistory_Ugly_UnknownConversationFails(t *core.T) { core.Option{Key: "conversation_id", Value: "missing"}, )) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) + core.AssertError(t, result.Value.(resultFailure)) } func TestActionModels_Good_ReportsSizeAndStatus(t *core.T) { @@ -293,8 +293,8 @@ func TestActionSelectModel_Bad_RequiresModelName(t *core.T) { result := c.Action("gui.chat.selectModel").Run(context.Background(), core.NewOptions()) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "model is required") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "model is required") } func TestActionSelectModel_Ugly_RejectsUnknownDiscoveredModel(t *core.T) { @@ -307,8 +307,8 @@ func TestActionSelectModel_Ugly_RejectsUnknownDiscoveredModel(t *core.T) { core.Option{Key: "model", Value: "missing"}, )) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "model is not available") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "model is not available") } func TestActionConversationsList_Good_ReturnsNewestFirst(t *core.T) { @@ -401,8 +401,8 @@ func TestActionConversationsLoad_Bad_RequiresConversationID(t *core.T) { result := c.Action("gui.chat.conversations.load").Run(context.Background(), core.NewOptions()) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "conversation id is required") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "conversation id is required") } func TestActionConversationsLoad_Ugly_UnknownConversationFails(t *core.T) { @@ -414,7 +414,7 @@ func TestActionConversationsLoad_Ugly_UnknownConversationFails(t *core.T) { core.Option{Key: "conversation_id", Value: "missing"}, )) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) + core.AssertError(t, result.Value.(resultFailure)) } func TestActionConversationsDelete_Good_RemovesConversation(t *core.T) { @@ -449,8 +449,8 @@ func TestActionConversationsDelete_Bad_RequiresConversationID(t *core.T) { result := c.Action("gui.chat.conversations.delete").Run(context.Background(), core.NewOptions()) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "conversation id is required") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "conversation id is required") } func TestActionConversationsDelete_Ugly_IsIdempotentForMissingConversation(t *core.T) { @@ -486,8 +486,8 @@ func TestActionThinkingStart_Bad_RequiresConversationID(t *core.T) { result := c.Action("gui.chat.thinking.start").Run(context.Background(), core.NewOptions()) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "conversation id is required") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "conversation id is required") } func TestActionThinkingStart_Ugly_RestartReplacesExistingState(t *core.T) { @@ -534,8 +534,8 @@ func TestActionThinkingStop_Bad_RequiresConversationID(t *core.T) { result := c.Action("gui.chat.thinking.stop").Run(context.Background(), core.NewOptions()) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "conversation id is required") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "conversation id is required") } func TestActionThinkingStop_Ugly_AllowsStopWithoutStart(t *core.T) { diff --git a/go/pkg/chat/store.go b/go/pkg/chat/store.go index 8c6e6a52..d858c470 100644 --- a/go/pkg/chat/store.go +++ b/go/pkg/chat/store.go @@ -12,7 +12,7 @@ type chatStore struct { data map[string]map[string]string } -func newChatStore(path string) (*chatStore, error) { +func newChatStore(path string) (*chatStore, resultFailure) { store := &chatStore{ path: path, data: make(map[string]map[string]string), @@ -39,7 +39,7 @@ func newChatStore(path string) (*chatStore, error) { return store, nil } -func (s *chatStore) set(group, key, value string) error { +func (s *chatStore) set(group, key, value string) resultFailure { if s == nil { return core.NewError("chat store is nil") } @@ -57,7 +57,7 @@ func (s *chatStore) set(group, key, value string) error { return s.persistLocked() } -func (s *chatStore) get(group, key string) (string, error) { +func (s *chatStore) get(group, key string) (string, resultFailure) { if s == nil { return "", core.NewError("chat store is nil") } @@ -69,7 +69,7 @@ func (s *chatStore) get(group, key string) (string, error) { return "", core.NewError("not found") } -func (s *chatStore) getAll(group string) (map[string]string, error) { +func (s *chatStore) getAll(group string) (map[string]string, resultFailure) { if s == nil { return nil, core.NewError("chat store is nil") } @@ -82,7 +82,7 @@ func (s *chatStore) getAll(group string) (map[string]string, error) { return copy, nil } -func (s *chatStore) delete(group, key string) error { +func (s *chatStore) delete(group, key string) resultFailure { if s == nil { return core.NewError("chat store is nil") } @@ -97,7 +97,7 @@ func (s *chatStore) delete(group, key string) error { return s.persistLocked() } -func (s *chatStore) persistLocked() error { +func (s *chatStore) persistLocked() resultFailure { if s.path == "" || s.path == ":memory:" { return nil } diff --git a/go/pkg/chat/tool_handler.go b/go/pkg/chat/tool_handler.go index dca4542b..2daba522 100644 --- a/go/pkg/chat/tool_handler.go +++ b/go/pkg/chat/tool_handler.go @@ -14,16 +14,32 @@ const mcpToolActionPrefix = "mcp.tool." type ToolExecutor interface { Manifest() []guimcp.ToolDescriptor ManifestText() string - CallTool(ctx context.Context, name string, arguments map[string]any) (string, error) + CallTool(ctx context.Context, name string, arguments map[string]any) (string, resultFailure) } // ToolCallHandler intercepts model-emitted tool calls and renders the tool // manifest that is injected into the system prompt. type ToolCallHandler interface { - OnToolCall(ctx context.Context, call ToolCall) (result any, err error) + OnToolCall(ctx context.Context, call ToolCall) (result any, err resultFailure) BuildToolManifest() string } +type adapter struct { + subsystem *guimcp.Subsystem +} + +func (e adapter) Manifest() []guimcp.ToolDescriptor { + return e.subsystem.Manifest() +} + +func (e adapter) ManifestText() string { + return e.subsystem.ManifestText() +} + +func (e adapter) CallTool(ctx context.Context, name string, arguments map[string]any) (string, resultFailure) { + return e.subsystem.CallTool(ctx, name, arguments) +} + type mcpToolCallHandler struct { executor ToolExecutor } @@ -37,7 +53,7 @@ func NewToolCallHandler(executor ToolExecutor) ToolCallHandler { type noopToolCallHandler struct{} -func (noopToolCallHandler) OnToolCall(context.Context, ToolCall) (any, error) { +func (noopToolCallHandler) OnToolCall(context.Context, ToolCall) (any, resultFailure) { return nil, core.E("chat.tool_call", "tool execution unavailable", nil) } @@ -45,7 +61,7 @@ func (noopToolCallHandler) BuildToolManifest() string { return "" } -func (h *mcpToolCallHandler) OnToolCall(ctx context.Context, call ToolCall) (any, error) { +func (h *mcpToolCallHandler) OnToolCall(ctx context.Context, call ToolCall) (any, resultFailure) { if h == nil || h.executor == nil { return nil, core.E("chat.tool_call", "tool execution unavailable", nil) } @@ -142,7 +158,7 @@ func (e *actionToolExecutor) ManifestText() string { return e.fallback.ManifestText() } -func (e *actionToolExecutor) CallTool(ctx context.Context, name string, arguments map[string]any) (string, error) { +func (e *actionToolExecutor) CallTool(ctx context.Context, name string, arguments map[string]any) (string, resultFailure) { if e == nil || e.fallback == nil { return "", core.E("chat.tool_call", "tool execution unavailable", nil) } @@ -163,7 +179,7 @@ type inlineToolCallEnvelope struct { ToolCall *ToolCall `json:"tool_call"` } -func parseInlineToolCall(content string) (ToolCall, bool, error) { +func parseInlineToolCall(content string) (ToolCall, bool, resultFailure) { trimmed := core.Trim(content) if trimmed == "" || !core.Contains(trimmed, "tool_call") { return ToolCall{}, false, nil @@ -252,8 +268,8 @@ func jsonString(value any) string { return "{}" } -func resultError(result core.Result) error { - if err, ok := result.Value.(error); ok { +func resultError(result core.Result) resultFailure { + if err, ok := result.Value.(resultFailure); ok { return err } return core.E("chat.tool_call", "unexpected result type", nil) diff --git a/go/pkg/chat/tool_handler_example_test.go b/go/pkg/chat/tool_handler_example_test.go index 2acb9fee..374cc074 100644 --- a/go/pkg/chat/tool_handler_example_test.go +++ b/go/pkg/chat/tool_handler_example_test.go @@ -21,7 +21,7 @@ func (exampleToolExecutor) ManifestText() string { return "Available MCP tools:\n- layout_suggest: Suggest a layout" } -func (exampleToolExecutor) CallTool(_ context.Context, name string, _ map[string]any) (string, error) { +func (exampleToolExecutor) CallTool(_ context.Context, name string, _ map[string]any) (string, resultFailure) { if name == "layout_suggest" { return `{"mode":"left-right"}`, nil } diff --git a/go/pkg/chat/tool_handler_test.go b/go/pkg/chat/tool_handler_test.go index 78a026f8..a6dce83d 100644 --- a/go/pkg/chat/tool_handler_test.go +++ b/go/pkg/chat/tool_handler_test.go @@ -32,7 +32,7 @@ func (m *strictToolExecutor) ManifestText() string { return "Available MCP tools:\n- layout_suggest: Suggest a layout" } -func (m *strictToolExecutor) CallTool(_ context.Context, name string, arguments map[string]any) (string, error) { +func (m *strictToolExecutor) CallTool(_ context.Context, name string, arguments map[string]any) (string, resultFailure) { m.mu.Lock() defer m.mu.Unlock() m.calls = append(m.calls, ToolCall{Name: name, Arguments: arguments}) diff --git a/go/pkg/clipboard/result_failure.go b/go/pkg/clipboard/result_failure.go new file mode 100644 index 00000000..e5269e49 --- /dev/null +++ b/go/pkg/clipboard/result_failure.go @@ -0,0 +1,5 @@ +package clipboard + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/clipboard/service.go b/go/pkg/clipboard/service.go index e3b05d9a..a44a4ca2 100644 --- a/go/pkg/clipboard/service.go +++ b/go/pkg/clipboard/service.go @@ -120,7 +120,7 @@ func (s *Service) handleQuery(_ *core.Core, q core.Query) core.Result { // clipboardImageData normalizes clipboard image inputs from MCP, preload bridge, and WS callers. // Use: bytes, err := clipboardImageData(core.NewOptions(core.Option{Key: "data", Value: "iVBORw0KGgo..."})) -func clipboardImageData(opts core.Options) ([]byte, error) { +func clipboardImageData(opts core.Options) ([]byte, resultFailure) { if raw, ok := opts.Get("data").Value.([]byte); ok && len(raw) > 0 { return append([]byte(nil), raw...), nil } diff --git a/go/pkg/container/core_helpers.go b/go/pkg/container/core_helpers.go index 4c08f971..051cd1b4 100644 --- a/go/pkg/container/core_helpers.go +++ b/go/pkg/container/core_helpers.go @@ -6,7 +6,7 @@ import ( core "dappco.re/go" ) -func lookPath(file string) (string, error) { +func lookPath(file string) (string, resultFailure) { name := core.Trim(file) if name == "" { return "", core.NewError("executable name is empty") @@ -57,7 +57,7 @@ func command(binary string, args ...string) *core.Cmd { return commandContext(nil, binary, args...) } -func coreWriteMode(path, content string, mode core.FileMode) error { +func coreWriteMode(path, content string, mode core.FileMode) resultFailure { result := core.WriteFile(path, []byte(content), mode) if result.OK { return nil diff --git a/go/pkg/container/detect.go b/go/pkg/container/detect.go index 8688931c..62a86614 100644 --- a/go/pkg/container/detect.go +++ b/go/pkg/container/detect.go @@ -29,7 +29,7 @@ const ( type DetectEnvironment struct { GOOS string ProductVersion string - LookPath func(file string) (string, error) + LookPath func(file string) (string, resultFailure) } // Detect prefers Apple Containers on macOS 26+, then Docker, then Podman. @@ -71,7 +71,7 @@ func DetectWithEnvironment(environment DetectEnvironment) ContainerRuntime { return RuntimeNone } -func hasBinary(lookPath func(string) (string, error), binary string) bool { +func hasBinary(lookPath func(string) (string, resultFailure), binary string) bool { if core.Trim(binary) == "" { return false } diff --git a/go/pkg/container/detect_test.go b/go/pkg/container/detect_test.go index 46c35582..955bd104 100644 --- a/go/pkg/container/detect_test.go +++ b/go/pkg/container/detect_test.go @@ -8,7 +8,7 @@ func TestDetectWithEnvironment_PrefersAppleContainersOnMacOS26(t *core.T) { runtime := DetectWithEnvironment(DetectEnvironment{ GOOS: "darwin", ProductVersion: "26.0", - LookPath: func(file string) (string, error) { + LookPath: func(file string) (string, resultFailure) { if file == "container" { return "/usr/bin/container", nil } @@ -23,7 +23,7 @@ func TestDetectWithEnvironment_FallsBackToDockerWhenAppleUnavailable(t *core.T) runtime := DetectWithEnvironment(DetectEnvironment{ GOOS: "darwin", ProductVersion: "26.1", - LookPath: func(file string) (string, error) { + LookPath: func(file string) (string, resultFailure) { if file == "docker" { return "/usr/local/bin/docker", nil } @@ -38,7 +38,7 @@ func TestDetectWithEnvironment_UsesDockerOnNonMacHosts(t *core.T) { runtime := DetectWithEnvironment(DetectEnvironment{ GOOS: "linux", ProductVersion: "", - LookPath: func(file string) (string, error) { + LookPath: func(file string) (string, resultFailure) { if file == "docker" { return "/usr/bin/docker", nil } @@ -53,7 +53,7 @@ func TestDetectWithEnvironment_UsesPodmanWhenDockerMissing(t *core.T) { runtime := DetectWithEnvironment(DetectEnvironment{ GOOS: "linux", ProductVersion: "", - LookPath: func(file string) (string, error) { + LookPath: func(file string) (string, resultFailure) { if file == "podman" { return "/usr/bin/podman", nil } @@ -68,7 +68,7 @@ func TestDetectWithEnvironment_ReturnsNoneWhenNoRuntimeIsAvailable(t *core.T) { runtime := DetectWithEnvironment(DetectEnvironment{ GOOS: "linux", ProductVersion: "", - LookPath: func(string) (string, error) { + LookPath: func(string) (string, resultFailure) { return "", core.NewError("not found") }, }) @@ -89,7 +89,7 @@ func TestDetect_Good(t *core.T) { runtime := DetectWithEnvironment(DetectEnvironment{ GOOS: "darwin", ProductVersion: "26.0", - LookPath: func(file string) (string, error) { + LookPath: func(file string) (string, resultFailure) { if file == "container" { return containerPath, nil } diff --git a/go/pkg/container/result_failure.go b/go/pkg/container/result_failure.go new file mode 100644 index 00000000..bef1aafc --- /dev/null +++ b/go/pkg/container/result_failure.go @@ -0,0 +1,5 @@ +package container + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/container/service.go b/go/pkg/container/service.go index 3dbbb270..2f5c349e 100644 --- a/go/pkg/container/service.go +++ b/go/pkg/container/service.go @@ -19,7 +19,7 @@ var timContainerNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_.-]*$`) func NewService(c *core.Core, options TIMOptions) *Service { options, err := normalizeTIMOptions(options) if options.Exec == nil && c != nil { - options.Exec = func(ctx context.Context, name string, args ...string) error { + options.Exec = func(ctx context.Context, name string, args ...string) resultFailure { result := c.Process().Run(ctx, name, args...) if result.OK { return nil @@ -42,7 +42,7 @@ func OptionsFromEnv() TIMOptions { return options } -func OptionsFromEnvValidated() (TIMOptions, error) { +func OptionsFromEnvValidated() (TIMOptions, resultFailure) { return TIMOptions{ Name: core.Trim(core.Env("CORE_TIM_NAME")), Image: core.Trim(core.Env("CORE_TIM_IMAGE")), @@ -75,11 +75,11 @@ func (s *Service) OnStartup(_ context.Context) core.Result { return core.Result{OK: true} } -func (options TIMOptions) Validate() (TIMOptions, error) { +func (options TIMOptions) Validate() (TIMOptions, resultFailure) { return normalizeTIMOptions(options) } -func normalizeTIMOptions(options TIMOptions) (TIMOptions, error) { +func normalizeTIMOptions(options TIMOptions) (TIMOptions, resultFailure) { options.Name = core.Trim(options.Name) options.Image = core.Trim(options.Image) options.DataDir = core.Trim(options.DataDir) @@ -97,7 +97,7 @@ func normalizeTIMOptions(options TIMOptions) (TIMOptions, error) { return options, nil } -func validateTIMContainerName(value string) error { +func validateTIMContainerName(value string) resultFailure { if value == "" { return nil } @@ -113,7 +113,7 @@ func validateTIMContainerName(value string) error { return nil } -func validateTIMArgValue(label, value string) error { +func validateTIMArgValue(label, value string) resultFailure { if value == "" { return nil } diff --git a/go/pkg/container/service_test.go b/go/pkg/container/service_test.go index f52709d0..9a5ad498 100644 --- a/go/pkg/container/service_test.go +++ b/go/pkg/container/service_test.go @@ -23,7 +23,7 @@ func newTestContainerService(t *core.T, options TIMOptions) (*Service, *core.Cor return svc, c } -func newInvalidTestContainerService(t *core.T, options TIMOptions) (*Service, *core.Core, error) { +func newInvalidTestContainerService(t *core.T, options TIMOptions) (*Service, *core.Core, resultFailure) { t.Helper() if options.Detect == nil { @@ -43,7 +43,7 @@ func newInvalidTestContainerService(t *core.T, options TIMOptions) (*Service, *c result := c.ServiceStartup(context.Background(), nil) core.AssertFalse(t, result.OK) core.AssertNotNil(t, svc) - err, ok := result.Value.(error) + err, ok := result.Value.(resultFailure) core.RequireTrue(t, ok) return svc, c, err } @@ -238,7 +238,7 @@ func TestService_OnStartup_GoodCase(t *core.T) { Detect: func() ContainerRuntime { return RuntimeDocker }, - Exec: func(_ context.Context, name string, args ...string) error { + Exec: func(_ context.Context, name string, args ...string) resultFailure { calls = append(calls, append([]string{name}, args...)...) return nil }, @@ -291,8 +291,8 @@ func TestService_OnStartup_BadCase(t *core.T) { result := c.Action("tim.start").Run(context.Background(), core.NewOptions()) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "no supported container runtime detected") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "no supported container runtime detected") } func TestService_OnStartup_UglyCase(t *core.T) { @@ -300,7 +300,7 @@ func TestService_OnStartup_UglyCase(t *core.T) { Detect: func() ContainerRuntime { return RuntimeDocker }, - Exec: func(context.Context, string, ...string) error { + Exec: func(context.Context, string, ...string) resultFailure { return core.NewError("boom") }, }) @@ -308,8 +308,8 @@ func TestService_OnStartup_UglyCase(t *core.T) { result := c.Action("tim.start").Run(context.Background(), core.NewOptions()) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "boom") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "boom") status := c.Action("tim.status").Run(context.Background(), core.NewOptions()) core.RequireTrue(t, status.OK) diff --git a/go/pkg/container/tim.go b/go/pkg/container/tim.go index c59f353c..cc361239 100644 --- a/go/pkg/container/tim.go +++ b/go/pkg/container/tim.go @@ -18,7 +18,7 @@ type TIMOptions struct { DataDir string Runtime ContainerRuntime Detect func() ContainerRuntime - Exec func(context.Context, string, ...string) error + Exec func(context.Context, string, ...string) resultFailure Now func() time.Time Resources TIMResources } @@ -57,7 +57,7 @@ func NewTIMManager(options TIMOptions) *TIMManager { options.Detect = Detect } if options.Exec == nil { - options.Exec = func(ctx context.Context, name string, args ...string) error { + options.Exec = func(ctx context.Context, name string, args ...string) resultFailure { cmd := commandContext(ctx, name, args...) return cmd.Run() } @@ -85,7 +85,7 @@ func (m *TIMManager) State() TIMState { return cloneTIMState(m.state) } -func (m *TIMManager) Start(ctx context.Context) (TIMState, error) { +func (m *TIMManager) Start(ctx context.Context) (TIMState, resultFailure) { m.mu.Lock() runtime := coalesceRuntime(m.options.Runtime, m.options.Detect()) m.state.Runtime = runtime @@ -114,7 +114,7 @@ func (m *TIMManager) Start(ctx context.Context) (TIMState, error) { return state, nil } -func (m *TIMManager) Stop(ctx context.Context) (TIMState, error) { +func (m *TIMManager) Stop(ctx context.Context) (TIMState, resultFailure) { m.mu.Lock() if m.state.Runtime == RuntimeNone { m.state.Status = "stopped" diff --git a/go/pkg/container/tim_test.go b/go/pkg/container/tim_test.go index d97c02e7..2c200a03 100644 --- a/go/pkg/container/tim_test.go +++ b/go/pkg/container/tim_test.go @@ -80,7 +80,7 @@ func TestTIMManager_Start_Good(t *core.T) { Detect: func() ContainerRuntime { return RuntimeDocker }, - Exec: func(_ context.Context, name string, args ...string) error { + Exec: func(_ context.Context, name string, args ...string) resultFailure { calls = append(calls, append([]string{name}, args...)...) return nil }, @@ -126,7 +126,7 @@ func TestTIMManager_Start_Ugly(t *core.T) { Detect: func() ContainerRuntime { return RuntimeDocker }, - Exec: func(context.Context, string, ...string) error { + Exec: func(context.Context, string, ...string) resultFailure { return core.NewError("docker failed") }, }) @@ -146,7 +146,7 @@ func TestTIMManager_Stop_Good(t *core.T) { Detect: func() ContainerRuntime { return RuntimeDocker }, - Exec: func(_ context.Context, name string, args ...string) error { + Exec: func(_ context.Context, name string, args ...string) resultFailure { calls = append(calls, append([]string{name}, args...)...) return nil }, @@ -174,7 +174,7 @@ func TestTIMManager_Stop_Bad(t *core.T) { Detect: func() ContainerRuntime { return RuntimeDocker }, - Exec: func(context.Context, string, ...string) error { + Exec: func(context.Context, string, ...string) resultFailure { return core.NewError("stop failed") }, }) diff --git a/go/pkg/contextmenu/platform.go b/go/pkg/contextmenu/platform.go index 915f231c..aa8dc5be 100644 --- a/go/pkg/contextmenu/platform.go +++ b/go/pkg/contextmenu/platform.go @@ -10,10 +10,10 @@ type Platform interface { // The onItemClick callback is called with (menuName, actionID, data) // when any item in the menu is clicked. The adapter creates per-item // OnClick handlers that call this with the appropriate ActionID. - Add(name string, menu ContextMenuDef, onItemClick func(menuName, actionID, data string)) error + Add(name string, menu ContextMenuDef, onItemClick func(menuName, actionID, data string)) resultFailure // Remove unregisters a context menu by name. - Remove(name string) error + Remove(name string) resultFailure // Get returns a context menu definition by name, or false if not found. Get(name string) (*ContextMenuDef, bool) diff --git a/go/pkg/contextmenu/result_failure.go b/go/pkg/contextmenu/result_failure.go new file mode 100644 index 00000000..be1b5357 --- /dev/null +++ b/go/pkg/contextmenu/result_failure.go @@ -0,0 +1,5 @@ +package contextmenu + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/contextmenu/service.go b/go/pkg/contextmenu/service.go index 03637d4e..e3281dcb 100644 --- a/go/pkg/contextmenu/service.go +++ b/go/pkg/contextmenu/service.go @@ -18,7 +18,7 @@ type Service struct { registeredMenus map[string]ContextMenuDef } -func platformUnavailableError(op string) error { +func platformUnavailableError(op string) resultFailure { return core.E("contextmenu."+op, "platform backend unavailable", nil) } @@ -118,7 +118,7 @@ func (s *Service) queryList() map[string]ContextMenuDef { return result } -func (s *Service) taskAdd(t TaskAdd) error { +func (s *Service) taskAdd(t TaskAdd) resultFailure { if s.platform == nil { return platformUnavailableError("taskAdd") } @@ -146,7 +146,7 @@ func (s *Service) taskAdd(t TaskAdd) error { return nil } -func (s *Service) taskRemove(t TaskRemove) error { +func (s *Service) taskRemove(t TaskRemove) resultFailure { if s.platform == nil { return platformUnavailableError("taskRemove") } @@ -165,7 +165,7 @@ func (s *Service) taskRemove(t TaskRemove) error { return nil } -func (s *Service) taskUpdate(t TaskUpdate) error { +func (s *Service) taskUpdate(t TaskUpdate) resultFailure { if s.platform == nil { return platformUnavailableError("taskUpdate") } @@ -191,7 +191,7 @@ func (s *Service) taskUpdate(t TaskUpdate) error { return nil } -func (s *Service) taskDestroy(t TaskDestroy) error { +func (s *Service) taskDestroy(t TaskDestroy) resultFailure { if s.platform == nil { return platformUnavailableError("taskDestroy") } diff --git a/go/pkg/contextmenu/service_test.go b/go/pkg/contextmenu/service_test.go index f97b3463..b57e9f6c 100644 --- a/go/pkg/contextmenu/service_test.go +++ b/go/pkg/contextmenu/service_test.go @@ -14,8 +14,8 @@ type mockPlatform struct { menus map[string]ContextMenuDef clickHandlers map[string]func(menuName, actionID, data string) removed []string - addErr error - removeErr error + addErr resultFailure + removeErr resultFailure } func newMockPlatform() *mockPlatform { @@ -25,7 +25,7 @@ func newMockPlatform() *mockPlatform { } } -func (m *mockPlatform) Add(name string, menu ContextMenuDef, onItemClick func(string, string, string)) error { +func (m *mockPlatform) Add(name string, menu ContextMenuDef, onItemClick func(string, string, string)) resultFailure { m.mu.Lock() defer m.mu.Unlock() if m.addErr != nil { @@ -36,7 +36,7 @@ func (m *mockPlatform) Add(name string, menu ContextMenuDef, onItemClick func(st return nil } -func (m *mockPlatform) Remove(name string) error { +func (m *mockPlatform) Remove(name string) resultFailure { m.mu.Lock() defer m.mu.Unlock() if m.removeErr != nil { @@ -78,7 +78,7 @@ func newFlakyAddPlatform() *flakyAddPlatform { return &flakyAddPlatform{mockPlatform: newMockPlatform()} } -func (m *flakyAddPlatform) Add(name string, menu ContextMenuDef, onItemClick func(string, string, string)) error { +func (m *flakyAddPlatform) Add(name string, menu ContextMenuDef, onItemClick func(string, string, string)) resultFailure { m.mu.Lock() defer m.mu.Unlock() if m.failAddOnce { @@ -140,7 +140,7 @@ func TestNilPlatform_Good_MutationAndShutdownAreSafe(t *core.T) { for _, tc := range cases { r := taskRun(c, tc.action, tc.task) core.AssertFalse(t, r.OK, tc.name) - err, _ := r.Value.(error) + err, _ := r.Value.(resultFailure) core.AssertError(t, err) core.AssertContains(t, err.Error(), "platform backend unavailable") } @@ -217,7 +217,7 @@ func TestTaskRemove_Bad_NotFound(t *core.T) { r := taskRun(c, "contextmenu.remove", TaskRemove{Name: "nonexistent"}) core.AssertFalse(t, r.OK) - err, _ := r.Value.(error) + err, _ := r.Value.(resultFailure) core.AssertErrorIs(t, err, ErrorMenuNotFound) } @@ -407,12 +407,12 @@ func TestTaskUpdate_Bad_NotFound(t *core.T) { Menu: ContextMenuDef{Name: "ghost"}, }) core.AssertFalse(t, r.OK) - err, _ := r.Value.(error) + err, _ := r.Value.(resultFailure) core.AssertErrorIs(t, err, ErrorMenuNotFound) } func TestTaskUpdate_Ugly_PlatformRemoveError(t *core.T) { - // Platform Remove fails mid-update — error is propagated + // Platform Remove fails mid-update — resultFailure is propagated mp := newMockPlatform() _, c := newTestContextMenuService(t, mp) @@ -422,7 +422,7 @@ func TestTaskUpdate_Ugly_PlatformRemoveError(t *core.T) { }) mp.mu.Lock() - mp.removeErr = ErrorMenuNotFound // reuse sentinel as a platform-level error + mp.removeErr = ErrorMenuNotFound // reuse sentinel as a platform-level resultFailure mp.mu.Unlock() r := taskRun(c, "contextmenu.update", TaskUpdate{ @@ -488,12 +488,12 @@ func TestTaskDestroy_Bad_NotFound(t *core.T) { r := taskRun(c, "contextmenu.destroy", TaskDestroy{Name: "nonexistent"}) core.AssertFalse(t, r.OK) - err, _ := r.Value.(error) + err, _ := r.Value.(resultFailure) core.AssertErrorIs(t, err, ErrorMenuNotFound) } func TestTaskDestroy_Ugly_PlatformError(t *core.T) { - // Platform Remove fails — error is propagated but service remains consistent + // Platform Remove fails — resultFailure is propagated but service remains consistent mp := newMockPlatform() _, c := newTestContextMenuService(t, mp) @@ -559,7 +559,7 @@ func TestOnShutdown_Good_CleansUpMenus(t *core.T) { } func TestOnShutdown_Bad_NothingRegistered(t *core.T) { - // OnShutdown with no menus — no-op, no error + // OnShutdown with no menus — no-op, no resultFailure mp := newMockPlatform() _, c := newTestContextMenuService(t, mp) @@ -577,7 +577,7 @@ func TestOnShutdown_Ugly_PlatformRemoveErrors(t *core.T) { mp.removeErr = ErrorMenuNotFound mp.mu.Unlock() - // Shutdown must not return an error even if platform Remove fails + // Shutdown must not return an resultFailure even if platform Remove fails core.AssertTrue(t, c.ServiceShutdown(t.Context()).OK) } diff --git a/go/pkg/deno/result_failure.go b/go/pkg/deno/result_failure.go new file mode 100644 index 00000000..d40bf8b0 --- /dev/null +++ b/go/pkg/deno/result_failure.go @@ -0,0 +1,5 @@ +package deno + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/deno/sidecar.go b/go/pkg/deno/sidecar.go index 87e41e17..9e19be6a 100644 --- a/go/pkg/deno/sidecar.go +++ b/go/pkg/deno/sidecar.go @@ -59,7 +59,7 @@ func New(options Options) *Manager { } } -func (m *Manager) Start(ctx context.Context) (Status, error) { +func (m *Manager) Start(ctx context.Context) (Status, resultFailure) { m.mu.Lock() defer m.mu.Unlock() if m.cmd != nil && m.cmd.Process != nil { @@ -95,7 +95,7 @@ func (m *Manager) Start(ctx context.Context) (Status, error) { return m.statusLocked(), nil } -func (m *Manager) Stop(context.Context) (Status, error) { +func (m *Manager) Stop(context.Context) (Status, resultFailure) { m.mu.Lock() if m.cmd == nil || m.cmd.Process == nil { m.mu.Unlock() @@ -168,7 +168,7 @@ func (m *Manager) OnEvent(handler func(Event)) { m.events = append(m.events, handler) } -func (m *Manager) Eval(ctx context.Context, code string) (EvalResult, error) { +func (m *Manager) Eval(ctx context.Context, code string) (EvalResult, resultFailure) { response, err := m.request(ctx, rpcMessage{Type: "eval", Code: code}) if err != nil { return EvalResult{}, err @@ -176,14 +176,14 @@ func (m *Manager) Eval(ctx context.Context, code string) (EvalResult, error) { return EvalResult{Value: response.Result}, nil } -func (m *Manager) Emit(name string, data any) error { +func (m *Manager) Emit(name string, data any) resultFailure { if core.Trim(name) == "" { return core.NewError("event name is required") } return m.send(rpcMessage{Type: "event", Name: name, Data: data}) } -func (m *Manager) request(ctx context.Context, message rpcMessage) (rpcMessage, error) { +func (m *Manager) request(ctx context.Context, message rpcMessage) (rpcMessage, resultFailure) { m.mu.Lock() if m.stdin == nil { m.mu.Unlock() @@ -291,7 +291,7 @@ func (m *Manager) handleAction(message rpcMessage) { } } -func (m *Manager) send(message rpcMessage) error { +func (m *Manager) send(message rpcMessage) resultFailure { m.mu.Lock() defer m.mu.Unlock() if m.stdin == nil { @@ -305,7 +305,7 @@ func (m *Manager) send(message rpcMessage) error { return err } -func marshalRPCMessage(message rpcMessage) ([]byte, error) { +func marshalRPCMessage(message rpcMessage) ([]byte, resultFailure) { result := core.JSONMarshal(message) if !result.OK { if err, ok := result.Value.(error); ok { diff --git a/go/pkg/dialog/platform.go b/go/pkg/dialog/platform.go index 1a6bc114..b3d5dc30 100644 --- a/go/pkg/dialog/platform.go +++ b/go/pkg/dialog/platform.go @@ -3,10 +3,10 @@ package dialog // Platform abstracts the native dialog backend. type Platform interface { - OpenFile(options OpenFileOptions) ([]string, error) - SaveFile(options SaveFileOptions) (string, error) - OpenDirectory(options OpenDirectoryOptions) (string, error) - MessageDialog(options MessageDialogOptions) (string, error) + OpenFile(options OpenFileOptions) ([]string, resultFailure) + SaveFile(options SaveFileOptions) (string, resultFailure) + OpenDirectory(options OpenDirectoryOptions) (string, resultFailure) + MessageDialog(options MessageDialogOptions) (string, resultFailure) } // DialogType represents the type of message dialog. diff --git a/go/pkg/dialog/result_failure.go b/go/pkg/dialog/result_failure.go new file mode 100644 index 00000000..ba0ebe92 --- /dev/null +++ b/go/pkg/dialog/result_failure.go @@ -0,0 +1,5 @@ +package dialog + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/dialog/service.go b/go/pkg/dialog/service.go index 3214c174..c3e642ff 100644 --- a/go/pkg/dialog/service.go +++ b/go/pkg/dialog/service.go @@ -146,7 +146,7 @@ func (s *Service) HandleIPCEvents(_ *core.Core, _ core.Message) core.Result { return core.Result{OK: true} } -func openFileOptionsFrom(opts core.Options) (OpenFileOptions, error) { +func openFileOptionsFrom(opts core.Options) (OpenFileOptions, resultFailure) { if task := opts.Get("task"); task.OK { switch v := task.Value.(type) { case TaskOpenFile: @@ -162,7 +162,7 @@ func openFileOptionsFrom(opts core.Options) (OpenFileOptions, error) { return decodeOptions[OpenFileOptions](opts) } -func saveFileOptionsFrom(opts core.Options) (SaveFileOptions, error) { +func saveFileOptionsFrom(opts core.Options) (SaveFileOptions, resultFailure) { if task := opts.Get("task"); task.OK { switch v := task.Value.(type) { case TaskSaveFile: @@ -178,7 +178,7 @@ func saveFileOptionsFrom(opts core.Options) (SaveFileOptions, error) { return decodeOptions[SaveFileOptions](opts) } -func openDirectoryOptionsFrom(opts core.Options) (OpenDirectoryOptions, error) { +func openDirectoryOptionsFrom(opts core.Options) (OpenDirectoryOptions, resultFailure) { if task := opts.Get("task"); task.OK { switch v := task.Value.(type) { case TaskOpenDirectory: @@ -190,7 +190,7 @@ func openDirectoryOptionsFrom(opts core.Options) (OpenDirectoryOptions, error) { return decodeOptions[OpenDirectoryOptions](opts) } -func messageDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) { +func messageDialogOptionsFrom(opts core.Options) (MessageDialogOptions, resultFailure) { if task := opts.Get("task"); task.OK { switch v := task.Value.(type) { case TaskMessageDialog: @@ -202,7 +202,7 @@ func messageDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) { return decodeOptions[MessageDialogOptions](opts) } -func infoDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) { +func infoDialogOptionsFrom(opts core.Options) (MessageDialogOptions, resultFailure) { if task := opts.Get("task"); task.OK { switch v := task.Value.(type) { case TaskInfo: @@ -222,7 +222,7 @@ func infoDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) { return typedMessageDialogOptionsFrom(opts, DialogInfo, "dialog.infoDialogOptionsFrom") } -func warningDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) { +func warningDialogOptionsFrom(opts core.Options) (MessageDialogOptions, resultFailure) { if task := opts.Get("task"); task.OK { switch v := task.Value.(type) { case TaskWarning: @@ -242,7 +242,7 @@ func warningDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) { return typedMessageDialogOptionsFrom(opts, DialogWarning, "dialog.warningDialogOptionsFrom") } -func errorDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) { +func errorDialogOptionsFrom(opts core.Options) (MessageDialogOptions, resultFailure) { if task := opts.Get("task"); task.OK { switch v := task.Value.(type) { case TaskError: @@ -262,7 +262,7 @@ func errorDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) { return typedMessageDialogOptionsFrom(opts, DialogError, "dialog.errorDialogOptionsFrom") } -func typedMessageDialogOptionsFrom(opts core.Options, dialogType DialogType, op string) (MessageDialogOptions, error) { +func typedMessageDialogOptionsFrom(opts core.Options, dialogType DialogType, op string) (MessageDialogOptions, resultFailure) { if !hasDirectDialogOptions(opts) { return MessageDialogOptions{}, core.E(op, "failed to decode dialog options", nil) } @@ -283,7 +283,7 @@ func hasDirectDialogOptions(opts core.Options) bool { return false } -func questionDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) { +func questionDialogOptionsFrom(opts core.Options) (MessageDialogOptions, resultFailure) { if task := opts.Get("task"); task.OK { switch v := task.Value.(type) { case TaskQuestion: @@ -314,7 +314,7 @@ func questionDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) return decoded, nil } -func promptOptionsFrom(opts core.Options) (TaskPrompt, error) { +func promptOptionsFrom(opts core.Options) (TaskPrompt, resultFailure) { if task := opts.Get("task"); task.OK { if v, ok := task.Value.(TaskPrompt); ok { return v, nil @@ -323,7 +323,7 @@ func promptOptionsFrom(opts core.Options) (TaskPrompt, error) { return decodeOptions[TaskPrompt](opts) } -func decodeOptions[T any](opts core.Options) (T, error) { +func decodeOptions[T any](opts core.Options) (T, resultFailure) { var input T items := make(map[string]any, opts.Len()) for _, item := range opts.Items() { @@ -342,7 +342,7 @@ func decodeOptions[T any](opts core.Options) (T, error) { return input, nil } -func (s *Service) promptWindowName() (string, error) { +func (s *Service) promptWindowName() (string, resultFailure) { r := s.Core().QUERY(window.QueryWindowList{}) if !r.OK { return "", core.E("dialog.promptWindowName", "window service unavailable", nil) diff --git a/go/pkg/dialog/service_test.go b/go/pkg/dialog/service_test.go index 5882877a..a092ee03 100644 --- a/go/pkg/dialog/service_test.go +++ b/go/pkg/dialog/service_test.go @@ -14,29 +14,29 @@ type mockPlatform struct { saveFilePath string openDirPath string messageButton string - openFileErr error - saveFileErr error - openDirErr error - messageErr error + openFileErr resultFailure + saveFileErr resultFailure + openDirErr resultFailure + messageErr resultFailure lastOpenOpts OpenFileOptions lastSaveOpts SaveFileOptions lastDirOpts OpenDirectoryOptions lastMsgOpts MessageDialogOptions } -func (m *mockPlatform) OpenFile(opts OpenFileOptions) ([]string, error) { +func (m *mockPlatform) OpenFile(opts OpenFileOptions) ([]string, resultFailure) { m.lastOpenOpts = opts return m.openFilePaths, m.openFileErr } -func (m *mockPlatform) SaveFile(opts SaveFileOptions) (string, error) { +func (m *mockPlatform) SaveFile(opts SaveFileOptions) (string, resultFailure) { m.lastSaveOpts = opts return m.saveFilePath, m.saveFileErr } -func (m *mockPlatform) OpenDirectory(opts OpenDirectoryOptions) (string, error) { +func (m *mockPlatform) OpenDirectory(opts OpenDirectoryOptions) (string, resultFailure) { m.lastDirOpts = opts return m.openDirPath, m.openDirErr } -func (m *mockPlatform) MessageDialog(opts MessageDialogOptions) (string, error) { +func (m *mockPlatform) MessageDialog(opts MessageDialogOptions) (string, resultFailure) { m.lastMsgOpts = opts return m.messageButton, m.messageErr } diff --git a/go/pkg/display/api.go b/go/pkg/display/api.go index c314c1cb..c86b07dc 100644 --- a/go/pkg/display/api.go +++ b/go/pkg/display/api.go @@ -79,11 +79,11 @@ type Theme struct { IsDark bool `json:"isDark"` } -func unexpectedResultType(method string) error { +func unexpectedResultType(method string) resultFailure { return core.E(method, "unexpected result type", nil) } -func failedQuery(method, query string) error { +func failedQuery(method, query string) resultFailure { return core.E(method, query+" query failed", nil) } @@ -103,7 +103,7 @@ func (s *Service) GetScreens() []*Screen { return result } -func (s *Service) GetScreen(id string) (*Screen, error) { +func (s *Service) GetScreen(id string) (*Screen, resultFailure) { r := s.Core().QUERY(screen.QueryByID{ID: id}) if !r.OK { if err, ok := r.Value.(error); ok { @@ -118,7 +118,7 @@ func (s *Service) GetScreen(id string) (*Screen, error) { return screenToDisplay(scr), nil } -func (s *Service) GetPrimaryScreen() (*Screen, error) { +func (s *Service) GetPrimaryScreen() (*Screen, resultFailure) { r := s.Core().QUERY(screen.QueryPrimary{}) if !r.OK { if err, ok := r.Value.(error); ok { @@ -133,7 +133,7 @@ func (s *Service) GetPrimaryScreen() (*Screen, error) { return screenToDisplay(scr), nil } -func (s *Service) GetScreenAtPoint(x, y int) (*Screen, error) { +func (s *Service) GetScreenAtPoint(x, y int) (*Screen, resultFailure) { r := s.Core().QUERY(screen.QueryAtPoint{X: x, Y: y}) if !r.OK { if err, ok := r.Value.(error); ok { @@ -148,7 +148,7 @@ func (s *Service) GetScreenAtPoint(x, y int) (*Screen, error) { return screenToDisplay(scr), nil } -func (s *Service) GetScreenForWindow(name string) (*Screen, error) { +func (s *Service) GetScreenForWindow(name string) (*Screen, resultFailure) { info, err := s.GetWindowInfo(name) if err != nil || info == nil { return nil, err @@ -173,7 +173,7 @@ func (s *Service) GetWorkAreas() []*WorkArea { return result } -func (s *Service) OpenSingleFileDialog(opts OpenFileOptions) (string, error) { +func (s *Service) OpenSingleFileDialog(opts OpenFileOptions) (string, resultFailure) { paths, err := s.OpenFileDialog(opts) if err != nil || len(paths) == 0 { return "", err @@ -181,7 +181,7 @@ func (s *Service) OpenSingleFileDialog(opts OpenFileOptions) (string, error) { return paths[0], nil } -func (s *Service) OpenFileDialog(opts OpenFileOptions) ([]string, error) { +func (s *Service) OpenFileDialog(opts OpenFileOptions) ([]string, resultFailure) { result := s.Core().Action("dialog.openFile").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskOpenFile{Options: toDialogOpenFileOptions(opts)}}, )) @@ -198,7 +198,7 @@ func (s *Service) OpenFileDialog(opts OpenFileOptions) ([]string, error) { return paths, nil } -func (s *Service) SaveFileDialog(opts SaveFileOptions) (string, error) { +func (s *Service) SaveFileDialog(opts SaveFileOptions) (string, resultFailure) { result := s.Core().Action("dialog.saveFile").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskSaveFile{Options: toDialogSaveFileOptions(opts)}}, )) @@ -215,7 +215,7 @@ func (s *Service) SaveFileDialog(opts SaveFileOptions) (string, error) { return path, nil } -func (s *Service) OpenDirectoryDialog(opts OpenDirectoryOptions) (string, error) { +func (s *Service) OpenDirectoryDialog(opts OpenDirectoryOptions) (string, resultFailure) { result := s.Core().Action("dialog.openDirectory").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskOpenDirectory{Options: toDialogOpenDirectoryOptions(opts)}}, )) @@ -232,7 +232,7 @@ func (s *Service) OpenDirectoryDialog(opts OpenDirectoryOptions) (string, error) return path, nil } -func (s *Service) ConfirmDialog(title, message string) (bool, error) { +func (s *Service) ConfirmDialog(title, message string) (bool, resultFailure) { result := s.Core().Action("dialog.question").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskQuestion{ Title: title, @@ -253,7 +253,7 @@ func (s *Service) ConfirmDialog(title, message string) (bool, error) { return button == "Yes", nil } -func (s *Service) PromptDialog(title, message string) (string, bool, error) { +func (s *Service) PromptDialog(title, message string) (string, bool, resultFailure) { result := s.Core().Action("dialog.prompt").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskPrompt{Title: title, Message: message}}, )) @@ -270,7 +270,7 @@ func (s *Service) PromptDialog(title, message string) (string, bool, error) { return prompt.Value, prompt.Confirmed, nil } -func (s *Service) SetTrayIcon(icon []byte) error { +func (s *Service) SetTrayIcon(icon []byte) resultFailure { result := s.Core().Action("systray.setIcon").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: systray.TaskSetTrayIcon{Data: icon}}, )) @@ -283,7 +283,7 @@ func (s *Service) SetTrayIcon(icon []byte) error { return nil } -func (s *Service) SetTrayTooltip(tooltip string) error { +func (s *Service) SetTrayTooltip(tooltip string) resultFailure { result := s.Core().Action("systray.setTooltip").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: systray.TaskSetTrayTooltip{Tooltip: tooltip}}, )) @@ -296,7 +296,7 @@ func (s *Service) SetTrayTooltip(tooltip string) error { return nil } -func (s *Service) SetTrayLabel(label string) error { +func (s *Service) SetTrayLabel(label string) resultFailure { result := s.Core().Action("systray.setLabel").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: systray.TaskSetTrayLabel{Label: label}}, )) @@ -309,7 +309,7 @@ func (s *Service) SetTrayLabel(label string) error { return nil } -func (s *Service) SetTrayMenu(items []TrayMenuItem) error { +func (s *Service) SetTrayMenu(items []TrayMenuItem) resultFailure { result := s.Core().Action("systray.setMenu").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: systray.TaskSetTrayMenu{Items: trayMenuItemsToSystray(items)}}, )) @@ -331,7 +331,7 @@ func (s *Service) GetTrayInfo() map[string]any { return info } -func (s *Service) ShowTrayMessage(title, message string) error { +func (s *Service) ShowTrayMessage(title, message string) resultFailure { result := s.Core().Action("systray.showMessage").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: systray.TaskShowMessage{Title: title, Message: message}}, )) @@ -344,7 +344,7 @@ func (s *Service) ShowTrayMessage(title, message string) error { return nil } -func (s *Service) ReadClipboard() (string, error) { +func (s *Service) ReadClipboard() (string, resultFailure) { r := s.Core().QUERY(clipboard.QueryText{}) if !r.OK { if err, ok := r.Value.(error); ok { @@ -359,7 +359,7 @@ func (s *Service) ReadClipboard() (string, error) { return content.Text, nil } -func (s *Service) WriteClipboard(text string) error { +func (s *Service) WriteClipboard(text string) resultFailure { result := s.Core().Action("clipboard.setText").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: clipboard.TaskSetText{Text: text}}, )) @@ -377,7 +377,7 @@ func (s *Service) HasClipboard() bool { return err == nil && text != "" } -func (s *Service) ClearClipboard() error { +func (s *Service) ClearClipboard() resultFailure { result := s.Core().Action("clipboard.clear").Run(context.Background(), core.NewOptions()) if !result.OK { if err, ok := result.Value.(error); ok { @@ -388,7 +388,7 @@ func (s *Service) ClearClipboard() error { return nil } -func (s *Service) ReadClipboardImage() ([]byte, error) { +func (s *Service) ReadClipboardImage() ([]byte, resultFailure) { r := s.Core().QUERY(clipboard.QueryImage{}) if !r.OK { if err, ok := r.Value.(error); ok { @@ -406,7 +406,7 @@ func (s *Service) ReadClipboardImage() ([]byte, error) { return append([]byte(nil), content.Data...), nil } -func (s *Service) WriteClipboardImage(data []byte) error { +func (s *Service) WriteClipboardImage(data []byte) resultFailure { if len(data) == 0 { return core.E(writeClipboardImageOp, "clipboard image data is required", nil) } @@ -425,7 +425,7 @@ func (s *Service) WriteClipboardImage(data []byte) error { return nil } -func (s *Service) ShowNotification(opts NotificationOptions) error { +func (s *Service) ShowNotification(opts NotificationOptions) resultFailure { return s.sendNotification(notification.NotificationOptions{ ID: opts.ID, Title: opts.Title, @@ -434,11 +434,11 @@ func (s *Service) ShowNotification(opts NotificationOptions) error { }) } -func (s *Service) ShowInfoNotification(title, message string) error { +func (s *Service) ShowInfoNotification(title, message string) resultFailure { return s.sendNotification(notification.NotificationOptions{Title: title, Message: message}) } -func (s *Service) ShowWarningNotification(title, message string) error { +func (s *Service) ShowWarningNotification(title, message string) resultFailure { return s.sendNotification(notification.NotificationOptions{ Title: title, Message: message, @@ -446,7 +446,7 @@ func (s *Service) ShowWarningNotification(title, message string) error { }) } -func (s *Service) ShowErrorNotification(title, message string) error { +func (s *Service) ShowErrorNotification(title, message string) resultFailure { return s.sendNotification(notification.NotificationOptions{ Title: title, Message: message, @@ -454,7 +454,7 @@ func (s *Service) ShowErrorNotification(title, message string) error { }) } -func (s *Service) RequestNotificationPermission() (bool, error) { +func (s *Service) RequestNotificationPermission() (bool, resultFailure) { r := s.Core().Action("notification.requestPermission").Run(context.Background(), core.NewOptions()) if !r.OK { if err, ok := r.Value.(error); ok { @@ -469,7 +469,7 @@ func (s *Service) RequestNotificationPermission() (bool, error) { return granted, nil } -func (s *Service) CheckNotificationPermission() (bool, error) { +func (s *Service) CheckNotificationPermission() (bool, resultFailure) { r := s.Core().QUERY(notification.QueryPermission{}) if !r.OK { if err, ok := r.Value.(error); ok { @@ -484,7 +484,7 @@ func (s *Service) CheckNotificationPermission() (bool, error) { return status.Granted, nil } -func (s *Service) ClearNotifications() error { +func (s *Service) ClearNotifications() resultFailure { result := s.Core().Action("notification.clear").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: notification.TaskClear{}}, )) @@ -497,7 +497,7 @@ func (s *Service) ClearNotifications() error { return nil } -func (s *Service) SetTheme(theme string) error { +func (s *Service) SetTheme(theme string) resultFailure { result := s.Core().Action("environment.setTheme").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: environment.TaskSetTheme{Theme: theme}}, )) @@ -542,7 +542,7 @@ func themeInfoFromQueryResult(s *Service, method string, r core.Result) (environ return environment.ThemeInfo{}, false } -func (s *Service) sendNotification(opts notification.NotificationOptions) error { +func (s *Service) sendNotification(opts notification.NotificationOptions) resultFailure { result := s.Core().Action("notification.send").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: notification.TaskSend{Options: opts}}, )) diff --git a/go/pkg/display/api_wrappers_test.go b/go/pkg/display/api_wrappers_test.go index 8389a3fb..9fd5ae01 100644 --- a/go/pkg/display/api_wrappers_test.go +++ b/go/pkg/display/api_wrappers_test.go @@ -13,7 +13,7 @@ import ( type errorOnlyWrapperCase struct { name string action string - call func(*Service) error + call func(*Service) resultFailure setupGood func(*core.T, *core.Core) } @@ -56,7 +56,7 @@ func TestDisplayAPI_TrayWrappers(t *core.T) { { name: "SetTrayTooltip", action: "systray.setTooltip", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.SetTrayTooltip("Helper tooltip") }, setupGood: func(t *core.T, c *core.Core) { @@ -71,7 +71,7 @@ func TestDisplayAPI_TrayWrappers(t *core.T) { { name: "SetTrayLabel", action: "systray.setLabel", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.SetTrayLabel("Launcher") }, setupGood: func(t *core.T, c *core.Core) { @@ -86,7 +86,7 @@ func TestDisplayAPI_TrayWrappers(t *core.T) { { name: "SetTrayMenu", action: "systray.setMenu", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.SetTrayMenu([]TrayMenuItem{ {Label: "Open", ActionID: "open"}, { @@ -111,7 +111,7 @@ func TestDisplayAPI_TrayWrappers(t *core.T) { { name: "ShowTrayMessage", action: "systray.showMessage", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.ShowTrayMessage("Status", "Task complete") }, setupGood: func(t *core.T, c *core.Core) { @@ -138,7 +138,7 @@ func TestDisplayAPI_ClipboardWrappers(t *core.T) { runErrorOnlyWrapperCase(t, errorOnlyWrapperCase{ name: "ClearClipboard", action: "clipboard.clear", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.ClearClipboard() }, setupGood: func(t *core.T, c *core.Core) { @@ -204,7 +204,7 @@ func TestDisplayAPI_NotificationWrappers(t *core.T) { { name: "ShowNotification", action: "notification.send", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.ShowNotification(NotificationOptions{ ID: "alert-1", Title: "Deploy", @@ -229,7 +229,7 @@ func TestDisplayAPI_NotificationWrappers(t *core.T) { { name: "ShowInfoNotification", action: "notification.send", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.ShowInfoNotification("Info", "Ready") }, setupGood: func(t *core.T, c *core.Core) { @@ -247,7 +247,7 @@ func TestDisplayAPI_NotificationWrappers(t *core.T) { { name: "ShowWarningNotification", action: "notification.send", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.ShowWarningNotification("Warn", "Careful") }, setupGood: func(t *core.T, c *core.Core) { @@ -266,7 +266,7 @@ func TestDisplayAPI_NotificationWrappers(t *core.T) { { name: "ShowErrorNotification", action: "notification.send", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.ShowErrorNotification("Error", "Failed") }, setupGood: func(t *core.T, c *core.Core) { @@ -285,7 +285,7 @@ func TestDisplayAPI_NotificationWrappers(t *core.T) { { name: "ClearNotifications", action: "notification.clear", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.ClearNotifications() }, setupGood: func(t *core.T, c *core.Core) { @@ -310,7 +310,7 @@ func TestDisplayAPI_ThemeWrapper(t *core.T) { runErrorOnlyWrapperCase(t, errorOnlyWrapperCase{ name: "SetTheme", action: "environment.setTheme", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.SetTheme("system") }, setupGood: func(t *core.T, c *core.Core) { diff --git a/go/pkg/display/core_helpers.go b/go/pkg/display/core_helpers.go index 686db181..7b798df2 100644 --- a/go/pkg/display/core_helpers.go +++ b/go/pkg/display/core_helpers.go @@ -8,7 +8,7 @@ import ( var sidecarWarningWriter core.Writer = core.Stderr() -func coreResultError(result core.Result, fallback string) error { +func coreResultError(result core.Result, fallback string) resultFailure { if result.OK { return nil } @@ -21,31 +21,31 @@ func coreResultError(result core.Result, fallback string) error { return core.NewError(fallback) } -func coreMkdirAll(path string, mode core.FileMode) error { +func coreMkdirAll(path string, mode core.FileMode) resultFailure { return coreResultError(core.MkdirAll(path, mode), "failed to create directory") } -func coreMkdir(path string, mode core.FileMode) error { +func coreMkdir(path string, mode core.FileMode) resultFailure { return coreResultError(core.Mkdir(path, mode), "failed to create directory") } -func coreRemoveAll(path string) error { +func coreRemoveAll(path string) resultFailure { return coreResultError(core.RemoveAll(path), "failed to remove path") } -func coreWriteFile(path string, data []byte, mode core.FileMode) error { +func coreWriteFile(path string, data []byte, mode core.FileMode) resultFailure { return coreResultError(core.WriteFile(path, data, mode), "failed to write file") } -func coreWriteMode(path, content string, mode core.FileMode) error { +func coreWriteMode(path, content string, mode core.FileMode) resultFailure { return coreWriteFile(path, []byte(content), mode) } -func coreEnsureDir(path string) error { +func coreEnsureDir(path string) resultFailure { return coreMkdirAll(path, 0o755) } -func coreReadFile(path string) ([]byte, error) { +func coreReadFile(path string) ([]byte, resultFailure) { result := core.ReadFile(path) if !result.OK { return nil, coreResultError(result, "failed to read file") @@ -53,7 +53,7 @@ func coreReadFile(path string) ([]byte, error) { return result.Value.([]byte), nil } -func coreStat(path string) (core.FsFileInfo, error) { +func coreStat(path string) (core.FsFileInfo, resultFailure) { result := core.Stat(path) if !result.OK { return nil, coreResultError(result, "failed to stat path") @@ -61,7 +61,7 @@ func coreStat(path string) (core.FsFileInfo, error) { return result.Value.(core.FsFileInfo), nil } -func coreLstat(path string) (core.FsFileInfo, error) { +func coreLstat(path string) (core.FsFileInfo, resultFailure) { result := core.Lstat(path) if !result.OK { return nil, coreResultError(result, "failed to stat path") @@ -69,7 +69,7 @@ func coreLstat(path string) (core.FsFileInfo, error) { return result.Value.(core.FsFileInfo), nil } -func coreHostname() (string, error) { +func coreHostname() (string, resultFailure) { result := core.Hostname() if !result.OK { return "", coreResultError(result, "failed to read hostname") @@ -77,7 +77,7 @@ func coreHostname() (string, error) { return result.Value.(string), nil } -func coreUserConfigDir() (string, error) { +func coreUserConfigDir() (string, resultFailure) { result := core.UserConfigDir() if !result.OK { return "", coreResultError(result, "failed to read config dir") @@ -85,7 +85,7 @@ func coreUserConfigDir() (string, error) { return result.Value.(string), nil } -func coreUserHomeDir() (string, error) { +func coreUserHomeDir() (string, resultFailure) { result := core.UserHomeDir() if !result.OK { return "", coreResultError(result, "failed to read home dir") @@ -93,7 +93,7 @@ func coreUserHomeDir() (string, error) { return result.Value.(string), nil } -func pathAbs(path string) (string, error) { +func pathAbs(path string) (string, resultFailure) { result := core.PathAbs(path) if !result.OK { return "", coreResultError(result, "failed to make path absolute") @@ -101,7 +101,7 @@ func pathAbs(path string) (string, error) { return result.Value.(string), nil } -func pathEvalSymlinks(path string) (string, error) { +func pathEvalSymlinks(path string) (string, resultFailure) { result := core.PathEvalSymlinks(path) if !result.OK { return "", coreResultError(result, "failed to resolve symlinks") @@ -109,7 +109,7 @@ func pathEvalSymlinks(path string) (string, error) { return result.Value.(string), nil } -func pathRel(base, target string) (string, error) { +func pathRel(base, target string) (string, resultFailure) { result := core.PathRel(base, target) if !result.OK { return "", coreResultError(result, "failed to compare paths") @@ -137,7 +137,7 @@ func pathFromSlash(path string) string { return core.Replace(path, "/", string(core.PathSeparator)) } -func jsonMarshal(value any) ([]byte, error) { +func jsonMarshal(value any) ([]byte, resultFailure) { result := core.JSONMarshal(value) if !result.OK { return nil, coreResultError(result, "failed to encode JSON") @@ -145,7 +145,7 @@ func jsonMarshal(value any) ([]byte, error) { return result.Value.([]byte), nil } -func jsonUnmarshal(data []byte, target any) error { +func jsonUnmarshal(data []byte, target any) resultFailure { return coreResultError(core.JSONUnmarshal(data, target), "failed to decode JSON") } @@ -209,7 +209,7 @@ func indexString(value, needle string) int { return -1 } -func lookPath(file string) (string, error) { +func lookPath(file string) (string, resultFailure) { name := core.Trim(file) if name == "" { return "", core.NewError("executable name is empty") diff --git a/go/pkg/display/display.go b/go/pkg/display/display.go index c57ac6de..028af41b 100644 --- a/go/pkg/display/display.go +++ b/go/pkg/display/display.go @@ -33,7 +33,7 @@ import ( type Options struct{} -func failedAction(method, action string) error { +func failedAction(method, action string) resultFailure { return core.E(method, action+" action failed", nil) } @@ -66,7 +66,7 @@ type Service struct { // New returns a display Service with empty config sections. // s, _ := display.New(); s.loadConfigFrom("/path/to/config.yaml") -func New() (*Service, error) { +func New() (*Service, resultFailure) { return &Service{ configData: map[string]map[string]any{ "window": {}, @@ -220,7 +220,7 @@ func (s *Service) OnShutdown(ctx context.Context) core.Result { if events != nil { events.Close() } - var shutdownErr error + var shutdownErr resultFailure if s.storage != nil { if err := s.storage.Close(); err != nil { shutdownErr = err @@ -519,7 +519,7 @@ type WSMessage struct { } // requireStringField extracts a string field from WebSocket data and fails when it is missing. -func requireStringField(data map[string]any, key string) (string, error) { +func requireStringField(data map[string]any, key string) (string, resultFailure) { v, _ := data[key].(string) if v == "" { return "", core.E("display.requireStringField", "missing required field \""+key+"\"", nil) @@ -528,11 +528,11 @@ func requireStringField(data map[string]any, key string) (string, error) { } // wsRequire is kept for backward compatibility inside the display package. -func wsRequire(data map[string]any, key string) (string, error) { +func wsRequire(data map[string]any, key string) (string, resultFailure) { return requireStringField(data, key) } -func requireFloatField(data map[string]any, key string) (float64, error) { +func requireFloatField(data map[string]any, key string) (float64, resultFailure) { value, ok := data[key] if !ok || value == nil { return 0, core.E("display.handleWSMessage", "missing required field \""+key+"\"", nil) @@ -575,7 +575,7 @@ func requireFloatField(data map[string]any, key string) (float64, error) { } } -func intFromFloatField(value float64, key string) (int, error) { +func intFromFloatField(value float64, key string) (int, resultFailure) { if math.IsNaN(value) || math.IsInf(value, 0) { return 0, core.E("display.handleWSMessage", "invalid required field \""+key+"\"", nil) } @@ -588,7 +588,7 @@ func intFromFloatField(value float64, key string) (int, error) { return int(value), nil } -func requireIntField(data map[string]any, key string) (int, error) { +func requireIntField(data map[string]any, key string) (int, resultFailure) { value, ok := data[key] if !ok || value == nil { return 0, core.E("display.handleWSMessage", "missing required field \""+key+"\"", nil) @@ -807,7 +807,7 @@ func (s *Service) handleWSMessage(msg WSMessage) core.Result { } marshalResult := core.JSONMarshal(menuValue) if !marshalResult.OK { - if err, ok := marshalResult.Value.(error); ok { + if err, ok := marshalResult.Value.(resultFailure); ok { return core.Result{Value: core.E("display.handleWSMessage", "failed to marshal menu definition", err), OK: false} } return core.Result{Value: core.E("display.handleWSMessage", "failed to marshal menu definition", nil), OK: false} @@ -816,7 +816,7 @@ func (s *Service) handleWSMessage(msg WSMessage) core.Result { menuJSON, _ := marshalResult.Value.([]byte) unmarshalResult := core.JSONUnmarshal(menuJSON, &menuDef) if !unmarshalResult.OK { - if err, ok := unmarshalResult.Value.(error); ok { + if err, ok := unmarshalResult.Value.(resultFailure); ok { return core.Result{Value: core.E("display.handleWSMessage", "failed to unmarshal menu definition", err), OK: false} } return core.Result{Value: core.E("display.handleWSMessage", "failed to unmarshal menu definition", nil), OK: false} @@ -1222,7 +1222,7 @@ func (s *Service) handleConfigQuery(_ *core.Core, q core.Query) core.Result { } } -func (s *Service) persistSection(key string, value map[string]any) error { +func (s *Service) persistSection(key string, value map[string]any) resultFailure { if s.configFile == nil { return nil } @@ -1252,7 +1252,7 @@ func (s *Service) windowService() *window.Service { // --- Window Management (delegates via IPC) --- // OpenWindow creates a new window via IPC. -func (s *Service) OpenWindow(options ...window.WindowOption) error { +func (s *Service) OpenWindow(options ...window.WindowOption) resultFailure { spec, err := window.ApplyOptions(options...) if err != nil { return err @@ -1261,7 +1261,7 @@ func (s *Service) OpenWindow(options ...window.WindowOption) error { core.Option{Key: "task", Value: window.TaskOpenWindow{Window: spec}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return core.E("display.OpenWindow", "window.open action failed", nil) @@ -1270,10 +1270,10 @@ func (s *Service) OpenWindow(options ...window.WindowOption) error { } // GetWindowInfo returns information about a window via IPC. -func (s *Service) GetWindowInfo(name string) (*window.WindowInfo, error) { +func (s *Service) GetWindowInfo(name string) (*window.WindowInfo, resultFailure) { r := s.Core().QUERY(window.QueryWindowByName{Name: name}) if !r.OK { - if err, ok := r.Value.(error); ok { + if err, ok := r.Value.(resultFailure); ok { return nil, err } return nil, failedAction("display.GetWindowInfo", "window.queryWindowByName") @@ -1302,12 +1302,12 @@ func (s *Service) ListWindowInfos() []window.WindowInfo { } // SetWindowPosition moves a window via IPC. -func (s *Service) SetWindowPosition(name string, x, y int) error { +func (s *Service) SetWindowPosition(name string, x, y int) resultFailure { r := s.Core().Action("window.setPosition").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetPosition{Name: name, X: x, Y: y}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.SetWindowPosition", "window.setPosition") @@ -1316,12 +1316,12 @@ func (s *Service) SetWindowPosition(name string, x, y int) error { } // SetWindowSize resizes a window via IPC. -func (s *Service) SetWindowSize(name string, width, height int) error { +func (s *Service) SetWindowSize(name string, width, height int) resultFailure { r := s.Core().Action("window.setSize").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetSize{Name: name, Width: width, Height: height}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.SetWindowSize", "window.setSize") @@ -1330,7 +1330,7 @@ func (s *Service) SetWindowSize(name string, width, height int) error { } // SetWindowBounds sets both position and size of a window via IPC. -func (s *Service) SetWindowBounds(name string, x, y, width, height int) error { +func (s *Service) SetWindowBounds(name string, x, y, width, height int) resultFailure { if err := s.SetWindowPosition(name, x, y); err != nil { return err } @@ -1338,12 +1338,12 @@ func (s *Service) SetWindowBounds(name string, x, y, width, height int) error { } // MaximizeWindow maximizes a window via IPC. -func (s *Service) MaximizeWindow(name string) error { +func (s *Service) MaximizeWindow(name string) resultFailure { r := s.Core().Action("window.maximise").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskMaximise{Name: name}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.MaximizeWindow", "window.maximise") @@ -1352,12 +1352,12 @@ func (s *Service) MaximizeWindow(name string) error { } // MinimizeWindow minimizes a window via IPC. -func (s *Service) MinimizeWindow(name string) error { +func (s *Service) MinimizeWindow(name string) resultFailure { r := s.Core().Action("window.minimise").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskMinimise{Name: name}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.MinimizeWindow", "window.minimise") @@ -1366,12 +1366,12 @@ func (s *Service) MinimizeWindow(name string) error { } // FocusWindow brings a window to the front via IPC. -func (s *Service) FocusWindow(name string) error { +func (s *Service) FocusWindow(name string) resultFailure { r := s.Core().Action("window.focus").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskFocus{Name: name}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.FocusWindow", "window.focus") @@ -1380,12 +1380,12 @@ func (s *Service) FocusWindow(name string) error { } // CloseWindow closes a window via IPC. -func (s *Service) CloseWindow(name string) error { +func (s *Service) CloseWindow(name string) resultFailure { r := s.Core().Action("window.close").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskCloseWindow{Name: name}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.CloseWindow", "window.close") @@ -1394,12 +1394,12 @@ func (s *Service) CloseWindow(name string) error { } // RestoreWindow restores a maximized/minimized window. -func (s *Service) RestoreWindow(name string) error { +func (s *Service) RestoreWindow(name string) resultFailure { r := s.Core().Action("window.restore").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskRestore{Name: name}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.RestoreWindow", "window.restore") @@ -1408,12 +1408,12 @@ func (s *Service) RestoreWindow(name string) error { } // SetWindowVisibility shows or hides a window. -func (s *Service) SetWindowVisibility(name string, visible bool) error { +func (s *Service) SetWindowVisibility(name string, visible bool) resultFailure { r := s.Core().Action("window.setVisibility").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetVisibility{Name: name, Visible: visible}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.SetWindowVisibility", "window.setVisibility") @@ -1422,12 +1422,12 @@ func (s *Service) SetWindowVisibility(name string, visible bool) error { } // SetWindowAlwaysOnTop sets whether a window stays on top. -func (s *Service) SetWindowAlwaysOnTop(name string, alwaysOnTop bool) error { +func (s *Service) SetWindowAlwaysOnTop(name string, alwaysOnTop bool) resultFailure { r := s.Core().Action("window.setAlwaysOnTop").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetAlwaysOnTop{Name: name, AlwaysOnTop: alwaysOnTop}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.SetWindowAlwaysOnTop", "window.setAlwaysOnTop") @@ -1436,12 +1436,12 @@ func (s *Service) SetWindowAlwaysOnTop(name string, alwaysOnTop bool) error { } // SetWindowTitle changes a window's title. -func (s *Service) SetWindowTitle(name string, title string) error { +func (s *Service) SetWindowTitle(name string, title string) resultFailure { r := s.Core().Action("window.setTitle").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetTitle{Name: name, Title: title}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.SetWindowTitle", "window.setTitle") @@ -1450,12 +1450,12 @@ func (s *Service) SetWindowTitle(name string, title string) error { } // SetWindowFullscreen sets a window to fullscreen mode. -func (s *Service) SetWindowFullscreen(name string, fullscreen bool) error { +func (s *Service) SetWindowFullscreen(name string, fullscreen bool) resultFailure { r := s.Core().Action("window.fullscreen").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskFullscreen{Name: name, Fullscreen: fullscreen}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.SetWindowFullscreen", "window.fullscreen") @@ -1464,14 +1464,14 @@ func (s *Service) SetWindowFullscreen(name string, fullscreen bool) error { } // SetWindowBackgroundColour sets the background colour of a window. -func (s *Service) SetWindowBackgroundColour(name string, r, g, b, a uint8) error { +func (s *Service) SetWindowBackgroundColour(name string, r, g, b, a uint8) resultFailure { result := s.Core().Action("window.setBackgroundColour").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetBackgroundColour{ Name: name, Red: r, Green: g, Blue: b, Alpha: a, }}, )) if !result.OK { - if e, ok := result.Value.(error); ok { + if e, ok := result.Value.(resultFailure); ok { return e } return failedAction("display.SetWindowBackgroundColour", "window.setBackgroundColour") @@ -1491,7 +1491,7 @@ func (s *Service) GetFocusedWindow() string { } // GetWindowTitle returns the title of a window by name. -func (s *Service) GetWindowTitle(name string) (string, error) { +func (s *Service) GetWindowTitle(name string) (string, resultFailure) { info, err := s.GetWindowInfo(name) if err != nil { return "", err @@ -1503,7 +1503,7 @@ func (s *Service) GetWindowTitle(name string) (string, error) { } // ResetWindowState clears saved window positions. -func (s *Service) ResetWindowState() error { +func (s *Service) ResetWindowState() resultFailure { ws := s.windowService() if ws != nil { ws.Manager().State().Clear() @@ -1538,7 +1538,7 @@ type CreateWindowOptions struct { Height int `json:"height,omitempty"` } -func (s *Service) CreateWindow(options CreateWindowOptions) (*window.WindowInfo, error) { +func (s *Service) CreateWindow(options CreateWindowOptions) (*window.WindowInfo, resultFailure) { if options.Name == "" { return nil, core.E("display.CreateWindow", "window name is required", nil) } @@ -1556,7 +1556,7 @@ func (s *Service) CreateWindow(options CreateWindowOptions) (*window.WindowInfo, }}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return nil, e } return nil, core.E("display.CreateWindow", "window.open action failed", nil) @@ -1571,12 +1571,12 @@ func (s *Service) CreateWindow(options CreateWindowOptions) (*window.WindowInfo, // --- Layout delegation --- // SaveLayout saves the current window arrangement as a named layout. -func (s *Service) SaveLayout(name string) error { +func (s *Service) SaveLayout(name string) resultFailure { r := s.Core().Action("window.saveLayout").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSaveLayout{Name: name}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.SaveLayout", "window.saveLayout") @@ -1585,12 +1585,12 @@ func (s *Service) SaveLayout(name string) error { } // RestoreLayout applies a saved layout. -func (s *Service) RestoreLayout(name string) error { +func (s *Service) RestoreLayout(name string) resultFailure { r := s.Core().Action("window.restoreLayout").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskRestoreLayout{Name: name}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.RestoreLayout", "window.restoreLayout") @@ -1612,12 +1612,12 @@ func (s *Service) ListLayouts() []window.LayoutInfo { } // DeleteLayout removes a saved layout by name. -func (s *Service) DeleteLayout(name string) error { +func (s *Service) DeleteLayout(name string) resultFailure { r := s.Core().Action("window.deleteLayout").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskDeleteLayout{Name: name}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.DeleteLayout", "window.deleteLayout") @@ -1644,12 +1644,12 @@ func (s *Service) GetLayout(name string) *window.Layout { // --- Tiling/snapping delegation --- // TileWindows arranges windows in a tiled layout. -func (s *Service) TileWindows(mode window.TileMode, windowNames []string) error { +func (s *Service) TileWindows(mode window.TileMode, windowNames []string) resultFailure { r := s.Core().Action("window.tileWindows").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskTileWindows{Mode: mode.String(), Windows: windowNames}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.TileWindows", "window.tileWindows") @@ -1658,12 +1658,12 @@ func (s *Service) TileWindows(mode window.TileMode, windowNames []string) error } // SnapWindow snaps a window to a screen edge or corner. -func (s *Service) SnapWindow(name string, position window.SnapPosition) error { +func (s *Service) SnapWindow(name string, position window.SnapPosition) resultFailure { r := s.Core().Action("window.snapWindow").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSnapWindow{Name: name, Position: position.String()}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.SnapWindow", "window.snapWindow") @@ -1672,12 +1672,12 @@ func (s *Service) SnapWindow(name string, position window.SnapPosition) error { } // StackWindows arranges windows in a cascade pattern. -func (s *Service) StackWindows(windowNames []string, offsetX, offsetY int) error { +func (s *Service) StackWindows(windowNames []string, offsetX, offsetY int) resultFailure { r := s.Core().Action("window.stackWindows").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskStackWindows{Windows: windowNames, OffsetX: offsetX, OffsetY: offsetY}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.StackWindows", "window.stackWindows") @@ -1686,12 +1686,12 @@ func (s *Service) StackWindows(windowNames []string, offsetX, offsetY int) error } // ApplyWorkflowLayout applies a predefined layout for a specific workflow. -func (s *Service) ApplyWorkflowLayout(workflow window.WorkflowLayout) error { +func (s *Service) ApplyWorkflowLayout(workflow window.WorkflowLayout) resultFailure { r := s.Core().Action("window.applyWorkflow").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskApplyWorkflow{Workflow: workflow.String()}}, )) if !r.OK { - if e, ok := r.Value.(error); ok { + if e, ok := r.Value.(resultFailure); ok { return e } return failedAction("display.ApplyWorkflowLayout", "window.applyWorkflow") @@ -1702,14 +1702,14 @@ func (s *Service) ApplyWorkflowLayout(workflow window.WorkflowLayout) error { // 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) { +func (s *Service) LayoutBesideEditor(name, editor, side string, ratio float64) (window.LayoutBesideEditorResult, resultFailure) { 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 { + if e, ok := r.Value.(resultFailure); ok { return window.LayoutBesideEditorResult{}, e } return window.LayoutBesideEditorResult{}, failedAction("display.LayoutBesideEditor", "window.layoutBesideEditor") @@ -1724,14 +1724,14 @@ func (s *Service) LayoutBesideEditor(name, editor, side string, ratio float64) ( // 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) { +func (s *Service) LayoutSuggest(screenID string, windowCount int) (window.LayoutSuggestion, resultFailure) { 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 { + if e, ok := r.Value.(resultFailure); ok { return window.LayoutSuggestion{}, e } return window.LayoutSuggestion{}, failedAction("display.LayoutSuggest", "window.layoutSuggest") @@ -1746,14 +1746,14 @@ func (s *Service) LayoutSuggest(screenID string, windowCount int) (window.Layout // 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) { +func (s *Service) FindScreenSpace(screenID string, width, height, padding int) (window.ScreenSpace, resultFailure) { 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 { + if e, ok := r.Value.(resultFailure); ok { return window.ScreenSpace{}, e } return window.ScreenSpace{}, failedAction("display.FindScreenSpace", "window.findSpace") @@ -1768,14 +1768,14 @@ func (s *Service) FindScreenSpace(screenID string, width, height, padding int) ( // 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) { +func (s *Service) ArrangeWindowPair(primary, secondary, screenID string, ratio float64) (window.PairArrangement, resultFailure) { 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 { + if e, ok := r.Value.(resultFailure); ok { return window.PairArrangement{}, e } return window.PairArrangement{}, failedAction("display.ArrangeWindowPair", "window.arrangePair") diff --git a/go/pkg/display/display_contextmenu_test.go b/go/pkg/display/display_contextmenu_test.go index afc11d0c..f49c02cf 100644 --- a/go/pkg/display/display_contextmenu_test.go +++ b/go/pkg/display/display_contextmenu_test.go @@ -18,14 +18,14 @@ func newWSContextMenuPlatform() *wsContextMenuPlatform { } } -func (m *wsContextMenuPlatform) Add(name string, menu contextmenu.ContextMenuDef, _ func(string, string, string)) error { +func (m *wsContextMenuPlatform) Add(name string, menu contextmenu.ContextMenuDef, _ func(string, string, string)) resultFailure { m.mu.Lock() defer m.mu.Unlock() m.menus[name] = menu return nil } -func (m *wsContextMenuPlatform) Remove(name string) error { +func (m *wsContextMenuPlatform) Remove(name string) resultFailure { m.mu.Lock() defer m.mu.Unlock() delete(m.menus, name) @@ -70,7 +70,7 @@ func TestDisplay_handleWSMessage_ContextMenuAdd_MissingMenu(t *core.T) { }) core.AssertFalse(t, result.OK) - err, ok := result.Value.(error) + err, ok := result.Value.(resultFailure) core.RequireTrue(t, ok) core.AssertContains(t, err.Error(), `missing required field "menu"`) _, ok = platform.Get("menu") diff --git a/go/pkg/display/display_layout_wrappers_test.go b/go/pkg/display/display_layout_wrappers_test.go index d6d726e3..323fc8ed 100644 --- a/go/pkg/display/display_layout_wrappers_test.go +++ b/go/pkg/display/display_layout_wrappers_test.go @@ -11,7 +11,7 @@ type layoutResultWrapperCase struct { name string action string zero any - call func(*Service) (any, error) + call func(*Service) (any, resultFailure) setupGood func(*core.T, *core.Core) wantGood func(*core.T, any) } @@ -72,7 +72,7 @@ func TestDisplay_LayoutDelegationWrappers(t *core.T) { { name: "DeleteLayout", action: "window.deleteLayout", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.DeleteLayout("development") }, setupGood: func(t *core.T, c *core.Core) { @@ -87,7 +87,7 @@ func TestDisplay_LayoutDelegationWrappers(t *core.T) { { name: "TileWindows", action: "window.tileWindows", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.TileWindows(window.TileModeGrid, []string{"editor", "terminal"}) }, setupGood: func(t *core.T, c *core.Core) { @@ -103,7 +103,7 @@ func TestDisplay_LayoutDelegationWrappers(t *core.T) { { name: "SnapWindow", action: "window.snapWindow", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.SnapWindow("preview", window.SnapCenter) }, setupGood: func(t *core.T, c *core.Core) { @@ -119,7 +119,7 @@ func TestDisplay_LayoutDelegationWrappers(t *core.T) { { name: "StackWindows", action: "window.stackWindows", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.StackWindows([]string{"editor", "preview"}, 24, 18) }, setupGood: func(t *core.T, c *core.Core) { @@ -136,7 +136,7 @@ func TestDisplay_LayoutDelegationWrappers(t *core.T) { { name: "ApplyWorkflowLayout", action: "window.applyWorkflow", - call: func(svc *Service) error { + call: func(svc *Service) resultFailure { return svc.ApplyWorkflowLayout(window.WorkflowCoding) }, setupGood: func(t *core.T, c *core.Core) { @@ -160,7 +160,7 @@ func TestDisplay_LayoutDelegationWrappers(t *core.T) { name: "LayoutBesideEditor", action: "window.layoutBesideEditor", zero: window.LayoutBesideEditorResult{}, - call: func(svc *Service) (any, error) { + call: func(svc *Service) (any, resultFailure) { return svc.LayoutBesideEditor("preview", "code", "right", 0.62) }, setupGood: func(t *core.T, c *core.Core) { @@ -200,7 +200,7 @@ func TestDisplay_LayoutDelegationWrappers(t *core.T) { name: "FindScreenSpace", action: "window.findSpace", zero: window.ScreenSpace{}, - call: func(svc *Service) (any, error) { + call: func(svc *Service) (any, resultFailure) { return svc.FindScreenSpace("screen-1", 800, 600, 24) }, setupGood: func(t *core.T, c *core.Core) { @@ -238,7 +238,7 @@ func TestDisplay_LayoutDelegationWrappers(t *core.T) { name: "ArrangeWindowPair", action: "window.arrangePair", zero: window.PairArrangement{}, - call: func(svc *Service) (any, error) { + call: func(svc *Service) (any, resultFailure) { return svc.ArrangeWindowPair("editor", "preview", "screen-1", 0.55) }, setupGood: func(t *core.T, c *core.Core) { diff --git a/go/pkg/display/display_test.go b/go/pkg/display/display_test.go index c444edd2..75d2e400 100644 --- a/go/pkg/display/display_test.go +++ b/go/pkg/display/display_test.go @@ -335,7 +335,7 @@ func TestStorageTask_Bad(t *core.T) { )) core.AssertFalse(t, r.OK) - core.AssertContains(t, r.Value.(error).Error(), "invalid storage entry") + core.AssertContains(t, r.Value.(resultFailure).Error(), "invalid storage entry") } func TestResolveScheme_StoreRoute_GoodCase(t *core.T) { @@ -925,7 +925,7 @@ func TestDisplay_handleWSMessage_Bad(t *core.T) { result := svc.handleWSMessage(WSMessage{Action: "unknown:action"}) core.AssertFalse(t, result.OK) - core.AssertContains(t, result.Value.(error).Error(), "unknown websocket action") + core.AssertContains(t, result.Value.(resultFailure).Error(), "unknown websocket action") } func TestDisplay_handleWSMessage_Ugly(t *core.T) { @@ -941,7 +941,7 @@ func TestDisplay_handleWSMessage_Ugly(t *core.T) { }) core.AssertFalse(t, result.OK) - core.AssertContains(t, result.Value.(error).Error(), "missing required field \"opacity\"") + core.AssertContains(t, result.Value.(resultFailure).Error(), "missing required field \"opacity\"") } func TestDisplay_handleWSMessage_RejectsFloatOverflow(t *core.T) { @@ -1135,7 +1135,7 @@ func TestDisplay_handleWSMessage_LayoutCommands_Bad(t *core.T) { core.AssertFalse(t, result.OK) core.AssertFalse(t, called) - core.AssertContains(t, result.Value.(error).Error(), "missing required field \""+tc.field+"\"") + core.AssertContains(t, result.Value.(resultFailure).Error(), "missing required field \""+tc.field+"\"") }) } } @@ -1219,7 +1219,7 @@ func TestDisplay_handleWSMessage_LayoutCommands_Ugly(t *core.T) { core.AssertFalse(t, result.OK) core.AssertFalse(t, called) - core.AssertContains(t, result.Value.(error).Error(), "invalid required field \""+tc.field+"\"") + core.AssertContains(t, result.Value.(resultFailure).Error(), "invalid required field \""+tc.field+"\"") }) } } diff --git a/go/pkg/display/events_test.go b/go/pkg/display/events_test.go index a05ebb2d..9e99558d 100644 --- a/go/pkg/display/events_test.go +++ b/go/pkg/display/events_test.go @@ -64,7 +64,7 @@ func (w *testWindowListener) ToggleFullscreen() {} func (w *testWindowListener) ToggleMaximise() {} func (w *testWindowListener) ExecJS(string) {} func (w *testWindowListener) Flash(bool) {} -func (w *testWindowListener) Print() error { return nil } +func (w *testWindowListener) Print() resultFailure { return nil } func (w *testWindowListener) OnWindowEvent(handler func(window.WindowEvent)) { w.handler = handler } diff --git a/go/pkg/display/hlcrf.go b/go/pkg/display/hlcrf.go index a966ef0e..bfaf73c2 100644 --- a/go/pkg/display/hlcrf.go +++ b/go/pkg/display/hlcrf.go @@ -8,7 +8,7 @@ import ( var hlcrfSlotPattern = regexp.MustCompile(`\{\{\s*slot\s+"([^"]*)"\s*\}\}`) -func (s *Service) buildHLCRFComponents(pageURL string) (string, error) { +func (s *Service) buildHLCRFComponents(pageURL string) (string, resultFailure) { loaded, err := s.loadManifestForOrigin(pageURL) if err != nil || loaded == nil { if err != nil && core.Contains(err.Error(), "view manifest not found") { diff --git a/go/pkg/display/manifest.go b/go/pkg/display/manifest.go index 935e7a68..469fe6d8 100644 --- a/go/pkg/display/manifest.go +++ b/go/pkg/display/manifest.go @@ -45,7 +45,7 @@ type loadedManifest struct { Manifest ViewManifest } -func (s *Service) loadManifestForOrigin(pageURL string) (*loadedManifest, error) { +func (s *Service) loadManifestForOrigin(pageURL string) (*loadedManifest, resultFailure) { s.manifestMu.Lock() defer s.manifestMu.Unlock() if s.manifestCache == nil { @@ -98,11 +98,11 @@ func manifestBaseDir(manifestPath string) string { return baseDir } -func safeManifestPreloadPath(baseDir, preloadPath string) (string, error) { +func safeManifestPreloadPath(baseDir, preloadPath string) (string, resultFailure) { return safeManifestRelativePath(baseDir, preloadPath, "preload path") } -func safeManifestRelativePath(baseDir, relativePath, label string) (string, error) { +func safeManifestRelativePath(baseDir, relativePath, label string) (string, resultFailure) { trimmed := core.Trim(relativePath) if trimmed == "" { return "", core.E("display.safeManifestRelativePath", label+" is empty", nil) @@ -147,7 +147,7 @@ func safeManifestRelativePath(baseDir, relativePath, label string) (string, erro return candidateResolved, nil } -func discoverManifestPath(pageURL string) (string, error) { +func discoverManifestPath(pageURL string) (string, resultFailure) { trimmed := core.Trim(pageURL) fsys := (&core.Fs{}).NewUnrestricted() candidates := make([]string, 0, 4) @@ -202,7 +202,7 @@ func appendLocalManifestCandidates(candidates *[]string, fsys *core.Fs, path str *candidates = append(*candidates, core.PathJoin(core.PathDir(dir), ".core", "view.yaml")) } -func manifestHostPathComponent(parsed *url.URL) (string, error) { +func manifestHostPathComponent(parsed *url.URL) (string, resultFailure) { host := parsed.Hostname() if host == "" { return "", core.E("display.manifestHostPathComponent", "manifest host is empty", nil) @@ -213,7 +213,7 @@ func manifestHostPathComponent(parsed *url.URL) (string, error) { return host, nil } -func validateManifestHostPathComponent(host string) error { +func validateManifestHostPathComponent(host string) resultFailure { for i := 0; i < len(host); i++ { if host[i] < 0x20 || host[i] == 0x7f { return core.E("display.validateManifestHostPathComponent", "manifest host contains control character", nil) @@ -258,7 +258,7 @@ func (s *Service) manifestWindowConfig(pageURL string) map[string]ManifestWindow return windows } -func (s *Service) readManifestPreload(baseDir, preloadPath string) ([]byte, error) { +func (s *Service) readManifestPreload(baseDir, preloadPath string) ([]byte, resultFailure) { resolvedPath, err := safeManifestPreloadPath(baseDir, preloadPath) if err != nil { return nil, err diff --git a/go/pkg/display/manifest_test.go b/go/pkg/display/manifest_test.go index 5f57da3e..9f24cc79 100644 --- a/go/pkg/display/manifest_test.go +++ b/go/pkg/display/manifest_test.go @@ -270,7 +270,7 @@ func TestManifest_LoadManifestForOrigin_Concurrent(t *core.T) { core.RequireNoError(t, err) var wg sync.WaitGroup - errs := make(chan error, 16) + errs := make(chan resultFailure, 16) for i := 0; i < 16; i++ { wg.Add(1) go func() { diff --git a/go/pkg/display/marketplace.go b/go/pkg/display/marketplace.go index ced04a80..449d18bb 100644 --- a/go/pkg/display/marketplace.go +++ b/go/pkg/display/marketplace.go @@ -133,7 +133,10 @@ func marketplaceInstallRoot(raw string) string { return core.PathJoin(home, ".core", "apps") } -func (s *Service) marketplaceGitRunner() func(context.Context, string, ...string) ([]byte, error) { +func (s *Service) marketplaceGitRunner() func(context.Context, string, ...string) ( + []byte, + error, +) { if marketplaceGitRunner != nil { return marketplaceGitRunner } diff --git a/go/pkg/display/marketplace_test.go b/go/pkg/display/marketplace_test.go index 9c329d2e..681d2373 100644 --- a/go/pkg/display/marketplace_test.go +++ b/go/pkg/display/marketplace_test.go @@ -161,8 +161,8 @@ func TestMarketplace_registerMarketplaceActions_BadCase(t *core.T) { result := c.Action("display.marketplace.fetch").Run(context.Background(), core.NewOptions()) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "manifest url is required") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "manifest url is required") } func TestMarketplace_registerMarketplaceActions_UglyCase(t *core.T) { @@ -170,8 +170,8 @@ func TestMarketplace_registerMarketplaceActions_UglyCase(t *core.T) { result := c.Action("display.marketplace.install").Run(context.Background(), core.NewOptions()) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error)) - core.AssertContains(t, result.Value.(error).Error(), "manifest url is required") + core.AssertError(t, result.Value.(resultFailure)) + core.AssertContains(t, result.Value.(resultFailure).Error(), "manifest url is required") } func signedMarketplaceManifest(t *core.T, manifest marketplace.Manifest) marketplace.Manifest { diff --git a/go/pkg/display/p2p_test.go b/go/pkg/display/p2p_test.go index 78798c08..6be2290a 100644 --- a/go/pkg/display/p2p_test.go +++ b/go/pkg/display/p2p_test.go @@ -32,7 +32,7 @@ func newLoopbackP2PDriver() *loopbackP2PDriver { return &loopbackP2PDriver{handlers: make(map[string]func(p2p.Envelope))} } -func (d *loopbackP2PDriver) Publish(_ context.Context, envelope p2p.Envelope) error { +func (d *loopbackP2PDriver) Publish(_ context.Context, envelope p2p.Envelope) resultFailure { d.mu.Lock() handler := d.handlers[envelope.Topic] d.mu.Unlock() @@ -42,7 +42,7 @@ func (d *loopbackP2PDriver) Publish(_ context.Context, envelope p2p.Envelope) er return nil } -func (d *loopbackP2PDriver) Subscribe(_ context.Context, topic string, handler func(p2p.Envelope)) error { +func (d *loopbackP2PDriver) Subscribe(_ context.Context, topic string, handler func(p2p.Envelope)) resultFailure { d.mu.Lock() d.handlers[topic] = handler d.mu.Unlock() @@ -53,11 +53,11 @@ type immediateP2PDriver struct { envelope p2p.Envelope } -func (d *immediateP2PDriver) Publish(context.Context, p2p.Envelope) error { +func (d *immediateP2PDriver) Publish(context.Context, p2p.Envelope) resultFailure { return nil } -func (d *immediateP2PDriver) Subscribe(_ context.Context, topic string, handler func(p2p.Envelope)) error { +func (d *immediateP2PDriver) Subscribe(_ context.Context, topic string, handler func(p2p.Envelope)) resultFailure { if handler != nil { handler(p2p.Envelope{ Topic: topic, diff --git a/go/pkg/display/preload.go b/go/pkg/display/preload.go index ef4a7d24..196ed952 100644 --- a/go/pkg/display/preload.go +++ b/go/pkg/display/preload.go @@ -15,7 +15,7 @@ type PreloadTarget interface { // InjectPreload injects the page preload script into a live webview. // Use: _ = display.InjectPreload(webview, "https://example.com") -func (s *Service) InjectPreload(webview PreloadTarget, origin string) error { +func (s *Service) InjectPreload(webview PreloadTarget, origin string) resultFailure { script, err := s.BuildPreloadScript(origin) if err != nil { return err @@ -30,13 +30,13 @@ func (s *Service) InjectPreload(webview PreloadTarget, origin string) error { // BuildPreloadScript returns the JavaScript bootstrap that CoreGUI injects // before page code runs. // Use: script, _ := display.BuildPreloadScript("https://example.com") -func (s *Service) BuildPreloadScript(pageURL string) (string, error) { +func (s *Service) BuildPreloadScript(pageURL string) (string, resultFailure) { return s.BuildPreloadScriptWithTrustedOriginPolicy(pageURL, DefaultTrustedOriginPolicy()) } // BuildPreloadScriptWithTrustedOriginPolicy builds the preload script using // the caller-provided scheme-origin allow-list. -func (s *Service) BuildPreloadScriptWithTrustedOriginPolicy(pageURL string, policy TrustedOriginPolicy) (string, error) { +func (s *Service) BuildPreloadScriptWithTrustedOriginPolicy(pageURL string, policy TrustedOriginPolicy) (string, resultFailure) { trustedOrigin := trustedPreloadOrigin(pageURL, policy) manifestAllowed := manifestBackedPreloadOriginAllowedByPolicy(pageURL, policy) manifestBackedAllowed := manifestAllowed && s.manifestBackedPreloadOrigin(pageURL, policy) @@ -1331,7 +1331,7 @@ func (s *Service) injectCoreMLShim(trustedOrigin bool) string { })();` } -func (s *Service) injectAppPreloads(pageURL string) (string, error) { +func (s *Service) injectAppPreloads(pageURL string) (string, resultFailure) { loaded, err := s.loadManifestForOrigin(pageURL) if err != nil || loaded == nil { return "", err diff --git a/go/pkg/display/result_failure.go b/go/pkg/display/result_failure.go new file mode 100644 index 00000000..26bde364 --- /dev/null +++ b/go/pkg/display/result_failure.go @@ -0,0 +1,5 @@ +package display + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/display/scheme_handler_test.go b/go/pkg/display/scheme_handler_test.go index ea7aa8cc..d44a0194 100644 --- a/go/pkg/display/scheme_handler_test.go +++ b/go/pkg/display/scheme_handler_test.go @@ -137,7 +137,7 @@ func TestSchemeHandler_Handle_Bad(t *core.T) { result := handler.Handle(parsedURL) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error), "unknown core route: missing") + core.AssertError(t, result.Value.(resultFailure), "unknown core route: missing") } func TestSchemeHandler_Handle_Ugly(t *core.T) { @@ -151,7 +151,7 @@ func TestSchemeHandler_Handle_Ugly(t *core.T) { result := handler.Handle(parsedURL) core.AssertFalse(t, result.OK) - core.AssertError(t, result.Value.(error), "malformed core URL") + core.AssertError(t, result.Value.(resultFailure), "malformed core URL") } // AX7 generated source-matching smoke coverage. diff --git a/go/pkg/display/scheme_test.go b/go/pkg/display/scheme_test.go index 846378a4..0210598b 100644 --- a/go/pkg/display/scheme_test.go +++ b/go/pkg/display/scheme_test.go @@ -201,7 +201,7 @@ func TestScheme_ResolveSchemeRequest_BodyQuery_Bad(t *core.T) { []byte(repeatString("a", maxSchemeRequestBodyBytes+1)), ) core.AssertFalse(t, result.OK) - core.AssertContains(t, result.Value.(error).Error(), "request body exceeds") + core.AssertContains(t, result.Value.(resultFailure).Error(), "request body exceeds") } func TestScheme_ResolveScheme_Ugly(t *core.T) { diff --git a/go/pkg/display/sidecar.go b/go/pkg/display/sidecar.go index 4b2e8d60..de015fd5 100644 --- a/go/pkg/display/sidecar.go +++ b/go/pkg/display/sidecar.go @@ -77,7 +77,7 @@ func (s *Service) ensureSidecar() *deno.Manager { return s.sidecar } -func (s *Service) startSidecar(ctx context.Context) (deno.Status, error) { +func (s *Service) startSidecar(ctx context.Context) (deno.Status, resultFailure) { manager, err := s.sidecarForStart() if err != nil { s.sidecar = nil @@ -86,7 +86,7 @@ func (s *Service) startSidecar(ctx context.Context) (deno.Status, error) { return manager.Start(ctx) } -func (s *Service) sidecarForStart() (*deno.Manager, error) { +func (s *Service) sidecarForStart() (*deno.Manager, resultFailure) { options, err := sidecarLaunchOptions(s.coreRef()) if err != nil { return nil, err @@ -122,7 +122,7 @@ func (s *Service) newSidecar(options deno.Options) *deno.Manager { return manager } -func sidecarLaunchOptions(coreRef *core.Core) (deno.Options, error) { +func sidecarLaunchOptions(coreRef *core.Core) (deno.Options, resultFailure) { args := splitCommandArgs(core.Env("CORE_DENO_ARGS")) if err := validateSidecarArgs(args, coreRef); err != nil { return deno.Options{}, err @@ -145,7 +145,7 @@ func sidecarLaunchOptions(coreRef *core.Core) (deno.Options, error) { }, nil } -func validateSidecarArgs(args []string, coreRef *core.Core) error { +func validateSidecarArgs(args []string, coreRef *core.Core) resultFailure { for _, arg := range args { flag := denoPermissionFlag(arg) if flag == "" { @@ -181,7 +181,7 @@ func denoPermissionFlag(arg string) string { } } -func validateSidecarBinary(value string) (string, error) { +func validateSidecarBinary(value string) (string, resultFailure) { binary := core.Trim(value) if binary == "" { return "", nil @@ -209,7 +209,7 @@ func validateSidecarBinary(value string) (string, error) { return resolved, nil } -func validateSidecarDir(value string) (string, error) { +func validateSidecarDir(value string) (string, resultFailure) { dir := core.Trim(value) if dir == "" { return "", nil @@ -243,7 +243,7 @@ func hasParentPathComponent(path string) bool { return false } -func rejectSymlinkPathComponents(path string) error { +func rejectSymlinkPathComponents(path string) resultFailure { clean := core.CleanPath(path, string(core.PathSeparator)) volume := pathVolumeName(clean) rest := core.TrimPrefix(clean, volume) diff --git a/go/pkg/display/sidecar_test.go b/go/pkg/display/sidecar_test.go index 0d21ff01..d93ae978 100644 --- a/go/pkg/display/sidecar_test.go +++ b/go/pkg/display/sidecar_test.go @@ -58,7 +58,7 @@ func TestSidecar_LaunchOptions_Good_EmptyEnv(t *core.T) { var options deno.Options output := captureStderr(t, func() { - var err error + var err resultFailure options, err = sidecarLaunchOptions(nil) core.RequireNoError(t, err) }) @@ -119,7 +119,7 @@ func TestSidecar_StartAction_Bad_RefusesPermissionArgs(t *core.T) { result := c.Action("display.sidecar.start").Run(context.Background(), core.Options{}) core.AssertFalse(t, result.OK) - err, ok := result.Value.(error) + err, ok := result.Value.(resultFailure) core.RequireTrue(t, ok) core.AssertContains(t, err.Error(), "--allow-all") core.AssertNil(t, svc.sidecar) diff --git a/go/pkg/display/storage.go b/go/pkg/display/storage.go index 6ace020b..a82fa381 100644 --- a/go/pkg/display/storage.go +++ b/go/pkg/display/storage.go @@ -371,7 +371,7 @@ func storageEqualFold(left, right string) bool { return core.Lower(left) == core.Lower(right) } -func (r *StorageRegistry) Close() error { +func (r *StorageRegistry) Close() resultFailure { if r == nil || r.store == nil { return nil } diff --git a/go/pkg/display/storage_store.go b/go/pkg/display/storage_store.go index 22f2d9c7..72143869 100644 --- a/go/pkg/display/storage_store.go +++ b/go/pkg/display/storage_store.go @@ -12,7 +12,7 @@ type storageStore struct { data map[string]map[string]string } -func newStorageStore(path string) (*storageStore, error) { +func newStorageStore(path string) (*storageStore, resultFailure) { store := &storageStore{ path: path, data: make(map[string]map[string]string), @@ -39,7 +39,7 @@ func newStorageStore(path string) (*storageStore, error) { return store, nil } -func (s *storageStore) set(group, key, value string) error { +func (s *storageStore) set(group, key, value string) resultFailure { if s == nil { return core.NewError("storage store is nil") } @@ -57,7 +57,7 @@ func (s *storageStore) set(group, key, value string) error { return s.persistLocked() } -func (s *storageStore) delete(group, key string) error { +func (s *storageStore) delete(group, key string) resultFailure { if s == nil { return core.NewError("storage store is nil") } @@ -72,7 +72,7 @@ func (s *storageStore) delete(group, key string) error { return s.persistLocked() } -func (s *storageStore) getAll(group string) (map[string]string, error) { +func (s *storageStore) getAll(group string) (map[string]string, resultFailure) { if s == nil { return nil, core.NewError("storage store is nil") } @@ -85,7 +85,7 @@ func (s *storageStore) getAll(group string) (map[string]string, error) { return copy, nil } -func (s *storageStore) close() error { +func (s *storageStore) close() resultFailure { if s == nil { return nil } @@ -94,7 +94,7 @@ func (s *storageStore) close() error { return s.persistLocked() } -func (s *storageStore) persistLocked() error { +func (s *storageStore) persistLocked() resultFailure { if s.path == "" || s.path == ":memory:" { return nil } diff --git a/go/pkg/dock/platform.go b/go/pkg/dock/platform.go index 8ab1fd61..1a64d0a4 100644 --- a/go/pkg/dock/platform.go +++ b/go/pkg/dock/platform.go @@ -18,17 +18,17 @@ const ( // Windows: taskbar badge + progress bar (show/hide and bounce not supported). // Linux: not supported — adapter returns nil for all operations. type Platform interface { - ShowIcon() error - HideIcon() error - SetBadge(label string) error - RemoveBadge() error + ShowIcon() resultFailure + HideIcon() resultFailure + SetBadge(label string) resultFailure + RemoveBadge() resultFailure IsVisible() bool // SetProgressBar sets a progress indicator on the dock/taskbar icon. // progress is clamped to [0.0, 1.0]. Pass -1.0 to hide the indicator. - SetProgressBar(progress float64) error + SetProgressBar(progress float64) resultFailure // Bounce requests user attention by animating the dock icon. // Returns a request ID that can be passed to StopBounce. - Bounce(bounceType BounceType) (int, error) + Bounce(bounceType BounceType) (int, resultFailure) // StopBounce cancels a pending attention request by its ID. - StopBounce(requestID int) error + StopBounce(requestID int) resultFailure } diff --git a/go/pkg/dock/result_failure.go b/go/pkg/dock/result_failure.go new file mode 100644 index 00000000..e4ef6d00 --- /dev/null +++ b/go/pkg/dock/result_failure.go @@ -0,0 +1,5 @@ +package dock + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/dock/service_test.go b/go/pkg/dock/service_test.go index 30b1d7c9..c9ed200b 100644 --- a/go/pkg/dock/service_test.go +++ b/go/pkg/dock/service_test.go @@ -18,16 +18,16 @@ type mockPlatform struct { bounceType BounceType bounceCalled bool stopBounceCalled bool - showErr error - hideErr error - badgeErr error - removeErr error - progressErr error - bounceErr error - stopBounceErr error + showErr resultFailure + hideErr resultFailure + badgeErr resultFailure + removeErr resultFailure + progressErr resultFailure + bounceErr resultFailure + stopBounceErr resultFailure } -func (m *mockPlatform) ShowIcon() error { +func (m *mockPlatform) ShowIcon() resultFailure { if m.showErr != nil { return m.showErr } @@ -35,7 +35,7 @@ func (m *mockPlatform) ShowIcon() error { return nil } -func (m *mockPlatform) HideIcon() error { +func (m *mockPlatform) HideIcon() resultFailure { if m.hideErr != nil { return m.hideErr } @@ -43,7 +43,7 @@ func (m *mockPlatform) HideIcon() error { return nil } -func (m *mockPlatform) SetBadge(label string) error { +func (m *mockPlatform) SetBadge(label string) resultFailure { if m.badgeErr != nil { return m.badgeErr } @@ -52,7 +52,7 @@ func (m *mockPlatform) SetBadge(label string) error { return nil } -func (m *mockPlatform) RemoveBadge() error { +func (m *mockPlatform) RemoveBadge() resultFailure { if m.removeErr != nil { return m.removeErr } @@ -63,7 +63,7 @@ func (m *mockPlatform) RemoveBadge() error { func (m *mockPlatform) IsVisible() bool { return m.visible } -func (m *mockPlatform) SetProgressBar(progress float64) error { +func (m *mockPlatform) SetProgressBar(progress float64) resultFailure { if m.progressErr != nil { return m.progressErr } @@ -71,7 +71,7 @@ func (m *mockPlatform) SetProgressBar(progress float64) error { return nil } -func (m *mockPlatform) Bounce(bounceType BounceType) (int, error) { +func (m *mockPlatform) Bounce(bounceType BounceType) (int, resultFailure) { if m.bounceErr != nil { return 0, m.bounceErr } @@ -81,7 +81,7 @@ func (m *mockPlatform) Bounce(bounceType BounceType) (int, error) { return m.bounceID, nil } -func (m *mockPlatform) StopBounce(requestID int) error { +func (m *mockPlatform) StopBounce(requestID int) resultFailure { if m.stopBounceErr != nil { return m.stopBounceErr } diff --git a/go/pkg/environment/platform.go b/go/pkg/environment/platform.go index 0340466c..065b07a9 100644 --- a/go/pkg/environment/platform.go +++ b/go/pkg/environment/platform.go @@ -6,7 +6,7 @@ type Platform interface { IsDarkMode() bool Info() EnvironmentInfo AccentColour() string - OpenFileManager(path string, selectFile bool) error + OpenFileManager(path string, selectFile bool) resultFailure HasFocusFollowsMouse() bool OnThemeChange(handler func(isDark bool)) func() // returns cancel func } diff --git a/go/pkg/environment/result_failure.go b/go/pkg/environment/result_failure.go new file mode 100644 index 00000000..c1bda8d2 --- /dev/null +++ b/go/pkg/environment/result_failure.go @@ -0,0 +1,5 @@ +package environment + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/environment/service.go b/go/pkg/environment/service.go index 71cfe861..b8f54e87 100644 --- a/go/pkg/environment/service.go +++ b/go/pkg/environment/service.go @@ -132,7 +132,7 @@ func (s *Service) hasThemeOverride() bool { return s.override != "" } -func (s *Service) setThemeOverride(theme string) (bool, error) { +func (s *Service) setThemeOverride(theme string) (bool, resultFailure) { normalized, err := normalizeTheme(theme) if err != nil { return false, err @@ -150,7 +150,7 @@ func (s *Service) setThemeOverride(theme string) (bool, error) { return after, nil } -func normalizeTheme(theme string) (string, error) { +func normalizeTheme(theme string) (string, resultFailure) { switch core.Lower(core.Trim(theme)) { case "", "system": return "", nil @@ -163,7 +163,7 @@ func normalizeTheme(theme string) (string, error) { } } -func validatedOpenFileManagerPath(raw string) (string, error) { +func validatedOpenFileManagerPath(raw string) (string, resultFailure) { trimmed := core.Trim(raw) if trimmed == "" { return "", core.E("environment.openFileManager", "path is required", nil) diff --git a/go/pkg/environment/service_test.go b/go/pkg/environment/service_test.go index ad38e901..9b1ce537 100644 --- a/go/pkg/environment/service_test.go +++ b/go/pkg/environment/service_test.go @@ -12,7 +12,7 @@ type mockPlatform struct { isDark bool info EnvironmentInfo accentColour string - openFMErr error + openFMErr resultFailure openFMPath string openFMSelect bool focusFollowsMouse bool @@ -24,7 +24,7 @@ func (m *mockPlatform) IsDarkMode() bool { return m.isDark } func (m *mockPlatform) Info() EnvironmentInfo { return m.info } func (m *mockPlatform) AccentColour() string { return m.accentColour } func (m *mockPlatform) HasFocusFollowsMouse() bool { return m.focusFollowsMouse } -func (m *mockPlatform) OpenFileManager(path string, selectFile bool) error { +func (m *mockPlatform) OpenFileManager(path string, selectFile bool) resultFailure { m.openFMPath = path m.openFMSelect = selectFile return m.openFMErr @@ -115,7 +115,7 @@ func TestTaskOpenFileManager_Bad_InvalidPath(t *core.T) { core.Option{Key: "task", Value: TaskOpenFileManager{Path: "../tmp", Select: false}}, )) core.AssertFalse(t, r.OK) - core.AssertContains(t, r.Value.(error).Error(), "path must be absolute") + core.AssertContains(t, r.Value.(resultFailure).Error(), "path must be absolute") } func TestThemeChange_ActionBroadcast_GoodCase(t *core.T) { @@ -205,7 +205,7 @@ func TestQueryAccentColour_Ugly_NoService(t *core.T) { // --- OpenFileManager --- func TestTaskOpenFileManager_Bad_Error(t *core.T) { - // platform returns an error on open + // platform returns an resultFailure on open openErr := core.E("test", "file manager unavailable", nil) mock := &mockPlatform{openFMErr: openErr} c := core.New(core.WithService(Register(mock)), core.WithServiceLock()) @@ -215,7 +215,7 @@ func TestTaskOpenFileManager_Bad_Error(t *core.T) { core.Option{Key: "task", Value: TaskOpenFileManager{Path: "/missing", Select: false}}, )) core.AssertFalse(t, r.OK) - err, _ := r.Value.(error) + err, _ := r.Value.(resultFailure) core.AssertErrorIs(t, err, openErr) } diff --git a/go/pkg/events/result_failure.go b/go/pkg/events/result_failure.go new file mode 100644 index 00000000..0818888a --- /dev/null +++ b/go/pkg/events/result_failure.go @@ -0,0 +1,5 @@ +package events + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/events/service.go b/go/pkg/events/service.go index a3f3dd48..313b4b00 100644 --- a/go/pkg/events/service.go +++ b/go/pkg/events/service.go @@ -82,14 +82,14 @@ func (s *Service) OnStartup(_ context.Context) core.Result { return core.Result{OK: true} } -func (s *Service) requirePlatform(method string) error { +func (s *Service) requirePlatform(method string) resultFailure { if s == nil || s.platform == nil { return core.E(method, "event platform unavailable", nil) } return nil } -func validateEventName(method, name string) error { +func validateEventName(method, name string) resultFailure { if core.Trim(name) == "" { return core.E(method, "event name must not be empty", nil) } diff --git a/go/pkg/events/service_test.go b/go/pkg/events/service_test.go index 7d0db797..1c8270c0 100644 --- a/go/pkg/events/service_test.go +++ b/go/pkg/events/service_test.go @@ -345,21 +345,21 @@ func TestTaskEmit_PlatformUnavailable_BadCase(t *core.T) { r := taskRun(c, "events.emit", TaskEmit{Name: "user:login"}) core.AssertFalse(t, r.OK) - err, ok := r.Value.(error) + err, ok := r.Value.(resultFailure) core.RequireTrue(t, ok) core.AssertError(t, err) core.AssertContains(t, err.Error(), "event platform unavailable") r = taskRun(c, "events.on", TaskOn{Name: "user:login"}) core.AssertFalse(t, r.OK) - err, ok = r.Value.(error) + err, ok = r.Value.(resultFailure) core.RequireTrue(t, ok) core.AssertError(t, err) core.AssertContains(t, err.Error(), "event platform unavailable") r = taskRun(c, "events.off", TaskOff{Name: "user:login"}) core.AssertFalse(t, r.OK) - err, ok = r.Value.(error) + err, ok = r.Value.(resultFailure) core.RequireTrue(t, ok) core.AssertError(t, err) core.AssertContains(t, err.Error(), "event platform unavailable") diff --git a/go/pkg/keybinding/platform.go b/go/pkg/keybinding/platform.go index 154e08ab..f5e4c787 100644 --- a/go/pkg/keybinding/platform.go +++ b/go/pkg/keybinding/platform.go @@ -7,10 +7,10 @@ type Platform interface { // The handler is called when the shortcut is triggered. // Accelerator syntax is platform-aware: "Cmd+S" (macOS), "Ctrl+S" (Windows/Linux). // Special keys: F1-F12, Escape, Enter, Space, Tab, Backspace, Delete, arrow keys. - Add(accelerator string, handler func()) error + Add(accelerator string, handler func()) resultFailure // Remove unregisters a previously registered keyboard shortcut. - Remove(accelerator string) error + Remove(accelerator string) resultFailure // Process triggers the registered handler for the given accelerator programmatically. // Returns true if a handler was found and invoked, false if not registered. diff --git a/go/pkg/keybinding/result_failure.go b/go/pkg/keybinding/result_failure.go new file mode 100644 index 00000000..1b70b7ee --- /dev/null +++ b/go/pkg/keybinding/result_failure.go @@ -0,0 +1,5 @@ +package keybinding + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/keybinding/service.go b/go/pkg/keybinding/service.go index 7d5e3f1a..77f582af 100644 --- a/go/pkg/keybinding/service.go +++ b/go/pkg/keybinding/service.go @@ -60,7 +60,7 @@ func (s *Service) queryList() []BindingInfo { return result } -func (s *Service) taskAdd(t TaskAdd) error { +func (s *Service) taskAdd(t TaskAdd) resultFailure { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.registeredBindings[t.Accelerator]; exists { @@ -82,7 +82,7 @@ func (s *Service) taskAdd(t TaskAdd) error { return nil } -func (s *Service) taskRemove(t TaskRemove) error { +func (s *Service) taskRemove(t TaskRemove) resultFailure { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.registeredBindings[t.Accelerator]; !exists { @@ -102,7 +102,7 @@ func (s *Service) taskRemove(t TaskRemove) error { // Broadcasts ActionTriggered if handled; returns ErrorNotRegistered if the accelerator is unknown. // // c.Action("keybinding.process").Run(ctx, core.NewOptions(core.Option{Key:"task", Value:keybinding.TaskProcess{Accelerator:"Ctrl+S"}})) -func (s *Service) taskProcess(t TaskProcess) error { +func (s *Service) taskProcess(t TaskProcess) resultFailure { s.mu.RLock() _, exists := s.registeredBindings[t.Accelerator] s.mu.RUnlock() diff --git a/go/pkg/keybinding/service_test.go b/go/pkg/keybinding/service_test.go index 59a4e8b2..a048f8e3 100644 --- a/go/pkg/keybinding/service_test.go +++ b/go/pkg/keybinding/service_test.go @@ -19,14 +19,14 @@ func newMockPlatform() *mockPlatform { return &mockPlatform{handlers: make(map[string]func())} } -func (m *mockPlatform) Add(accelerator string, handler func()) error { +func (m *mockPlatform) Add(accelerator string, handler func()) resultFailure { m.mu.Lock() defer m.mu.Unlock() m.handlers[accelerator] = handler return nil } -func (m *mockPlatform) Remove(accelerator string) error { +func (m *mockPlatform) Remove(accelerator string) resultFailure { m.mu.Lock() defer m.mu.Unlock() delete(m.handlers, accelerator) @@ -111,7 +111,7 @@ func TestTaskAdd_Bad_Duplicate(t *core.T) { // Second add with same accelerator should fail r := taskRun(c, "keybinding.add", TaskAdd{Accelerator: "Ctrl+S", Description: "Save Again"}) core.AssertFalse(t, r.OK) - err, _ := r.Value.(error) + err, _ := r.Value.(resultFailure) core.AssertErrorIs(t, err, ErrorAlreadyRegistered) } @@ -241,7 +241,7 @@ func TestTaskProcess_Bad_NotRegistered(t *core.T) { r := taskRun(c, "keybinding.process", TaskProcess{Accelerator: "Ctrl+P"}) core.AssertFalse(t, r.OK) - err, _ := r.Value.(error) + err, _ := r.Value.(resultFailure) core.AssertErrorIs(t, err, ErrorNotRegistered) } @@ -255,7 +255,7 @@ func TestTaskProcess_Ugly_RemovedBinding(t *core.T) { // After remove, process should fail with ErrorNotRegistered r := taskRun(c, "keybinding.process", TaskProcess{Accelerator: "Ctrl+P"}) core.AssertFalse(t, r.OK) - err, _ := r.Value.(error) + err, _ := r.Value.(resultFailure) core.AssertErrorIs(t, err, ErrorNotRegistered) } @@ -267,7 +267,7 @@ func TestTaskRemove_Bad_ErrorSentinel(t *core.T) { r := taskRun(c, "keybinding.remove", TaskRemove{Accelerator: "Ctrl+X"}) core.AssertFalse(t, r.OK) - err, _ := r.Value.(error) + err, _ := r.Value.(resultFailure) core.AssertErrorIs(t, err, ErrorNotRegistered) } diff --git a/go/pkg/marketplace/core_helpers.go b/go/pkg/marketplace/core_helpers.go index 71d4c29a..6dd88bfb 100644 --- a/go/pkg/marketplace/core_helpers.go +++ b/go/pkg/marketplace/core_helpers.go @@ -6,7 +6,7 @@ import ( core "dappco.re/go" ) -func coreResultError(result core.Result, fallback string) error { +func coreResultError(result core.Result, fallback string) resultFailure { if result.OK { return nil } @@ -19,19 +19,19 @@ func coreResultError(result core.Result, fallback string) error { return core.NewError(fallback) } -func coreMkdirAll(path string, mode core.FileMode) error { +func coreMkdirAll(path string, mode core.FileMode) resultFailure { return coreResultError(core.MkdirAll(path, mode), "failed to create directory") } -func coreRemoveAll(path string) error { +func coreRemoveAll(path string) resultFailure { return coreResultError(core.RemoveAll(path), "failed to remove path") } -func coreWriteFile(path string, data []byte, mode core.FileMode) error { +func coreWriteFile(path string, data []byte, mode core.FileMode) resultFailure { return coreResultError(core.WriteFile(path, data, mode), "failed to write file") } -func coreReadFile(path string) ([]byte, error) { +func coreReadFile(path string) ([]byte, resultFailure) { result := core.ReadFile(path) if !result.OK { return nil, coreResultError(result, "failed to read file") @@ -39,7 +39,7 @@ func coreReadFile(path string) ([]byte, error) { return result.Value.([]byte), nil } -func coreStat(path string) (core.FsFileInfo, error) { +func coreStat(path string) (core.FsFileInfo, resultFailure) { result := core.Stat(path) if !result.OK { return nil, coreResultError(result, "failed to stat path") @@ -47,7 +47,7 @@ func coreStat(path string) (core.FsFileInfo, error) { return result.Value.(core.FsFileInfo), nil } -func pathAbs(path string) (string, error) { +func pathAbs(path string) (string, resultFailure) { result := core.PathAbs(path) if !result.OK { return "", coreResultError(result, "failed to make path absolute") @@ -55,7 +55,7 @@ func pathAbs(path string) (string, error) { return result.Value.(string), nil } -func pathEvalSymlinks(path string) (string, error) { +func pathEvalSymlinks(path string) (string, resultFailure) { result := core.PathEvalSymlinks(path) if !result.OK { return "", coreResultError(result, "failed to resolve symlinks") @@ -63,7 +63,7 @@ func pathEvalSymlinks(path string) (string, error) { return result.Value.(string), nil } -func pathRel(base, target string) (string, error) { +func pathRel(base, target string) (string, resultFailure) { result := core.PathRel(base, target) if !result.OK { return "", coreResultError(result, "failed to compare paths") @@ -71,7 +71,7 @@ func pathRel(base, target string) (string, error) { return result.Value.(string), nil } -func jsonUnmarshal(data []byte, target any) error { +func jsonUnmarshal(data []byte, target any) resultFailure { return coreResultError(core.JSONUnmarshal(data, target), "failed to decode JSON") } diff --git a/go/pkg/marketplace/marketplace.go b/go/pkg/marketplace/marketplace.go index 758b5918..d650ab4d 100644 --- a/go/pkg/marketplace/marketplace.go +++ b/go/pkg/marketplace/marketplace.go @@ -43,7 +43,7 @@ const maxManifestBytes = 1 << 20 var credentialRedactionPattern = regexp.MustCompile(`(?i)\b([a-z][a-z0-9+.-]*://)([^@\s/]+)@`) -func (i Installer) FetchManifest(ctx context.Context, manifestURL string) (Manifest, error) { +func (i Installer) FetchManifest(ctx context.Context, manifestURL string) (Manifest, resultFailure) { client := i.HTTPClient if client == nil { client = http.DefaultClient @@ -77,7 +77,7 @@ func (i Installer) FetchManifest(ctx context.Context, manifestURL string) (Manif return manifest, nil } -func VerifyManifest(manifest Manifest) error { +func VerifyManifest(manifest Manifest) resultFailure { if core.Lower(core.Trim(manifest.Signature.Algorithm)) != "ed25519" { return core.NewError("manifest signature algorithm must be ed25519") } @@ -105,7 +105,7 @@ func VerifyManifest(manifest Manifest) error { return nil } -func (i Installer) Verify(ctx context.Context, manifestURL string) (Manifest, error) { +func (i Installer) Verify(ctx context.Context, manifestURL string) (Manifest, resultFailure) { manifest, err := i.FetchManifest(ctx, manifestURL) if err != nil { return Manifest{}, err @@ -116,7 +116,7 @@ func (i Installer) Verify(ctx context.Context, manifestURL string) (Manifest, er return manifest, nil } -func (i Installer) Install(ctx context.Context, manifest Manifest) (string, error) { +func (i Installer) Install(ctx context.Context, manifest Manifest) (string, resultFailure) { if core.Trim(i.InstallDir) == "" { return "", core.NewError("install dir is required") } @@ -178,7 +178,9 @@ func (i Installer) Install(ctx context.Context, manifest Manifest) (string, erro } runGit := i.GitRunner if runGit == nil { - runGit = runGitCommand + runGit = func(ctx context.Context, binary string, args ...string) ([]byte, error) { + return runGitCommand(ctx, binary, args...) + } } if output, err := runGit(ctx, binary, args...); err != nil { return "", core.Errorf("git clone failed: %w: %s", err, sanitizeCommandOutput(output)) @@ -190,12 +192,12 @@ func (i Installer) Install(ctx context.Context, manifest Manifest) (string, erro return targetDir, nil } -func runGitCommand(ctx context.Context, binary string, args ...string) ([]byte, error) { +func runGitCommand(ctx context.Context, binary string, args ...string) ([]byte, resultFailure) { cmd := commandContext(ctx, binary, args...) return cmd.CombinedOutput() } -func (i Installer) List(ctx context.Context, registryURL string) ([]Manifest, error) { +func (i Installer) List(ctx context.Context, registryURL string) ([]Manifest, resultFailure) { client := i.HTTPClient if client == nil { client = http.DefaultClient @@ -222,7 +224,7 @@ func (i Installer) List(ctx context.Context, registryURL string) ([]Manifest, er return decodeManifestList(body) } -func validateManifestName(value string) error { +func validateManifestName(value string) resultFailure { trimmed := core.Trim(value) if trimmed == "" { return core.NewError("manifest name is required") @@ -236,7 +238,7 @@ func validateManifestName(value string) error { return nil } -func validateCloneArg(label, value string) error { +func validateCloneArg(label, value string) resultFailure { trimmed := core.Trim(value) if trimmed == "" { return core.Errorf("%s is required", label) @@ -250,7 +252,7 @@ func validateCloneArg(label, value string) error { return nil } -func validateCloneArgOptional(label, value string) error { +func validateCloneArgOptional(label, value string) resultFailure { trimmed := core.Trim(value) if trimmed == "" { return nil @@ -258,7 +260,7 @@ func validateCloneArgOptional(label, value string) error { return validateCloneArg(label, trimmed) } -func validateRepositorySource(value string) error { +func validateRepositorySource(value string) resultFailure { trimmed := core.Trim(value) if trimmed == "" { return core.NewError("repository is required") @@ -333,7 +335,7 @@ func fallbackSafeName(value string) string { return "module-" + hex.EncodeToString(hash[:])[:8] } -func decodeManifestList(body []byte) ([]Manifest, error) { +func decodeManifestList(body []byte) ([]Manifest, resultFailure) { trimmed := core.Trim(string(body)) if trimmed == "" { return nil, nil @@ -357,7 +359,7 @@ func decodeManifestList(body []byte) ([]Manifest, error) { return manifests, nil } -func writeInstalledManifest(targetDir string, manifest Manifest) error { +func writeInstalledManifest(targetDir string, manifest Manifest) resultFailure { manifestDir := core.PathJoin(targetDir, ".core") if err := coreMkdirAll(manifestDir, 0o755); err != nil { return err diff --git a/go/pkg/marketplace/result_failure.go b/go/pkg/marketplace/result_failure.go new file mode 100644 index 00000000..7c928826 --- /dev/null +++ b/go/pkg/marketplace/result_failure.go @@ -0,0 +1,5 @@ +package marketplace + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/mcp/mcp_test.go b/go/pkg/mcp/mcp_test.go index 7a2d5530..9bff4cc5 100644 --- a/go/pkg/mcp/mcp_test.go +++ b/go/pkg/mcp/mcp_test.go @@ -164,12 +164,12 @@ func TestSubsystem_Good_CallTool_LayoutSuggest(t *core.T) { core.AssertContains(t, result, "left-right") } -func (m *manifestBrowserPlatform) OpenURL(url string) error { +func (m *manifestBrowserPlatform) OpenURL(url string) resultFailure { m.lastURL = url return nil } -func (m *manifestBrowserPlatform) OpenFile(path string) error { +func (m *manifestBrowserPlatform) OpenFile(path string) resultFailure { m.lastPath = path return nil } @@ -223,12 +223,16 @@ type aliasDialogPlatform struct { last dialog.MessageDialogOptions } -func (m *aliasDialogPlatform) OpenFile(_ dialog.OpenFileOptions) ([]string, error) { return nil, nil } -func (m *aliasDialogPlatform) SaveFile(_ dialog.SaveFileOptions) (string, error) { return "", nil } -func (m *aliasDialogPlatform) OpenDirectory(_ dialog.OpenDirectoryOptions) (string, error) { +func (m *aliasDialogPlatform) OpenFile(_ dialog.OpenFileOptions) ([]string, resultFailure) { + return nil, nil +} +func (m *aliasDialogPlatform) SaveFile(_ dialog.SaveFileOptions) (string, resultFailure) { + return "", nil +} +func (m *aliasDialogPlatform) OpenDirectory(_ dialog.OpenDirectoryOptions) (string, resultFailure) { return "", nil } -func (m *aliasDialogPlatform) MessageDialog(opts dialog.MessageDialogOptions) (string, error) { +func (m *aliasDialogPlatform) MessageDialog(opts dialog.MessageDialogOptions) (string, resultFailure) { m.last = opts return "OK", nil } diff --git a/go/pkg/mcp/result_failure.go b/go/pkg/mcp/result_failure.go new file mode 100644 index 00000000..1ea46e9e --- /dev/null +++ b/go/pkg/mcp/result_failure.go @@ -0,0 +1,5 @@ +package mcp + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/mcp/subsystem.go b/go/pkg/mcp/subsystem.go index 612f5ae2..6a5d20bb 100644 --- a/go/pkg/mcp/subsystem.go +++ b/go/pkg/mcp/subsystem.go @@ -28,7 +28,7 @@ type ToolDescriptor struct { type toolRecord struct { descriptor ToolDescriptor - call func(context.Context, map[string]any) (string, error) + call func(context.Context, map[string]any) (string, resultFailure) } // New(c) creates a display MCP subsystem backed by a Core instance. @@ -110,7 +110,7 @@ func (s *Subsystem) ManifestText() string { } // CallTool executes a recorded GUI MCP tool directly by name. -func (s *Subsystem) CallTool(ctx context.Context, name string, arguments map[string]any) (string, error) { +func (s *Subsystem) CallTool(ctx context.Context, name string, arguments map[string]any) (string, resultFailure) { s.mu.RLock() record, ok := s.tools[name] s.mu.RUnlock() @@ -123,7 +123,12 @@ func (s *Subsystem) CallTool(ctx context.Context, name string, arguments map[str return record.call(ctx, arguments) } -func addTool[In, Out any](s *Subsystem, server *mcp.Server, tool *mcp.Tool, handler mcp.ToolHandlerFor[In, Out]) { +func addTool[In, Out any]( + s *Subsystem, + server *mcp.Server, + tool *mcp.Tool, + handler func(context.Context, *mcp.CallToolRequest, In) (*mcp.CallToolResult, Out, resultFailure), +) { if tool.InputSchema == nil { tool.InputSchema = schemaForValue(new(In)) if tool.InputSchema == nil { @@ -131,13 +136,19 @@ func addTool[In, Out any](s *Subsystem, server *mcp.Server, tool *mcp.Tool, hand } } - mcp.AddTool(server, tool, handler) - s.recordTool(tool, func(ctx context.Context, arguments map[string]any) (string, error) { + mcp.AddTool(server, tool, func(ctx context.Context, req *mcp.CallToolRequest, input In) (*mcp.CallToolResult, Out, error) { + result, output, err := handler(ctx, req, input) + if err != nil { + return nil, output, err + } + return result, output, nil + }) + s.recordTool(tool, func(ctx context.Context, arguments map[string]any) (string, resultFailure) { var input In if len(arguments) > 0 { result := core.JSONUnmarshalString(core.JSONMarshalString(arguments), &input) if !result.OK { - if err, ok := result.Value.(error); ok { + if err, ok := result.Value.(resultFailure); ok { return "", err } return "", core.E("mcp.addTool", "failed to decode tool input", nil) @@ -155,7 +166,7 @@ func addTool[In, Out any](s *Subsystem, server *mcp.Server, tool *mcp.Tool, hand }) } -func (s *Subsystem) recordTool(tool *mcp.Tool, call func(context.Context, map[string]any) (string, error)) { +func (s *Subsystem) recordTool(tool *mcp.Tool, call func(context.Context, map[string]any) (string, resultFailure)) { s.mu.Lock() defer s.mu.Unlock() s.tools[tool.Name] = toolRecord{ diff --git a/go/pkg/mcp/tools_browser.go b/go/pkg/mcp/tools_browser.go index a8753b9a..d1ab2e83 100644 --- a/go/pkg/mcp/tools_browser.go +++ b/go/pkg/mcp/tools_browser.go @@ -17,7 +17,7 @@ type BrowserOpenURLOutput struct { Success bool `json:"success"` } -func (s *Subsystem) browserOpenURL(_ context.Context, _ *mcp.CallToolRequest, input BrowserOpenURLInput) (*mcp.CallToolResult, BrowserOpenURLOutput, error) { +func (s *Subsystem) browserOpenURL(_ context.Context, _ *mcp.CallToolRequest, input BrowserOpenURLInput) (*mcp.CallToolResult, BrowserOpenURLOutput, resultFailure) { r := s.core.Action("browser.openURL").Run(context.Background(), core.NewOptions( core.Option{Key: "url", Value: input.URL}, )) @@ -39,7 +39,7 @@ type BrowserOpenFileOutput struct { Success bool `json:"success"` } -func (s *Subsystem) browserOpenFile(_ context.Context, _ *mcp.CallToolRequest, input BrowserOpenFileInput) (*mcp.CallToolResult, BrowserOpenFileOutput, error) { +func (s *Subsystem) browserOpenFile(_ context.Context, _ *mcp.CallToolRequest, input BrowserOpenFileInput) (*mcp.CallToolResult, BrowserOpenFileOutput, resultFailure) { r := s.core.Action("browser.openFile").Run(context.Background(), core.NewOptions( core.Option{Key: core.Concat("pa", "th"), Value: input.Path}, )) diff --git a/go/pkg/mcp/tools_chat.go b/go/pkg/mcp/tools_chat.go index 82f4caee..9354557b 100644 --- a/go/pkg/mcp/tools_chat.go +++ b/go/pkg/mcp/tools_chat.go @@ -91,7 +91,7 @@ type ChatSendOutput struct { MessageID string `json:"message_id"` } -func (s *Subsystem) chatSend(_ context.Context, _ *mcp.CallToolRequest, input ChatSendInput) (*mcp.CallToolResult, ChatSendOutput, error) { +func (s *Subsystem) chatSend(_ context.Context, _ *mcp.CallToolRequest, input ChatSendInput) (*mcp.CallToolResult, ChatSendOutput, resultFailure) { result := s.core.Action("gui.chat.send").Run(context.Background(), core.NewOptions( core.Option{Key: "conversation_id", Value: input.ConversationID}, core.Option{Key: "content", Value: input.Content}, @@ -120,7 +120,7 @@ type ChatHistoryOutput struct { Messages []ChatMessage `json:"messages"` } -func (s *Subsystem) chatHistory(_ context.Context, _ *mcp.CallToolRequest, input ChatHistoryInput) (*mcp.CallToolResult, ChatHistoryOutput, error) { +func (s *Subsystem) chatHistory(_ context.Context, _ *mcp.CallToolRequest, input ChatHistoryInput) (*mcp.CallToolResult, ChatHistoryOutput, resultFailure) { result := s.core.Action("gui.chat.history").Run(context.Background(), core.NewOptions( core.Option{Key: "conversation_id", Value: input.ConversationID}, core.Option{Key: "id", Value: input.ID}, @@ -145,7 +145,7 @@ type ChatModelsOutput struct { Models []ChatModel `json:"models"` } -func (s *Subsystem) chatModels(_ context.Context, _ *mcp.CallToolRequest, _ ChatModelsInput) (*mcp.CallToolResult, ChatModelsOutput, error) { +func (s *Subsystem) chatModels(_ context.Context, _ *mcp.CallToolRequest, _ ChatModelsInput) (*mcp.CallToolResult, ChatModelsOutput, resultFailure) { result := s.core.Action("gui.chat.models").Run(context.Background(), core.NewOptions()) if !result.OK { if err, ok := result.Value.(error); ok { @@ -171,7 +171,7 @@ type ChatSelectModelOutput struct { Settings ChatSettings `json:"settings"` } -func (s *Subsystem) chatSelectModel(_ context.Context, _ *mcp.CallToolRequest, input ChatSelectModelInput) (*mcp.CallToolResult, ChatSelectModelOutput, error) { +func (s *Subsystem) chatSelectModel(_ context.Context, _ *mcp.CallToolRequest, input ChatSelectModelInput) (*mcp.CallToolResult, ChatSelectModelOutput, resultFailure) { result := s.core.Action("gui.chat.selectModel").Run(context.Background(), core.NewOptions( core.Option{Key: "name", Value: input.Name}, core.Option{Key: "model", Value: input.Model}, @@ -197,7 +197,7 @@ type ChatConversationsListOutput struct { Conversations []ChatConversation `json:"conversations"` } -func (s *Subsystem) chatConversationsList(_ context.Context, _ *mcp.CallToolRequest, _ ChatConversationsListInput) (*mcp.CallToolResult, ChatConversationsListOutput, error) { +func (s *Subsystem) chatConversationsList(_ context.Context, _ *mcp.CallToolRequest, _ ChatConversationsListInput) (*mcp.CallToolResult, ChatConversationsListOutput, resultFailure) { result := s.core.Action("gui.chat.conversations.list").Run(context.Background(), core.NewOptions()) if !result.OK { if err, ok := result.Value.(error); ok { @@ -221,7 +221,7 @@ type ChatConversationsLoadOutput struct { Conversation ChatConversation `json:"conversation"` } -func (s *Subsystem) chatConversationsLoad(_ context.Context, _ *mcp.CallToolRequest, input ChatConversationsLoadInput) (*mcp.CallToolResult, ChatConversationsLoadOutput, error) { +func (s *Subsystem) chatConversationsLoad(_ context.Context, _ *mcp.CallToolRequest, input ChatConversationsLoadInput) (*mcp.CallToolResult, ChatConversationsLoadOutput, resultFailure) { result := s.core.Action("gui.chat.conversations.load").Run(context.Background(), core.NewOptions( core.Option{Key: "conversation_id", Value: input.ConversationID}, core.Option{Key: "id", Value: input.ID}, @@ -248,7 +248,7 @@ type ChatConversationsDeleteOutput struct { Success bool `json:"success"` } -func (s *Subsystem) chatConversationsDelete(_ context.Context, _ *mcp.CallToolRequest, input ChatConversationsDeleteInput) (*mcp.CallToolResult, ChatConversationsDeleteOutput, error) { +func (s *Subsystem) chatConversationsDelete(_ context.Context, _ *mcp.CallToolRequest, input ChatConversationsDeleteInput) (*mcp.CallToolResult, ChatConversationsDeleteOutput, resultFailure) { result := s.core.Action("gui.chat.conversations.delete").Run(context.Background(), core.NewOptions( core.Option{Key: "conversation_id", Value: input.ConversationID}, core.Option{Key: "id", Value: input.ID}, @@ -276,7 +276,7 @@ type ChatThinkingStartOutput struct { State ChatThinkingState `json:"state"` } -func (s *Subsystem) chatThinkingStart(_ context.Context, _ *mcp.CallToolRequest, input ChatThinkingStartInput) (*mcp.CallToolResult, ChatThinkingStartOutput, error) { +func (s *Subsystem) chatThinkingStart(_ context.Context, _ *mcp.CallToolRequest, input ChatThinkingStartInput) (*mcp.CallToolResult, ChatThinkingStartOutput, resultFailure) { result := s.core.Action("gui.chat.thinking.start").Run(context.Background(), core.NewOptions( core.Option{Key: "conversation_id", Value: input.ConversationID}, core.Option{Key: "message_id", Value: input.MessageID}, @@ -306,7 +306,7 @@ type ChatThinkingStopOutput struct { State ChatThinkingState `json:"state"` } -func (s *Subsystem) chatThinkingStop(_ context.Context, _ *mcp.CallToolRequest, input ChatThinkingStopInput) (*mcp.CallToolResult, ChatThinkingStopOutput, error) { +func (s *Subsystem) chatThinkingStop(_ context.Context, _ *mcp.CallToolRequest, input ChatThinkingStopInput) (*mcp.CallToolResult, ChatThinkingStopOutput, resultFailure) { result := s.core.Action("gui.chat.thinking.stop").Run(context.Background(), core.NewOptions( core.Option{Key: "conversation_id", Value: input.ConversationID}, core.Option{Key: "message_id", Value: input.MessageID}, @@ -326,7 +326,7 @@ func (s *Subsystem) chatThinkingStop(_ context.Context, _ *mcp.CallToolRequest, return nil, ChatThinkingStopOutput{State: state}, nil } -func decodeChatValue[T any](value any) (T, error) { +func decodeChatValue[T any](value any) (T, resultFailure) { var output T result := core.JSONUnmarshalString(core.JSONMarshalString(value), &output) if result.OK { diff --git a/go/pkg/mcp/tools_clipboard.go b/go/pkg/mcp/tools_clipboard.go index d39dba10..c9235741 100644 --- a/go/pkg/mcp/tools_clipboard.go +++ b/go/pkg/mcp/tools_clipboard.go @@ -17,7 +17,7 @@ type ClipboardReadOutput struct { Content string `json:"content"` } -func (s *Subsystem) clipboardRead(_ context.Context, _ *mcp.CallToolRequest, _ ClipboardReadInput) (*mcp.CallToolResult, ClipboardReadOutput, error) { +func (s *Subsystem) clipboardRead(_ context.Context, _ *mcp.CallToolRequest, _ ClipboardReadInput) (*mcp.CallToolResult, ClipboardReadOutput, resultFailure) { r := s.core.QUERY(clipboard.QueryText{}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -41,7 +41,7 @@ type ClipboardWriteOutput struct { Success bool `json:"success"` } -func (s *Subsystem) clipboardWrite(_ context.Context, _ *mcp.CallToolRequest, input ClipboardWriteInput) (*mcp.CallToolResult, ClipboardWriteOutput, error) { +func (s *Subsystem) clipboardWrite(_ context.Context, _ *mcp.CallToolRequest, input ClipboardWriteInput) (*mcp.CallToolResult, ClipboardWriteOutput, resultFailure) { r := s.core.Action("clipboard.setText").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: clipboard.TaskSetText{Text: input.Text}}, )) @@ -61,7 +61,7 @@ type ClipboardHasOutput struct { HasContent bool `json:"hasContent"` } -func (s *Subsystem) clipboardHas(_ context.Context, _ *mcp.CallToolRequest, _ ClipboardHasInput) (*mcp.CallToolResult, ClipboardHasOutput, error) { +func (s *Subsystem) clipboardHas(_ context.Context, _ *mcp.CallToolRequest, _ ClipboardHasInput) (*mcp.CallToolResult, ClipboardHasOutput, resultFailure) { r := s.core.QUERY(clipboard.QueryText{}) if !r.OK { return nil, ClipboardHasOutput{}, nil @@ -80,7 +80,7 @@ type ClipboardClearOutput struct { Success bool `json:"success"` } -func (s *Subsystem) clipboardClear(_ context.Context, _ *mcp.CallToolRequest, _ ClipboardClearInput) (*mcp.CallToolResult, ClipboardClearOutput, error) { +func (s *Subsystem) clipboardClear(_ context.Context, _ *mcp.CallToolRequest, _ ClipboardClearInput) (*mcp.CallToolResult, ClipboardClearOutput, resultFailure) { r := s.core.Action("clipboard.clear").Run(context.Background(), core.NewOptions()) if !r.OK { if e, ok := r.Value.(error); ok { @@ -98,7 +98,7 @@ type ClipboardReadImageOutput struct { Base64 string `json:"base64"` } -func (s *Subsystem) clipboardReadImage(_ context.Context, _ *mcp.CallToolRequest, _ ClipboardReadImageInput) (*mcp.CallToolResult, ClipboardReadImageOutput, error) { +func (s *Subsystem) clipboardReadImage(_ context.Context, _ *mcp.CallToolRequest, _ ClipboardReadImageInput) (*mcp.CallToolResult, ClipboardReadImageOutput, resultFailure) { r := s.core.QUERY(clipboard.QueryImage{}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -125,7 +125,7 @@ type ClipboardWriteImageOutput struct { Success bool `json:"success"` } -func (s *Subsystem) clipboardWriteImage(_ context.Context, _ *mcp.CallToolRequest, input ClipboardWriteImageInput) (*mcp.CallToolResult, ClipboardWriteImageOutput, error) { +func (s *Subsystem) clipboardWriteImage(_ context.Context, _ *mcp.CallToolRequest, input ClipboardWriteImageInput) (*mcp.CallToolResult, ClipboardWriteImageOutput, resultFailure) { maxEncodedLen := ((clipboard.MaxImageBytes + 2) / 3) * 4 if len(input.Base64) == 0 || len(input.Base64) > maxEncodedLen { return nil, ClipboardWriteImageOutput{}, core.E("mcp.clipboardWriteImage", "clipboard image exceeds maximum size", nil) diff --git a/go/pkg/mcp/tools_container.go b/go/pkg/mcp/tools_container.go index 14f2e9e0..d1671df8 100644 --- a/go/pkg/mcp/tools_container.go +++ b/go/pkg/mcp/tools_container.go @@ -13,7 +13,7 @@ type ContainerDetectOutput struct { Runtime container.ContainerRuntime `json:"runtime"` } -func (s *Subsystem) containerDetect(_ context.Context, _ *mcp.CallToolRequest, _ ContainerDetectInput) (*mcp.CallToolResult, ContainerDetectOutput, error) { +func (s *Subsystem) containerDetect(_ context.Context, _ *mcp.CallToolRequest, _ ContainerDetectInput) (*mcp.CallToolResult, ContainerDetectOutput, resultFailure) { result := s.core.Action("container.runtime.detect").Run(context.Background(), core.Options{}) if !result.OK { if err, ok := result.Value.(error); ok { @@ -33,7 +33,7 @@ type TIMStateOutput struct { State container.TIMState `json:"state"` } -func (s *Subsystem) timStatus(_ context.Context, _ *mcp.CallToolRequest, _ TIMStateInput) (*mcp.CallToolResult, TIMStateOutput, error) { +func (s *Subsystem) timStatus(_ context.Context, _ *mcp.CallToolRequest, _ TIMStateInput) (*mcp.CallToolResult, TIMStateOutput, resultFailure) { result := s.core.Action("tim.status").Run(context.Background(), core.Options{}) if !result.OK { if err, ok := result.Value.(error); ok { @@ -53,7 +53,7 @@ type TIMStartOutput struct { State container.TIMState `json:"state"` } -func (s *Subsystem) timStart(_ context.Context, _ *mcp.CallToolRequest, _ TIMStartInput) (*mcp.CallToolResult, TIMStartOutput, error) { +func (s *Subsystem) timStart(_ context.Context, _ *mcp.CallToolRequest, _ TIMStartInput) (*mcp.CallToolResult, TIMStartOutput, resultFailure) { result := s.core.Action("tim.start").Run(context.Background(), core.Options{}) if !result.OK { if err, ok := result.Value.(error); ok { @@ -73,7 +73,7 @@ type TIMStopOutput struct { State container.TIMState `json:"state"` } -func (s *Subsystem) timStop(_ context.Context, _ *mcp.CallToolRequest, _ TIMStopInput) (*mcp.CallToolResult, TIMStopOutput, error) { +func (s *Subsystem) timStop(_ context.Context, _ *mcp.CallToolRequest, _ TIMStopInput) (*mcp.CallToolResult, TIMStopOutput, resultFailure) { result := s.core.Action("tim.stop").Run(context.Background(), core.Options{}) if !result.OK { if err, ok := result.Value.(error); ok { diff --git a/go/pkg/mcp/tools_contextmenu.go b/go/pkg/mcp/tools_contextmenu.go index 4d4a42bd..ad0d14d2 100644 --- a/go/pkg/mcp/tools_contextmenu.go +++ b/go/pkg/mcp/tools_contextmenu.go @@ -22,7 +22,7 @@ type ContextMenuAddOutput struct { Success bool `json:"success"` } -func jsonBytesFromResult(op, message string, result core.Result) ([]byte, error) { +func jsonBytesFromResult(op, message string, result core.Result) ([]byte, resultFailure) { if !result.OK { if err, ok := result.Value.(error); ok && err != nil { return nil, core.E(op, message, err) @@ -37,14 +37,14 @@ func jsonBytesFromResult(op, message string, result core.Result) ([]byte, error) return data, nil } -func resultError(result core.Result) error { +func resultError(result core.Result) resultFailure { if err, ok := result.Value.(error); ok && err != nil { return err } return nil } -func (s *Subsystem) contextMenuAdd(_ context.Context, _ *mcp.CallToolRequest, input ContextMenuAddInput) (*mcp.CallToolResult, ContextMenuAddOutput, error) { +func (s *Subsystem) contextMenuAdd(_ context.Context, _ *mcp.CallToolRequest, input ContextMenuAddInput) (*mcp.CallToolResult, ContextMenuAddOutput, resultFailure) { // Convert map[string]any to ContextMenuDef via JSON round-trip menuJSON, err := jsonBytesFromResult("mcp.contextMenuAdd", "failed to marshal menu definition", core.JSONMarshal(input.Menu)) if err != nil { @@ -77,7 +77,7 @@ type ContextMenuRemoveOutput struct { Success bool `json:"success"` } -func (s *Subsystem) contextMenuRemove(_ context.Context, _ *mcp.CallToolRequest, input ContextMenuRemoveInput) (*mcp.CallToolResult, ContextMenuRemoveOutput, error) { +func (s *Subsystem) contextMenuRemove(_ context.Context, _ *mcp.CallToolRequest, input ContextMenuRemoveInput) (*mcp.CallToolResult, ContextMenuRemoveOutput, resultFailure) { r := s.core.Action("contextmenu.remove").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: contextmenu.TaskRemove{Name: input.Name}}, )) @@ -99,7 +99,7 @@ type ContextMenuGetOutput struct { Menu map[string]any `json:"menu"` } -func (s *Subsystem) contextMenuGet(_ context.Context, _ *mcp.CallToolRequest, input ContextMenuGetInput) (*mcp.CallToolResult, ContextMenuGetOutput, error) { +func (s *Subsystem) contextMenuGet(_ context.Context, _ *mcp.CallToolRequest, input ContextMenuGetInput) (*mcp.CallToolResult, ContextMenuGetOutput, resultFailure) { r := s.core.QUERY(contextmenu.QueryGet{Name: input.Name}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -135,7 +135,7 @@ type ContextMenuListOutput struct { Menus map[string]any `json:"menus"` } -func (s *Subsystem) contextMenuList(_ context.Context, _ *mcp.CallToolRequest, _ ContextMenuListInput) (*mcp.CallToolResult, ContextMenuListOutput, error) { +func (s *Subsystem) contextMenuList(_ context.Context, _ *mcp.CallToolRequest, _ ContextMenuListInput) (*mcp.CallToolResult, ContextMenuListOutput, resultFailure) { r := s.core.QUERY(contextmenu.QueryList{}) if !r.OK { if e, ok := r.Value.(error); ok { diff --git a/go/pkg/mcp/tools_deno.go b/go/pkg/mcp/tools_deno.go index 9d3cb3f9..b7456a94 100644 --- a/go/pkg/mcp/tools_deno.go +++ b/go/pkg/mcp/tools_deno.go @@ -14,7 +14,7 @@ type DenoStatusOutput struct { Status deno.Status `json:"status"` } -func (s *Subsystem) denoStatus(_ context.Context, _ *mcp.CallToolRequest, _ DenoStatusInput) (*mcp.CallToolResult, DenoStatusOutput, error) { +func (s *Subsystem) denoStatus(_ context.Context, _ *mcp.CallToolRequest, _ DenoStatusInput) (*mcp.CallToolResult, DenoStatusOutput, resultFailure) { result := s.core.Action("core.deno.sidecar.status").Run(context.Background(), core.Options{}) if !result.OK { if err, ok := result.Value.(error); ok { @@ -34,7 +34,7 @@ type DenoStartOutput struct { Status deno.Status `json:"status"` } -func (s *Subsystem) denoStart(_ context.Context, _ *mcp.CallToolRequest, _ DenoStartInput) (*mcp.CallToolResult, DenoStartOutput, error) { +func (s *Subsystem) denoStart(_ context.Context, _ *mcp.CallToolRequest, _ DenoStartInput) (*mcp.CallToolResult, DenoStartOutput, resultFailure) { result := s.core.Action("core.deno.sidecar.start").Run(context.Background(), core.Options{}) if !result.OK { if err, ok := result.Value.(error); ok { @@ -54,7 +54,7 @@ type DenoStopOutput struct { Status deno.Status `json:"status"` } -func (s *Subsystem) denoStop(_ context.Context, _ *mcp.CallToolRequest, _ DenoStopInput) (*mcp.CallToolResult, DenoStopOutput, error) { +func (s *Subsystem) denoStop(_ context.Context, _ *mcp.CallToolRequest, _ DenoStopInput) (*mcp.CallToolResult, DenoStopOutput, resultFailure) { result := s.core.Action("core.deno.sidecar.stop").Run(context.Background(), core.Options{}) if !result.OK { if err, ok := result.Value.(error); ok { @@ -77,7 +77,7 @@ type DenoEvalOutput struct { Result deno.EvalResult `json:"result"` } -func (s *Subsystem) denoEval(_ context.Context, _ *mcp.CallToolRequest, input DenoEvalInput) (*mcp.CallToolResult, DenoEvalOutput, error) { +func (s *Subsystem) denoEval(_ context.Context, _ *mcp.CallToolRequest, input DenoEvalInput) (*mcp.CallToolResult, DenoEvalOutput, resultFailure) { result := s.core.Action("core.deno.sidecar.eval").Run(context.Background(), core.NewOptions( core.Option{Key: "code", Value: input.Code}, )) diff --git a/go/pkg/mcp/tools_dialog.go b/go/pkg/mcp/tools_dialog.go index 97593833..c91ee8f7 100644 --- a/go/pkg/mcp/tools_dialog.go +++ b/go/pkg/mcp/tools_dialog.go @@ -24,7 +24,7 @@ type DialogOpenFileOutput struct { Paths []string `json:"paths"` } -func (s *Subsystem) dialogOpenFile(_ context.Context, _ *mcp.CallToolRequest, input DialogOpenFileInput) (*mcp.CallToolResult, DialogOpenFileOutput, error) { +func (s *Subsystem) dialogOpenFile(_ context.Context, _ *mcp.CallToolRequest, input DialogOpenFileInput) (*mcp.CallToolResult, DialogOpenFileOutput, resultFailure) { r := s.core.Action("dialog.openFile").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskOpenFile{Options: dialog.OpenFileOptions{ Title: input.Title, @@ -62,7 +62,7 @@ type DialogSaveFileOutput struct { Path string `json:"path,omitempty"` } -func (s *Subsystem) dialogSaveFile(_ context.Context, _ *mcp.CallToolRequest, input DialogSaveFileInput) (*mcp.CallToolResult, DialogSaveFileOutput, error) { +func (s *Subsystem) dialogSaveFile(_ context.Context, _ *mcp.CallToolRequest, input DialogSaveFileInput) (*mcp.CallToolResult, DialogSaveFileOutput, resultFailure) { r := s.core.Action("dialog.saveFile").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskSaveFile{Options: dialog.SaveFileOptions{ Title: input.Title, @@ -96,7 +96,7 @@ type DialogOpenDirectoryOutput struct { Path string `json:"path,omitempty"` } -func (s *Subsystem) dialogOpenDirectory(_ context.Context, _ *mcp.CallToolRequest, input DialogOpenDirectoryInput) (*mcp.CallToolResult, DialogOpenDirectoryOutput, error) { +func (s *Subsystem) dialogOpenDirectory(_ context.Context, _ *mcp.CallToolRequest, input DialogOpenDirectoryInput) (*mcp.CallToolResult, DialogOpenDirectoryOutput, resultFailure) { r := s.core.Action("dialog.openDirectory").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskOpenDirectory{Options: dialog.OpenDirectoryOptions{ Title: input.Title, @@ -128,7 +128,7 @@ type DialogConfirmOutput struct { Button string `json:"button"` } -func (s *Subsystem) dialogConfirm(_ context.Context, _ *mcp.CallToolRequest, input DialogConfirmInput) (*mcp.CallToolResult, DialogConfirmOutput, error) { +func (s *Subsystem) dialogConfirm(_ context.Context, _ *mcp.CallToolRequest, input DialogConfirmInput) (*mcp.CallToolResult, DialogConfirmOutput, resultFailure) { r := s.core.Action("dialog.question").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskQuestion{ Title: input.Title, @@ -161,7 +161,7 @@ type DialogMessageOutput struct { Button string `json:"button"` } -func (s *Subsystem) dialogMessage(_ context.Context, _ *mcp.CallToolRequest, input DialogMessageInput) (*mcp.CallToolResult, DialogMessageOutput, error) { +func (s *Subsystem) dialogMessage(_ context.Context, _ *mcp.CallToolRequest, input DialogMessageInput) (*mcp.CallToolResult, DialogMessageOutput, resultFailure) { dialogType := dialog.DialogInfo switch input.Type { case "", "info": @@ -211,7 +211,7 @@ type DialogPromptOutput struct { Confirmed bool `json:"confirmed"` } -func (s *Subsystem) dialogPrompt(_ context.Context, _ *mcp.CallToolRequest, input DialogPromptInput) (*mcp.CallToolResult, DialogPromptOutput, error) { +func (s *Subsystem) dialogPrompt(_ context.Context, _ *mcp.CallToolRequest, input DialogPromptInput) (*mcp.CallToolResult, DialogPromptOutput, resultFailure) { r := s.core.Action("dialog.prompt").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskPrompt{ Title: input.Title, @@ -243,7 +243,7 @@ type DialogInfoOutput struct { Button string `json:"button"` } -func (s *Subsystem) dialogInfo(_ context.Context, _ *mcp.CallToolRequest, input DialogInfoInput) (*mcp.CallToolResult, DialogInfoOutput, error) { +func (s *Subsystem) dialogInfo(_ context.Context, _ *mcp.CallToolRequest, input DialogInfoInput) (*mcp.CallToolResult, DialogInfoOutput, resultFailure) { r := s.core.Action("dialog.info").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskInfo{ Title: input.Title, @@ -275,7 +275,7 @@ type DialogWarningOutput struct { Button string `json:"button"` } -func (s *Subsystem) dialogWarning(_ context.Context, _ *mcp.CallToolRequest, input DialogWarningInput) (*mcp.CallToolResult, DialogWarningOutput, error) { +func (s *Subsystem) dialogWarning(_ context.Context, _ *mcp.CallToolRequest, input DialogWarningInput) (*mcp.CallToolResult, DialogWarningOutput, resultFailure) { r := s.core.Action("dialog.warning").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskWarning{ Title: input.Title, @@ -307,7 +307,7 @@ type DialogErrorOutput struct { Button string `json:"button"` } -func (s *Subsystem) dialogError(_ context.Context, _ *mcp.CallToolRequest, input DialogErrorInput) (*mcp.CallToolResult, DialogErrorOutput, error) { +func (s *Subsystem) dialogError(_ context.Context, _ *mcp.CallToolRequest, input DialogErrorInput) (*mcp.CallToolResult, DialogErrorOutput, resultFailure) { r := s.core.Action("dialog.error").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dialog.TaskError{ Title: input.Title, diff --git a/go/pkg/mcp/tools_display.go b/go/pkg/mcp/tools_display.go index 7e1dff0d..f6d2d0a8 100644 --- a/go/pkg/mcp/tools_display.go +++ b/go/pkg/mcp/tools_display.go @@ -21,7 +21,7 @@ type SchemeResolveOutput struct { Body string `json:"body"` } -func (s *Subsystem) schemeResolve(_ context.Context, _ *mcp.CallToolRequest, input SchemeResolveInput) (*mcp.CallToolResult, SchemeResolveOutput, error) { +func (s *Subsystem) schemeResolve(_ context.Context, _ *mcp.CallToolRequest, input SchemeResolveInput) (*mcp.CallToolResult, SchemeResolveOutput, resultFailure) { result := s.core.Action("display.resolveScheme").Run(context.Background(), core.NewOptions( core.Option{Key: "url", Value: input.URL}, )) diff --git a/go/pkg/mcp/tools_dock.go b/go/pkg/mcp/tools_dock.go index 17909c11..9419fb69 100644 --- a/go/pkg/mcp/tools_dock.go +++ b/go/pkg/mcp/tools_dock.go @@ -16,7 +16,7 @@ type DockShowOutput struct { Success bool `json:"success"` } -func (s *Subsystem) dockShow(_ context.Context, _ *mcp.CallToolRequest, _ DockShowInput) (*mcp.CallToolResult, DockShowOutput, error) { +func (s *Subsystem) dockShow(_ context.Context, _ *mcp.CallToolRequest, _ DockShowInput) (*mcp.CallToolResult, DockShowOutput, resultFailure) { r := s.core.Action("dock.showIcon").Run(context.Background(), core.NewOptions()) if !r.OK { if e, ok := r.Value.(error); ok { @@ -34,7 +34,7 @@ type DockHideOutput struct { Success bool `json:"success"` } -func (s *Subsystem) dockHide(_ context.Context, _ *mcp.CallToolRequest, _ DockHideInput) (*mcp.CallToolResult, DockHideOutput, error) { +func (s *Subsystem) dockHide(_ context.Context, _ *mcp.CallToolRequest, _ DockHideInput) (*mcp.CallToolResult, DockHideOutput, resultFailure) { r := s.core.Action("dock.hideIcon").Run(context.Background(), core.NewOptions()) if !r.OK { if e, ok := r.Value.(error); ok { @@ -54,7 +54,7 @@ type DockBadgeOutput struct { Success bool `json:"success"` } -func (s *Subsystem) dockBadge(_ context.Context, _ *mcp.CallToolRequest, input DockBadgeInput) (*mcp.CallToolResult, DockBadgeOutput, error) { +func (s *Subsystem) dockBadge(_ context.Context, _ *mcp.CallToolRequest, input DockBadgeInput) (*mcp.CallToolResult, DockBadgeOutput, resultFailure) { r := s.core.Action("dock.setBadge").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dock.TaskSetBadge{Label: input.Label}}, )) @@ -75,7 +75,7 @@ type DockRemoveBadgeOutput struct { Success bool `json:"success"` } -func (s *Subsystem) dockRemoveBadge(_ context.Context, _ *mcp.CallToolRequest, _ DockRemoveBadgeInput) (*mcp.CallToolResult, DockRemoveBadgeOutput, error) { +func (s *Subsystem) dockRemoveBadge(_ context.Context, _ *mcp.CallToolRequest, _ DockRemoveBadgeInput) (*mcp.CallToolResult, DockRemoveBadgeOutput, resultFailure) { r := s.core.Action("dock.removeBadge").Run(context.Background(), core.NewOptions()) if !r.OK { if e, ok := r.Value.(error); ok { @@ -94,7 +94,7 @@ type DockInfoOutput struct { Visible bool `json:"visible"` } -func (s *Subsystem) dockInfo(_ context.Context, _ *mcp.CallToolRequest, _ DockInfoInput) (*mcp.CallToolResult, DockInfoOutput, error) { +func (s *Subsystem) dockInfo(_ context.Context, _ *mcp.CallToolRequest, _ DockInfoInput) (*mcp.CallToolResult, DockInfoOutput, resultFailure) { r := s.core.QUERY(dock.QueryVisible{}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -119,7 +119,7 @@ type DockSetProgressBarOutput struct { Success bool `json:"success"` } -func (s *Subsystem) dockSetProgressBar(_ context.Context, _ *mcp.CallToolRequest, input DockSetProgressBarInput) (*mcp.CallToolResult, DockSetProgressBarOutput, error) { +func (s *Subsystem) dockSetProgressBar(_ context.Context, _ *mcp.CallToolRequest, input DockSetProgressBarInput) (*mcp.CallToolResult, DockSetProgressBarOutput, resultFailure) { r := s.core.Action("dock.setProgressBar").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dock.TaskSetProgressBar{Progress: input.Progress}}, )) @@ -142,7 +142,7 @@ type DockBounceOutput struct { RequestID int `json:"requestId"` } -func (s *Subsystem) dockBounce(_ context.Context, _ *mcp.CallToolRequest, input DockBounceInput) (*mcp.CallToolResult, DockBounceOutput, error) { +func (s *Subsystem) dockBounce(_ context.Context, _ *mcp.CallToolRequest, input DockBounceInput) (*mcp.CallToolResult, DockBounceOutput, resultFailure) { r := s.core.Action("dock.bounce").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dock.TaskBounce{BounceType: input.BounceType}}, )) @@ -169,7 +169,7 @@ type DockStopBounceOutput struct { Success bool `json:"success"` } -func (s *Subsystem) dockStopBounce(_ context.Context, _ *mcp.CallToolRequest, input DockStopBounceInput) (*mcp.CallToolResult, DockStopBounceOutput, error) { +func (s *Subsystem) dockStopBounce(_ context.Context, _ *mcp.CallToolRequest, input DockStopBounceInput) (*mcp.CallToolResult, DockStopBounceOutput, resultFailure) { r := s.core.Action("dock.stopBounce").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: dock.TaskStopBounce{RequestID: input.RequestID}}, )) diff --git a/go/pkg/mcp/tools_environment.go b/go/pkg/mcp/tools_environment.go index b12a65d8..71c4a791 100644 --- a/go/pkg/mcp/tools_environment.go +++ b/go/pkg/mcp/tools_environment.go @@ -16,7 +16,7 @@ type ThemeGetOutput struct { Theme environment.ThemeInfo `json:"theme"` } -func (s *Subsystem) themeGet(_ context.Context, _ *mcp.CallToolRequest, _ ThemeGetInput) (*mcp.CallToolResult, ThemeGetOutput, error) { +func (s *Subsystem) themeGet(_ context.Context, _ *mcp.CallToolRequest, _ ThemeGetInput) (*mcp.CallToolResult, ThemeGetOutput, resultFailure) { result := s.core.QUERY(environment.QueryTheme{}) if !result.OK { if err, ok := result.Value.(error); ok { @@ -38,7 +38,7 @@ type ThemeSystemOutput struct { Info environment.EnvironmentInfo `json:"info"` } -func (s *Subsystem) themeSystem(_ context.Context, _ *mcp.CallToolRequest, _ ThemeSystemInput) (*mcp.CallToolResult, ThemeSystemOutput, error) { +func (s *Subsystem) themeSystem(_ context.Context, _ *mcp.CallToolRequest, _ ThemeSystemInput) (*mcp.CallToolResult, ThemeSystemOutput, resultFailure) { result := s.core.QUERY(environment.QueryInfo{}) if !result.OK { if err, ok := result.Value.(error); ok { @@ -63,7 +63,7 @@ type ThemeSetOutput struct { Theme environment.ThemeInfo `json:"theme"` } -func (s *Subsystem) themeSet(_ context.Context, _ *mcp.CallToolRequest, input ThemeSetInput) (*mcp.CallToolResult, ThemeSetOutput, error) { +func (s *Subsystem) themeSet(_ context.Context, _ *mcp.CallToolRequest, input ThemeSetInput) (*mcp.CallToolResult, ThemeSetOutput, resultFailure) { result := s.core.Action("environment.setTheme").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: environment.TaskSetTheme{Theme: input.Theme}}, )) diff --git a/go/pkg/mcp/tools_events.go b/go/pkg/mcp/tools_events.go index e8b59a3f..de515d65 100644 --- a/go/pkg/mcp/tools_events.go +++ b/go/pkg/mcp/tools_events.go @@ -19,7 +19,7 @@ type EventEmitOutput struct { Cancelled bool `json:"cancelled"` } -func (s *Subsystem) eventEmit(_ context.Context, _ *mcp.CallToolRequest, input EventEmitInput) (*mcp.CallToolResult, EventEmitOutput, error) { +func (s *Subsystem) eventEmit(_ context.Context, _ *mcp.CallToolRequest, input EventEmitInput) (*mcp.CallToolResult, EventEmitOutput, resultFailure) { r := s.core.Action("events.emit").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: events.TaskEmit{Name: input.Name, Data: input.Data}}, )) @@ -45,7 +45,7 @@ type EventOnOutput struct { Success bool `json:"success"` } -func (s *Subsystem) eventOn(_ context.Context, _ *mcp.CallToolRequest, input EventOnInput) (*mcp.CallToolResult, EventOnOutput, error) { +func (s *Subsystem) eventOn(_ context.Context, _ *mcp.CallToolRequest, input EventOnInput) (*mcp.CallToolResult, EventOnOutput, resultFailure) { r := s.core.Action("events.on").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: events.TaskOn{Name: input.Name}}, )) @@ -67,7 +67,7 @@ type EventSubscribeOutput struct { Success bool `json:"success"` } -func (s *Subsystem) eventSubscribe(ctx context.Context, req *mcp.CallToolRequest, input EventSubscribeInput) (*mcp.CallToolResult, EventSubscribeOutput, error) { +func (s *Subsystem) eventSubscribe(ctx context.Context, req *mcp.CallToolRequest, input EventSubscribeInput) (*mcp.CallToolResult, EventSubscribeOutput, resultFailure) { result, output, err := s.eventOn(ctx, req, EventOnInput{Name: input.Name}) if err != nil { return nil, EventSubscribeOutput{}, err @@ -87,7 +87,7 @@ type EventOffOutput struct { Success bool `json:"success"` } -func (s *Subsystem) eventOff(_ context.Context, _ *mcp.CallToolRequest, input EventOffInput) (*mcp.CallToolResult, EventOffOutput, error) { +func (s *Subsystem) eventOff(_ context.Context, _ *mcp.CallToolRequest, input EventOffInput) (*mcp.CallToolResult, EventOffOutput, resultFailure) { r := s.core.Action("events.off").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: events.TaskOff{Name: input.Name}}, )) @@ -109,7 +109,7 @@ type EventUnsubscribeOutput struct { Success bool `json:"success"` } -func (s *Subsystem) eventUnsubscribe(ctx context.Context, req *mcp.CallToolRequest, input EventUnsubscribeInput) (*mcp.CallToolResult, EventUnsubscribeOutput, error) { +func (s *Subsystem) eventUnsubscribe(ctx context.Context, req *mcp.CallToolRequest, input EventUnsubscribeInput) (*mcp.CallToolResult, EventUnsubscribeOutput, resultFailure) { result, output, err := s.eventOff(ctx, req, EventOffInput{Name: input.Name}) if err != nil { return nil, EventUnsubscribeOutput{}, err @@ -127,7 +127,7 @@ type EventListOutput struct { Listeners []events.ListenerInfo `json:"listeners"` } -func (s *Subsystem) eventList(_ context.Context, _ *mcp.CallToolRequest, _ EventListInput) (*mcp.CallToolResult, EventListOutput, error) { +func (s *Subsystem) eventList(_ context.Context, _ *mcp.CallToolRequest, _ EventListInput) (*mcp.CallToolResult, EventListOutput, resultFailure) { r := s.core.QUERY(events.QueryListeners{}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -149,7 +149,7 @@ type EventInfoOutput struct { Info events.ServerInfo `json:"info"` } -func (s *Subsystem) eventInfo(_ context.Context, _ *mcp.CallToolRequest, _ EventInfoInput) (*mcp.CallToolResult, EventInfoOutput, error) { +func (s *Subsystem) eventInfo(_ context.Context, _ *mcp.CallToolRequest, _ EventInfoInput) (*mcp.CallToolResult, EventInfoOutput, resultFailure) { r := s.core.QUERY(events.QueryServerInfo{}) if !r.OK { return nil, EventInfoOutput{}, nil diff --git a/go/pkg/mcp/tools_keybinding.go b/go/pkg/mcp/tools_keybinding.go index 147a00c8..7c683567 100644 --- a/go/pkg/mcp/tools_keybinding.go +++ b/go/pkg/mcp/tools_keybinding.go @@ -19,7 +19,7 @@ type KeybindingAddOutput struct { Success bool `json:"success"` } -func (s *Subsystem) keybindingAdd(_ context.Context, _ *mcp.CallToolRequest, input KeybindingAddInput) (*mcp.CallToolResult, KeybindingAddOutput, error) { +func (s *Subsystem) keybindingAdd(_ context.Context, _ *mcp.CallToolRequest, input KeybindingAddInput) (*mcp.CallToolResult, KeybindingAddOutput, resultFailure) { r := s.core.Action("keybinding.add").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: keybinding.TaskAdd{Accelerator: input.Accelerator, Description: input.Description}}, )) @@ -41,7 +41,7 @@ type KeybindingRemoveOutput struct { Success bool `json:"success"` } -func (s *Subsystem) keybindingRemove(_ context.Context, _ *mcp.CallToolRequest, input KeybindingRemoveInput) (*mcp.CallToolResult, KeybindingRemoveOutput, error) { +func (s *Subsystem) keybindingRemove(_ context.Context, _ *mcp.CallToolRequest, input KeybindingRemoveInput) (*mcp.CallToolResult, KeybindingRemoveOutput, resultFailure) { r := s.core.Action("keybinding.remove").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: keybinding.TaskRemove{Accelerator: input.Accelerator}}, )) diff --git a/go/pkg/mcp/tools_layout.go b/go/pkg/mcp/tools_layout.go index e3a9aaa3..6291fcb5 100644 --- a/go/pkg/mcp/tools_layout.go +++ b/go/pkg/mcp/tools_layout.go @@ -18,7 +18,7 @@ type LayoutSaveOutput struct { Success bool `json:"success"` } -func (s *Subsystem) layoutSave(_ context.Context, _ *mcp.CallToolRequest, input LayoutSaveInput) (*mcp.CallToolResult, LayoutSaveOutput, error) { +func (s *Subsystem) layoutSave(_ context.Context, _ *mcp.CallToolRequest, input LayoutSaveInput) (*mcp.CallToolResult, LayoutSaveOutput, resultFailure) { result := s.core.Action("window.saveLayout").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSaveLayout{Name: input.Name}}, )) @@ -40,7 +40,7 @@ type LayoutRestoreOutput struct { Success bool `json:"success"` } -func (s *Subsystem) layoutRestore(_ context.Context, _ *mcp.CallToolRequest, input LayoutRestoreInput) (*mcp.CallToolResult, LayoutRestoreOutput, error) { +func (s *Subsystem) layoutRestore(_ context.Context, _ *mcp.CallToolRequest, input LayoutRestoreInput) (*mcp.CallToolResult, LayoutRestoreOutput, resultFailure) { result := s.core.Action("window.restoreLayout").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskRestoreLayout{Name: input.Name}}, )) @@ -60,7 +60,7 @@ type LayoutListOutput struct { Layouts []window.LayoutInfo `json:"layouts"` } -func (s *Subsystem) layoutList(_ context.Context, _ *mcp.CallToolRequest, _ LayoutListInput) (*mcp.CallToolResult, LayoutListOutput, error) { +func (s *Subsystem) layoutList(_ context.Context, _ *mcp.CallToolRequest, _ LayoutListInput) (*mcp.CallToolResult, LayoutListOutput, resultFailure) { result := s.core.QUERY(window.QueryLayoutList{}) if !result.OK { if err, ok := result.Value.(error); ok { @@ -84,7 +84,7 @@ type LayoutDeleteOutput struct { Success bool `json:"success"` } -func (s *Subsystem) layoutDelete(_ context.Context, _ *mcp.CallToolRequest, input LayoutDeleteInput) (*mcp.CallToolResult, LayoutDeleteOutput, error) { +func (s *Subsystem) layoutDelete(_ context.Context, _ *mcp.CallToolRequest, input LayoutDeleteInput) (*mcp.CallToolResult, LayoutDeleteOutput, resultFailure) { result := s.core.Action("window.deleteLayout").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskDeleteLayout{Name: input.Name}}, )) @@ -106,7 +106,7 @@ type LayoutGetOutput struct { Layout *window.Layout `json:"layout"` } -func (s *Subsystem) layoutGet(_ context.Context, _ *mcp.CallToolRequest, input LayoutGetInput) (*mcp.CallToolResult, LayoutGetOutput, error) { +func (s *Subsystem) layoutGet(_ context.Context, _ *mcp.CallToolRequest, input LayoutGetInput) (*mcp.CallToolResult, LayoutGetOutput, resultFailure) { result := s.core.QUERY(window.QueryLayoutGet{Name: input.Name}) if !result.OK { if err, ok := result.Value.(error); ok { @@ -131,7 +131,7 @@ type LayoutTileOutput struct { Success bool `json:"success"` } -func (s *Subsystem) layoutTile(_ context.Context, _ *mcp.CallToolRequest, input LayoutTileInput) (*mcp.CallToolResult, LayoutTileOutput, error) { +func (s *Subsystem) layoutTile(_ context.Context, _ *mcp.CallToolRequest, input LayoutTileInput) (*mcp.CallToolResult, LayoutTileOutput, resultFailure) { result := s.core.Action("window.tileWindows").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskTileWindows{Mode: input.Mode, Windows: input.Windows}}, )) @@ -154,7 +154,7 @@ type LayoutSnapOutput struct { Success bool `json:"success"` } -func (s *Subsystem) layoutSnap(_ context.Context, _ *mcp.CallToolRequest, input LayoutSnapInput) (*mcp.CallToolResult, LayoutSnapOutput, error) { +func (s *Subsystem) layoutSnap(_ context.Context, _ *mcp.CallToolRequest, input LayoutSnapInput) (*mcp.CallToolResult, LayoutSnapOutput, resultFailure) { result := s.core.Action("window.snapWindow").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSnapWindow{Name: input.Name, Position: input.Position}}, )) @@ -178,7 +178,7 @@ type LayoutStackOutput struct { Success bool `json:"success"` } -func (s *Subsystem) layoutStack(_ context.Context, _ *mcp.CallToolRequest, input LayoutStackInput) (*mcp.CallToolResult, LayoutStackOutput, error) { +func (s *Subsystem) layoutStack(_ context.Context, _ *mcp.CallToolRequest, input LayoutStackInput) (*mcp.CallToolResult, LayoutStackOutput, resultFailure) { result := s.core.Action("window.stackWindows").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskStackWindows{Windows: input.Windows, OffsetX: input.OffsetX, OffsetY: input.OffsetY}}, )) @@ -201,7 +201,7 @@ type LayoutWorkflowOutput struct { Success bool `json:"success"` } -func (s *Subsystem) layoutWorkflow(_ context.Context, _ *mcp.CallToolRequest, input LayoutWorkflowInput) (*mcp.CallToolResult, LayoutWorkflowOutput, error) { +func (s *Subsystem) layoutWorkflow(_ context.Context, _ *mcp.CallToolRequest, input LayoutWorkflowInput) (*mcp.CallToolResult, LayoutWorkflowOutput, resultFailure) { result := s.core.Action("window.applyWorkflow").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskApplyWorkflow{Workflow: input.Workflow, Windows: input.Windows}}, )) @@ -227,7 +227,7 @@ type LayoutBesideEditorOutput struct { Result window.LayoutBesideEditorResult `json:"result"` } -func (s *Subsystem) layoutBesideEditor(_ context.Context, _ *mcp.CallToolRequest, input LayoutBesideEditorInput) (*mcp.CallToolResult, LayoutBesideEditorOutput, error) { +func (s *Subsystem) layoutBesideEditor(_ context.Context, _ *mcp.CallToolRequest, input LayoutBesideEditorInput) (*mcp.CallToolResult, LayoutBesideEditorOutput, resultFailure) { result := s.core.Action("window.layoutBesideEditor").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskLayoutBesideEditor{ Name: input.Name, Editor: input.Editor, Side: input.Side, Ratio: input.Ratio, @@ -257,7 +257,7 @@ type LayoutSuggestOutput struct { Suggestion window.LayoutSuggestion `json:"suggestion"` } -func (s *Subsystem) layoutSuggest(_ context.Context, _ *mcp.CallToolRequest, input LayoutSuggestInput) (*mcp.CallToolResult, LayoutSuggestOutput, error) { +func (s *Subsystem) layoutSuggest(_ context.Context, _ *mcp.CallToolRequest, input LayoutSuggestInput) (*mcp.CallToolResult, LayoutSuggestOutput, resultFailure) { result := s.core.Action("window.layoutSuggest").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskLayoutSuggest{ScreenID: input.ScreenID, WindowCount: input.WindowCount}}, )) @@ -287,7 +287,7 @@ type ScreenFindSpaceOutput struct { Space window.ScreenSpace `json:"space"` } -func (s *Subsystem) screenFindSpace(_ context.Context, _ *mcp.CallToolRequest, input ScreenFindSpaceInput) (*mcp.CallToolResult, ScreenFindSpaceOutput, error) { +func (s *Subsystem) screenFindSpace(_ context.Context, _ *mcp.CallToolRequest, input ScreenFindSpaceInput) (*mcp.CallToolResult, ScreenFindSpaceOutput, resultFailure) { result := s.core.Action("window.findSpace").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskScreenFindSpace{ ScreenID: input.ScreenID, Width: input.Width, Height: input.Height, Padding: input.Padding, @@ -319,7 +319,7 @@ type WindowArrangePairOutput struct { Arrangement window.PairArrangement `json:"arrangement"` } -func (s *Subsystem) windowArrangePair(_ context.Context, _ *mcp.CallToolRequest, input WindowArrangePairInput) (*mcp.CallToolResult, WindowArrangePairOutput, error) { +func (s *Subsystem) windowArrangePair(_ context.Context, _ *mcp.CallToolRequest, input WindowArrangePairInput) (*mcp.CallToolResult, WindowArrangePairOutput, resultFailure) { result := s.core.Action("window.arrangePair").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskWindowArrangePair{ Primary: input.Primary, Secondary: input.Secondary, ScreenID: input.ScreenID, Ratio: input.Ratio, diff --git a/go/pkg/mcp/tools_lifecycle.go b/go/pkg/mcp/tools_lifecycle.go index f6cbdd83..da535a48 100644 --- a/go/pkg/mcp/tools_lifecycle.go +++ b/go/pkg/mcp/tools_lifecycle.go @@ -16,7 +16,7 @@ type AppQuitOutput struct { Success bool `json:"success"` } -func (s *Subsystem) appQuit(_ context.Context, _ *mcp.CallToolRequest, _ AppQuitInput) (*mcp.CallToolResult, AppQuitOutput, error) { +func (s *Subsystem) appQuit(_ context.Context, _ *mcp.CallToolRequest, _ AppQuitInput) (*mcp.CallToolResult, AppQuitOutput, resultFailure) { // Broadcast the will-terminate action which triggers application shutdown coreutil.DispatchAction(s.core, "mcp.appQuit", lifecycle.ActionWillTerminate{}) return nil, AppQuitOutput{Success: true}, nil diff --git a/go/pkg/mcp/tools_marketplace.go b/go/pkg/mcp/tools_marketplace.go index 253f147a..632cbf99 100644 --- a/go/pkg/mcp/tools_marketplace.go +++ b/go/pkg/mcp/tools_marketplace.go @@ -18,7 +18,7 @@ type MarketplaceListOutput struct { Manifests []marketplace.Manifest `json:"manifests"` } -func (s *Subsystem) marketplaceList(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceListInput) (*mcp.CallToolResult, MarketplaceListOutput, error) { +func (s *Subsystem) marketplaceList(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceListInput) (*mcp.CallToolResult, MarketplaceListOutput, resultFailure) { r := s.core.Action("display.marketplace.list").Run(context.Background(), core.NewOptions( core.Option{Key: "url", Value: input.URL}, )) @@ -43,7 +43,7 @@ type MarketplaceFetchInput struct { URL string `json:"url"` } -func (s *Subsystem) marketplaceFetch(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceFetchInput) (*mcp.CallToolResult, marketplace.Manifest, error) { +func (s *Subsystem) marketplaceFetch(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceFetchInput) (*mcp.CallToolResult, marketplace.Manifest, resultFailure) { r := s.core.Action("display.marketplace.fetch").Run(context.Background(), core.NewOptions( core.Option{Key: "url", Value: input.URL}, )) @@ -69,7 +69,7 @@ type MarketplaceVerifyOutput struct { Digest string `json:"digest"` } -func (s *Subsystem) marketplaceVerify(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceVerifyInput) (*mcp.CallToolResult, MarketplaceVerifyOutput, error) { +func (s *Subsystem) marketplaceVerify(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceVerifyInput) (*mcp.CallToolResult, MarketplaceVerifyOutput, resultFailure) { r := s.core.Action("display.marketplace.verify").Run(context.Background(), core.NewOptions( core.Option{Key: "url", Value: input.URL}, )) @@ -103,7 +103,7 @@ type MarketplaceInstallOutput struct { InstallDir string `json:"install_dir"` } -func (s *Subsystem) marketplaceInstall(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceInstallInput) (*mcp.CallToolResult, MarketplaceInstallOutput, error) { +func (s *Subsystem) marketplaceInstall(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceInstallInput) (*mcp.CallToolResult, MarketplaceInstallOutput, resultFailure) { r := s.core.Action("display.marketplace.install").Run(context.Background(), core.NewOptions( core.Option{Key: "url", Value: input.URL}, core.Option{Key: "install_dir", Value: input.InstallDir}, diff --git a/go/pkg/mcp/tools_menu.go b/go/pkg/mcp/tools_menu.go index e4d4e069..90866ab6 100644 --- a/go/pkg/mcp/tools_menu.go +++ b/go/pkg/mcp/tools_menu.go @@ -18,7 +18,7 @@ type MenuSetInput struct { Items []map[string]any `json:"items"` } -func (s *Subsystem) menuGet(_ context.Context, _ *mcp.CallToolRequest, _ MenuGetInput) (*mcp.CallToolResult, MenuOutput, error) { +func (s *Subsystem) menuGet(_ context.Context, _ *mcp.CallToolRequest, _ MenuGetInput) (*mcp.CallToolResult, MenuOutput, resultFailure) { items, err := s.queryMenuItems() if err != nil { return nil, MenuOutput{}, err @@ -26,7 +26,7 @@ func (s *Subsystem) menuGet(_ context.Context, _ *mcp.CallToolRequest, _ MenuGet return nil, MenuOutput{Items: items}, nil } -func (s *Subsystem) menuSet(_ context.Context, _ *mcp.CallToolRequest, input MenuSetInput) (*mcp.CallToolResult, MenuOutput, error) { +func (s *Subsystem) menuSet(_ context.Context, _ *mcp.CallToolRequest, input MenuSetInput) (*mcp.CallToolResult, MenuOutput, resultFailure) { items, err := decodeMenuItems(input.Items) if err != nil { return nil, MenuOutput{}, err @@ -47,7 +47,7 @@ func (s *Subsystem) menuSet(_ context.Context, _ *mcp.CallToolRequest, input Men return nil, MenuOutput{Items: snapshot}, nil } -func (s *Subsystem) queryMenuItems() ([]map[string]any, error) { +func (s *Subsystem) queryMenuItems() ([]map[string]any, resultFailure) { r := s.core.QUERY(menu.QueryGetAppMenu{}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -102,7 +102,7 @@ func encodeMenuItems(items []menu.MenuItem) []map[string]any { return out } -func decodeMenuItems(items []map[string]any) ([]menu.MenuItem, error) { +func decodeMenuItems(items []map[string]any) ([]menu.MenuItem, resultFailure) { if len(items) == 0 { return nil, nil } @@ -131,7 +131,7 @@ func decodeMenuItems(items []map[string]any) ([]menu.MenuItem, error) { return out, nil } -func decodeMenuChildren(value any) ([]menu.MenuItem, error) { +func decodeMenuChildren(value any) ([]menu.MenuItem, resultFailure) { switch children := value.(type) { case nil: return nil, nil @@ -181,7 +181,7 @@ func encodeMenuRole(role menu.MenuRole) string { } } -func decodeMenuRole(role string) (*menu.MenuRole, error) { +func decodeMenuRole(role string) (*menu.MenuRole, resultFailure) { switch core.Trim(core.Lower(role)) { case "": return nil, nil diff --git a/go/pkg/mcp/tools_notification.go b/go/pkg/mcp/tools_notification.go index 201943a7..ca98110f 100644 --- a/go/pkg/mcp/tools_notification.go +++ b/go/pkg/mcp/tools_notification.go @@ -20,7 +20,7 @@ type NotificationShowOutput struct { Success bool `json:"success"` } -func (s *Subsystem) notificationShow(_ context.Context, _ *mcp.CallToolRequest, input NotificationShowInput) (*mcp.CallToolResult, NotificationShowOutput, error) { +func (s *Subsystem) notificationShow(_ context.Context, _ *mcp.CallToolRequest, input NotificationShowInput) (*mcp.CallToolResult, NotificationShowOutput, resultFailure) { result := s.core.Action("notification.send").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: notification.TaskSend{Options: notification.NotificationOptions{ Title: input.Title, @@ -44,7 +44,7 @@ type NotificationPermissionRequestOutput struct { Granted bool `json:"granted"` } -func (s *Subsystem) notificationPermissionRequest(_ context.Context, _ *mcp.CallToolRequest, _ NotificationPermissionRequestInput) (*mcp.CallToolResult, NotificationPermissionRequestOutput, error) { +func (s *Subsystem) notificationPermissionRequest(_ context.Context, _ *mcp.CallToolRequest, _ NotificationPermissionRequestInput) (*mcp.CallToolResult, NotificationPermissionRequestOutput, resultFailure) { result := s.core.Action("notification.requestPermission").Run(context.Background(), core.NewOptions()) if !result.OK { if err, ok := result.Value.(error); ok { @@ -66,7 +66,7 @@ type NotificationPermissionCheckOutput struct { Granted bool `json:"granted"` } -func (s *Subsystem) notificationPermissionCheck(_ context.Context, _ *mcp.CallToolRequest, _ NotificationPermissionCheckInput) (*mcp.CallToolResult, NotificationPermissionCheckOutput, error) { +func (s *Subsystem) notificationPermissionCheck(_ context.Context, _ *mcp.CallToolRequest, _ NotificationPermissionCheckInput) (*mcp.CallToolResult, NotificationPermissionCheckOutput, resultFailure) { result := s.core.QUERY(notification.QueryPermission{}) if !result.OK { if err, ok := result.Value.(error); ok { @@ -91,7 +91,7 @@ type NotificationClearOutput struct { Success bool `json:"success"` } -func (s *Subsystem) notificationClear(_ context.Context, _ *mcp.CallToolRequest, input NotificationClearInput) (*mcp.CallToolResult, NotificationClearOutput, error) { +func (s *Subsystem) notificationClear(_ context.Context, _ *mcp.CallToolRequest, input NotificationClearInput) (*mcp.CallToolResult, NotificationClearOutput, resultFailure) { result := s.core.Action("notification.clear").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: notification.TaskClear{ID: input.ID}}, )) @@ -118,7 +118,7 @@ type NotificationWithActionsOutput struct { Success bool `json:"success"` } -func (s *Subsystem) notificationWithActions(_ context.Context, _ *mcp.CallToolRequest, input NotificationWithActionsInput) (*mcp.CallToolResult, NotificationWithActionsOutput, error) { +func (s *Subsystem) notificationWithActions(_ context.Context, _ *mcp.CallToolRequest, input NotificationWithActionsInput) (*mcp.CallToolResult, NotificationWithActionsOutput, resultFailure) { result := s.core.Action("notification.send").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: notification.TaskSend{Options: notification.NotificationOptions{ Title: input.Title, diff --git a/go/pkg/mcp/tools_p2p.go b/go/pkg/mcp/tools_p2p.go index 444c08d2..2c6293db 100644 --- a/go/pkg/mcp/tools_p2p.go +++ b/go/pkg/mcp/tools_p2p.go @@ -19,7 +19,7 @@ type P2PPublishOutput struct { Success bool `json:"success"` } -func (s *Subsystem) p2pPublish(_ context.Context, _ *mcp.CallToolRequest, input P2PPublishInput) (*mcp.CallToolResult, P2PPublishOutput, error) { +func (s *Subsystem) p2pPublish(_ context.Context, _ *mcp.CallToolRequest, input P2PPublishInput) (*mcp.CallToolResult, P2PPublishOutput, resultFailure) { result := s.core.Action("p2p.publish").Run(context.Background(), core.NewOptions( core.Option{Key: "topic", Value: input.Topic}, core.Option{Key: "route", Value: input.Route}, @@ -41,7 +41,7 @@ type P2PStateOutput struct { State p2p.State `json:"state"` } -func (s *Subsystem) p2pState(_ context.Context, _ *mcp.CallToolRequest, _ P2PStateInput) (*mcp.CallToolResult, P2PStateOutput, error) { +func (s *Subsystem) p2pState(_ context.Context, _ *mcp.CallToolRequest, _ P2PStateInput) (*mcp.CallToolResult, P2PStateOutput, resultFailure) { result := s.core.Action("p2p.state").Run(context.Background(), core.Options{}) if !result.OK { if err, ok := result.Value.(error); ok { diff --git a/go/pkg/mcp/tools_screen.go b/go/pkg/mcp/tools_screen.go index b488c5d6..d91ce02e 100644 --- a/go/pkg/mcp/tools_screen.go +++ b/go/pkg/mcp/tools_screen.go @@ -17,7 +17,7 @@ type ScreenListOutput struct { Screens []screen.Screen `json:"screens"` } -func (s *Subsystem) screenList(_ context.Context, _ *mcp.CallToolRequest, _ ScreenListInput) (*mcp.CallToolResult, ScreenListOutput, error) { +func (s *Subsystem) screenList(_ context.Context, _ *mcp.CallToolRequest, _ ScreenListInput) (*mcp.CallToolResult, ScreenListOutput, resultFailure) { r := s.core.QUERY(screen.QueryAll{}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -41,7 +41,7 @@ type ScreenGetOutput struct { Screen *screen.Screen `json:"screen"` } -func (s *Subsystem) screenGet(_ context.Context, _ *mcp.CallToolRequest, input ScreenGetInput) (*mcp.CallToolResult, ScreenGetOutput, error) { +func (s *Subsystem) screenGet(_ context.Context, _ *mcp.CallToolRequest, input ScreenGetInput) (*mcp.CallToolResult, ScreenGetOutput, resultFailure) { r := s.core.QUERY(screen.QueryByID{ID: input.ID}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -63,7 +63,7 @@ type ScreenPrimaryOutput struct { Screen *screen.Screen `json:"screen"` } -func (s *Subsystem) screenPrimary(_ context.Context, _ *mcp.CallToolRequest, _ ScreenPrimaryInput) (*mcp.CallToolResult, ScreenPrimaryOutput, error) { +func (s *Subsystem) screenPrimary(_ context.Context, _ *mcp.CallToolRequest, _ ScreenPrimaryInput) (*mcp.CallToolResult, ScreenPrimaryOutput, resultFailure) { r := s.core.QUERY(screen.QueryPrimary{}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -88,7 +88,7 @@ type ScreenAtPointOutput struct { Screen *screen.Screen `json:"screen"` } -func (s *Subsystem) screenAtPoint(_ context.Context, _ *mcp.CallToolRequest, input ScreenAtPointInput) (*mcp.CallToolResult, ScreenAtPointOutput, error) { +func (s *Subsystem) screenAtPoint(_ context.Context, _ *mcp.CallToolRequest, input ScreenAtPointInput) (*mcp.CallToolResult, ScreenAtPointOutput, resultFailure) { r := s.core.QUERY(screen.QueryAtPoint{X: input.X, Y: input.Y}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -110,7 +110,7 @@ type ScreenWorkAreasOutput struct { WorkAreas []screen.Rect `json:"workAreas"` } -func (s *Subsystem) screenWorkAreas(_ context.Context, _ *mcp.CallToolRequest, _ ScreenWorkAreasInput) (*mcp.CallToolResult, ScreenWorkAreasOutput, error) { +func (s *Subsystem) screenWorkAreas(_ context.Context, _ *mcp.CallToolRequest, _ ScreenWorkAreasInput) (*mcp.CallToolResult, ScreenWorkAreasOutput, resultFailure) { r := s.core.QUERY(screen.QueryWorkAreas{}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -134,7 +134,7 @@ type ScreenWorkAreaOutput struct { WorkArea screen.Rect `json:"workArea"` } -func (s *Subsystem) screenWorkArea(_ context.Context, _ *mcp.CallToolRequest, input ScreenWorkAreaInput) (*mcp.CallToolResult, ScreenWorkAreaOutput, error) { +func (s *Subsystem) screenWorkArea(_ context.Context, _ *mcp.CallToolRequest, input ScreenWorkAreaInput) (*mcp.CallToolResult, ScreenWorkAreaOutput, resultFailure) { var query core.Query = screen.QueryPrimary{} if input.ID != "" { query = screen.QueryByID{ID: input.ID} @@ -165,7 +165,7 @@ type ScreenForWindowOutput struct { Screen *screen.Screen `json:"screen"` } -func (s *Subsystem) screenForWindow(_ context.Context, _ *mcp.CallToolRequest, input ScreenForWindowInput) (*mcp.CallToolResult, ScreenForWindowOutput, error) { +func (s *Subsystem) screenForWindow(_ context.Context, _ *mcp.CallToolRequest, input ScreenForWindowInput) (*mcp.CallToolResult, ScreenForWindowOutput, resultFailure) { r := s.core.QUERY(window.QueryWindowByName{Name: input.Name}) if !r.OK { return nil, ScreenForWindowOutput{}, nil diff --git a/go/pkg/mcp/tools_tray.go b/go/pkg/mcp/tools_tray.go index b02a3b17..0699e486 100644 --- a/go/pkg/mcp/tools_tray.go +++ b/go/pkg/mcp/tools_tray.go @@ -19,7 +19,7 @@ type TraySetIconOutput struct { Success bool `json:"success"` } -func (s *Subsystem) traySetIcon(_ context.Context, _ *mcp.CallToolRequest, input TraySetIconInput) (*mcp.CallToolResult, TraySetIconOutput, error) { +func (s *Subsystem) traySetIcon(_ context.Context, _ *mcp.CallToolRequest, input TraySetIconInput) (*mcp.CallToolResult, TraySetIconOutput, resultFailure) { if input.Base64 == "" { return nil, TraySetIconOutput{}, core.E("mcp.traySetIcon", "base64 icon data is required", nil) } @@ -48,7 +48,7 @@ type TraySetTooltipOutput struct { Success bool `json:"success"` } -func (s *Subsystem) traySetTooltip(_ context.Context, _ *mcp.CallToolRequest, input TraySetTooltipInput) (*mcp.CallToolResult, TraySetTooltipOutput, error) { +func (s *Subsystem) traySetTooltip(_ context.Context, _ *mcp.CallToolRequest, input TraySetTooltipInput) (*mcp.CallToolResult, TraySetTooltipOutput, resultFailure) { r := s.core.Action("systray.setTooltip").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: systray.TaskSetTrayTooltip{Tooltip: input.Tooltip}}, )) @@ -70,7 +70,7 @@ type TraySetLabelOutput struct { Success bool `json:"success"` } -func (s *Subsystem) traySetLabel(_ context.Context, _ *mcp.CallToolRequest, input TraySetLabelInput) (*mcp.CallToolResult, TraySetLabelOutput, error) { +func (s *Subsystem) traySetLabel(_ context.Context, _ *mcp.CallToolRequest, input TraySetLabelInput) (*mcp.CallToolResult, TraySetLabelOutput, resultFailure) { r := s.core.Action("systray.setLabel").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: systray.TaskSetTrayLabel{Label: input.Label}}, )) @@ -93,7 +93,7 @@ type TraySetMenuOutput struct { Success bool `json:"success"` } -func (s *Subsystem) traySetMenu(_ context.Context, _ *mcp.CallToolRequest, input TraySetMenuInput) (*mcp.CallToolResult, TraySetMenuOutput, error) { +func (s *Subsystem) traySetMenu(_ context.Context, _ *mcp.CallToolRequest, input TraySetMenuInput) (*mcp.CallToolResult, TraySetMenuOutput, resultFailure) { r := s.core.Action("systray.setMenu").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: systray.TaskSetTrayMenu{Items: input.Items}}, )) @@ -117,7 +117,7 @@ type TrayShowMessageOutput struct { Success bool `json:"success"` } -func (s *Subsystem) trayShowMessage(_ context.Context, _ *mcp.CallToolRequest, input TrayShowMessageInput) (*mcp.CallToolResult, TrayShowMessageOutput, error) { +func (s *Subsystem) trayShowMessage(_ context.Context, _ *mcp.CallToolRequest, input TrayShowMessageInput) (*mcp.CallToolResult, TrayShowMessageOutput, resultFailure) { r := s.core.Action("systray.showMessage").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: systray.TaskShowMessage{Title: input.Title, Message: input.Message}}, )) @@ -137,7 +137,7 @@ type TrayInfoOutput struct { Config map[string]any `json:"config"` } -func (s *Subsystem) trayInfo(_ context.Context, _ *mcp.CallToolRequest, _ TrayInfoInput) (*mcp.CallToolResult, TrayInfoOutput, error) { +func (s *Subsystem) trayInfo(_ context.Context, _ *mcp.CallToolRequest, _ TrayInfoInput) (*mcp.CallToolResult, TrayInfoOutput, resultFailure) { r := s.core.QUERY(systray.QueryInfo{}) if !r.OK { if e, ok := r.Value.(error); ok { diff --git a/go/pkg/mcp/tools_webview.go b/go/pkg/mcp/tools_webview.go index 8df46a0a..3b06a718 100644 --- a/go/pkg/mcp/tools_webview.go +++ b/go/pkg/mcp/tools_webview.go @@ -27,7 +27,7 @@ type WebviewEvalOutput struct { Window string `json:"window"` } -func (s *Subsystem) webviewEval(_ context.Context, _ *mcp.CallToolRequest, input WebviewEvalInput) (*mcp.CallToolResult, WebviewEvalOutput, error) { +func (s *Subsystem) webviewEval(_ context.Context, _ *mcp.CallToolRequest, input WebviewEvalInput) (*mcp.CallToolResult, WebviewEvalOutput, resultFailure) { r := s.core.Action("webview.evaluate").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskEvaluate{Window: input.Window, Script: input.Script}}, )) @@ -51,7 +51,7 @@ type WebviewClickOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewClick(_ context.Context, _ *mcp.CallToolRequest, input WebviewClickInput) (*mcp.CallToolResult, WebviewClickOutput, error) { +func (s *Subsystem) webviewClick(_ context.Context, _ *mcp.CallToolRequest, input WebviewClickInput) (*mcp.CallToolResult, WebviewClickOutput, resultFailure) { r := s.core.Action("webview.click").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskClick{Window: input.Window, Selector: input.Selector}}, )) @@ -76,7 +76,7 @@ type WebviewTypeOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewType(_ context.Context, _ *mcp.CallToolRequest, input WebviewTypeInput) (*mcp.CallToolResult, WebviewTypeOutput, error) { +func (s *Subsystem) webviewType(_ context.Context, _ *mcp.CallToolRequest, input WebviewTypeInput) (*mcp.CallToolResult, WebviewTypeOutput, resultFailure) { r := s.core.Action("webview.type").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskType{Window: input.Window, Selector: input.Selector, Text: input.Text}}, )) @@ -100,7 +100,7 @@ type WebviewNavigateOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewNavigate(_ context.Context, _ *mcp.CallToolRequest, input WebviewNavigateInput) (*mcp.CallToolResult, WebviewNavigateOutput, error) { +func (s *Subsystem) webviewNavigate(_ context.Context, _ *mcp.CallToolRequest, input WebviewNavigateInput) (*mcp.CallToolResult, WebviewNavigateOutput, resultFailure) { r := s.core.Action("webview.navigate").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskNavigate{Window: input.Window, URL: input.URL}}, )) @@ -124,7 +124,7 @@ type WebviewScreenshotOutput struct { MimeType string `json:"mimeType"` } -func (s *Subsystem) webviewScreenshot(_ context.Context, _ *mcp.CallToolRequest, input WebviewScreenshotInput) (*mcp.CallToolResult, WebviewScreenshotOutput, error) { +func (s *Subsystem) webviewScreenshot(_ context.Context, _ *mcp.CallToolRequest, input WebviewScreenshotInput) (*mcp.CallToolResult, WebviewScreenshotOutput, resultFailure) { r := s.core.Action("webview.screenshot").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskScreenshot{Window: input.Window}}, )) @@ -153,7 +153,7 @@ type WebviewScrollOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewScroll(_ context.Context, _ *mcp.CallToolRequest, input WebviewScrollInput) (*mcp.CallToolResult, WebviewScrollOutput, error) { +func (s *Subsystem) webviewScroll(_ context.Context, _ *mcp.CallToolRequest, input WebviewScrollInput) (*mcp.CallToolResult, WebviewScrollOutput, resultFailure) { r := s.core.Action("webview.scroll").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskScroll{Window: input.Window, X: input.X, Y: input.Y}}, )) @@ -177,7 +177,7 @@ type WebviewHoverOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewHover(_ context.Context, _ *mcp.CallToolRequest, input WebviewHoverInput) (*mcp.CallToolResult, WebviewHoverOutput, error) { +func (s *Subsystem) webviewHover(_ context.Context, _ *mcp.CallToolRequest, input WebviewHoverInput) (*mcp.CallToolResult, WebviewHoverOutput, resultFailure) { r := s.core.Action("webview.hover").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskHover{Window: input.Window, Selector: input.Selector}}, )) @@ -202,7 +202,7 @@ type WebviewSelectOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewSelect(_ context.Context, _ *mcp.CallToolRequest, input WebviewSelectInput) (*mcp.CallToolResult, WebviewSelectOutput, error) { +func (s *Subsystem) webviewSelect(_ context.Context, _ *mcp.CallToolRequest, input WebviewSelectInput) (*mcp.CallToolResult, WebviewSelectOutput, resultFailure) { r := s.core.Action("webview.select").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskSelect{Window: input.Window, Selector: input.Selector, Value: input.Value}}, )) @@ -227,7 +227,7 @@ type WebviewCheckOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewCheck(_ context.Context, _ *mcp.CallToolRequest, input WebviewCheckInput) (*mcp.CallToolResult, WebviewCheckOutput, error) { +func (s *Subsystem) webviewCheck(_ context.Context, _ *mcp.CallToolRequest, input WebviewCheckInput) (*mcp.CallToolResult, WebviewCheckOutput, resultFailure) { r := s.core.Action("webview.check").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskCheck{Window: input.Window, Selector: input.Selector, Checked: input.Checked}}, )) @@ -252,7 +252,7 @@ type WebviewUploadOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewUpload(_ context.Context, _ *mcp.CallToolRequest, input WebviewUploadInput) (*mcp.CallToolResult, WebviewUploadOutput, error) { +func (s *Subsystem) webviewUpload(_ context.Context, _ *mcp.CallToolRequest, input WebviewUploadInput) (*mcp.CallToolResult, WebviewUploadOutput, resultFailure) { r := s.core.Action("webview.uploadFile").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskUploadFile{Window: input.Window, Selector: input.Selector, Paths: input.Paths}}, )) @@ -277,7 +277,7 @@ type WebviewViewportOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewViewport(_ context.Context, _ *mcp.CallToolRequest, input WebviewViewportInput) (*mcp.CallToolResult, WebviewViewportOutput, error) { +func (s *Subsystem) webviewViewport(_ context.Context, _ *mcp.CallToolRequest, input WebviewViewportInput) (*mcp.CallToolResult, WebviewViewportOutput, resultFailure) { r := s.core.Action("webview.setViewport").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskSetViewport{Window: input.Window, Width: input.Width, Height: input.Height}}, )) @@ -302,7 +302,7 @@ type WebviewConsoleOutput struct { Messages []webview.ConsoleMessage `json:"messages"` } -func (s *Subsystem) webviewConsole(_ context.Context, _ *mcp.CallToolRequest, input WebviewConsoleInput) (*mcp.CallToolResult, WebviewConsoleOutput, error) { +func (s *Subsystem) webviewConsole(_ context.Context, _ *mcp.CallToolRequest, input WebviewConsoleInput) (*mcp.CallToolResult, WebviewConsoleOutput, resultFailure) { r := s.core.QUERY(webview.QueryConsole{Window: input.Window, Level: input.Level, Limit: input.Limit}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -327,7 +327,7 @@ type WebviewConsoleClearOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewConsoleClear(_ context.Context, _ *mcp.CallToolRequest, input WebviewConsoleClearInput) (*mcp.CallToolResult, WebviewConsoleClearOutput, error) { +func (s *Subsystem) webviewConsoleClear(_ context.Context, _ *mcp.CallToolRequest, input WebviewConsoleClearInput) (*mcp.CallToolResult, WebviewConsoleClearOutput, resultFailure) { r := s.core.Action("webview.clearConsole").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskClearConsole{Window: input.Window}}, )) @@ -351,7 +351,7 @@ type WebviewQueryOutput struct { Element *webview.ElementInfo `json:"element"` } -func (s *Subsystem) webviewQuery(_ context.Context, _ *mcp.CallToolRequest, input WebviewQueryInput) (*mcp.CallToolResult, WebviewQueryOutput, error) { +func (s *Subsystem) webviewQuery(_ context.Context, _ *mcp.CallToolRequest, input WebviewQueryInput) (*mcp.CallToolResult, WebviewQueryOutput, resultFailure) { r := s.core.QUERY(webview.QuerySelector{Window: input.Window, Selector: input.Selector}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -377,7 +377,7 @@ type WebviewQueryAllOutput struct { Elements []*webview.ElementInfo `json:"elements"` } -func (s *Subsystem) webviewQueryAll(_ context.Context, _ *mcp.CallToolRequest, input WebviewQueryAllInput) (*mcp.CallToolResult, WebviewQueryAllOutput, error) { +func (s *Subsystem) webviewQueryAll(_ context.Context, _ *mcp.CallToolRequest, input WebviewQueryAllInput) (*mcp.CallToolResult, WebviewQueryAllOutput, resultFailure) { r := s.core.QUERY(webview.QuerySelectorAll{Window: input.Window, Selector: input.Selector}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -403,7 +403,7 @@ type WebviewDOMTreeOutput struct { HTML string `json:"html"` } -func (s *Subsystem) webviewDOMTree(_ context.Context, _ *mcp.CallToolRequest, input WebviewDOMTreeInput) (*mcp.CallToolResult, WebviewDOMTreeOutput, error) { +func (s *Subsystem) webviewDOMTree(_ context.Context, _ *mcp.CallToolRequest, input WebviewDOMTreeInput) (*mcp.CallToolResult, WebviewDOMTreeOutput, resultFailure) { r := s.core.QUERY(webview.QueryDOMTree{Window: input.Window, Selector: input.Selector}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -428,7 +428,7 @@ type WebviewURLOutput struct { URL string `json:"url"` } -func (s *Subsystem) webviewURL(_ context.Context, _ *mcp.CallToolRequest, input WebviewURLInput) (*mcp.CallToolResult, WebviewURLOutput, error) { +func (s *Subsystem) webviewURL(_ context.Context, _ *mcp.CallToolRequest, input WebviewURLInput) (*mcp.CallToolResult, WebviewURLOutput, resultFailure) { r := s.core.QUERY(webview.QueryURL{Window: input.Window}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -453,7 +453,7 @@ type WebviewTitleOutput struct { Title string `json:"title"` } -func (s *Subsystem) webviewTitle(_ context.Context, _ *mcp.CallToolRequest, input WebviewTitleInput) (*mcp.CallToolResult, WebviewTitleOutput, error) { +func (s *Subsystem) webviewTitle(_ context.Context, _ *mcp.CallToolRequest, input WebviewTitleInput) (*mcp.CallToolResult, WebviewTitleOutput, resultFailure) { r := s.core.QUERY(webview.QueryTitle{Window: input.Window}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -476,7 +476,7 @@ type WebviewListOutput struct { Windows []window.WindowInfo `json:"windows"` } -func (s *Subsystem) webviewList(_ context.Context, _ *mcp.CallToolRequest, _ WebviewListInput) (*mcp.CallToolResult, WebviewListOutput, error) { +func (s *Subsystem) webviewList(_ context.Context, _ *mcp.CallToolRequest, _ WebviewListInput) (*mcp.CallToolResult, WebviewListOutput, resultFailure) { r := s.core.QUERY(window.QueryWindowList{}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -502,7 +502,7 @@ type WebviewErrorsOutput struct { Errors []webview.ExceptionInfo `json:"errors,omitempty"` } -func (s *Subsystem) webviewErrors(_ context.Context, _ *mcp.CallToolRequest, input WebviewErrorsInput) (*mcp.CallToolResult, WebviewErrorsOutput, error) { +func (s *Subsystem) webviewErrors(_ context.Context, _ *mcp.CallToolRequest, input WebviewErrorsInput) (*mcp.CallToolResult, WebviewErrorsOutput, resultFailure) { r := s.core.QUERY(webview.QueryExceptions{Window: input.Window, Limit: input.Limit}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -527,7 +527,7 @@ type WebviewClearConsoleOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewClearConsole(_ context.Context, _ *mcp.CallToolRequest, input WebviewClearConsoleInput) (*mcp.CallToolResult, WebviewClearConsoleOutput, error) { +func (s *Subsystem) webviewClearConsole(_ context.Context, _ *mcp.CallToolRequest, input WebviewClearConsoleInput) (*mcp.CallToolResult, WebviewClearConsoleOutput, resultFailure) { _, out, err := s.webviewConsoleClear(context.Background(), nil, WebviewConsoleClearInput{Window: input.Window}) return nil, WebviewClearConsoleOutput{Success: out.Success}, err } @@ -543,7 +543,7 @@ type WebviewElementInfoOutput struct { Element *webview.ElementInfo `json:"element"` } -func (s *Subsystem) webviewElementInfo(_ context.Context, _ *mcp.CallToolRequest, input WebviewElementInfoInput) (*mcp.CallToolResult, WebviewElementInfoOutput, error) { +func (s *Subsystem) webviewElementInfo(_ context.Context, _ *mcp.CallToolRequest, input WebviewElementInfoInput) (*mcp.CallToolResult, WebviewElementInfoOutput, resultFailure) { _, out, err := s.webviewQuery(context.Background(), nil, WebviewQueryInput{Window: input.Window, Selector: input.Selector}) return nil, WebviewElementInfoOutput{Element: out.Element}, err } @@ -560,7 +560,7 @@ type WebviewHighlightOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewHighlight(_ context.Context, _ *mcp.CallToolRequest, input WebviewHighlightInput) (*mcp.CallToolResult, WebviewHighlightOutput, error) { +func (s *Subsystem) webviewHighlight(_ context.Context, _ *mcp.CallToolRequest, input WebviewHighlightInput) (*mcp.CallToolResult, WebviewHighlightOutput, resultFailure) { result, err := s.evaluateWebview(input.Window, webview.HighlightScript(input.Selector, input.Colour)) if err != nil { return nil, WebviewHighlightOutput{}, err @@ -580,7 +580,7 @@ type WebviewComputedStyleOutput struct { Styles map[string]any `json:"styles"` } -func (s *Subsystem) webviewComputedStyle(_ context.Context, _ *mcp.CallToolRequest, input WebviewComputedStyleInput) (*mcp.CallToolResult, WebviewComputedStyleOutput, error) { +func (s *Subsystem) webviewComputedStyle(_ context.Context, _ *mcp.CallToolRequest, input WebviewComputedStyleInput) (*mcp.CallToolResult, WebviewComputedStyleOutput, resultFailure) { result, err := s.evaluateWebview(input.Window, webview.ComputedStyleScript(input.Selector)) if err != nil { return nil, WebviewComputedStyleOutput{}, err @@ -602,7 +602,7 @@ type WebviewSourceOutput struct { HTML string `json:"html"` } -func (s *Subsystem) webviewSource(_ context.Context, _ *mcp.CallToolRequest, input WebviewSourceInput) (*mcp.CallToolResult, WebviewSourceOutput, error) { +func (s *Subsystem) webviewSource(_ context.Context, _ *mcp.CallToolRequest, input WebviewSourceInput) (*mcp.CallToolResult, WebviewSourceOutput, resultFailure) { r := s.core.QUERY(webview.QueryDOMTree{Window: input.Window}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -629,7 +629,7 @@ type WebviewScreenshotElementOutput struct { MimeType string `json:"mimeType"` } -func (s *Subsystem) webviewScreenshotElement(_ context.Context, _ *mcp.CallToolRequest, input WebviewScreenshotElementInput) (*mcp.CallToolResult, WebviewScreenshotElementOutput, error) { +func (s *Subsystem) webviewScreenshotElement(_ context.Context, _ *mcp.CallToolRequest, input WebviewScreenshotElementInput) (*mcp.CallToolResult, WebviewScreenshotElementOutput, resultFailure) { r := s.core.QUERY(webview.QuerySelector{Window: input.Window, Selector: input.Selector}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -684,7 +684,7 @@ type WebviewPDFOutput struct { MimeType string `json:"mimeType"` } -func (s *Subsystem) webviewPDF(_ context.Context, _ *mcp.CallToolRequest, input WebviewPDFInput) (*mcp.CallToolResult, WebviewPDFOutput, error) { +func (s *Subsystem) webviewPDF(_ context.Context, _ *mcp.CallToolRequest, input WebviewPDFInput) (*mcp.CallToolResult, WebviewPDFOutput, resultFailure) { r := s.core.Action("webview.print").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskPrint{Window: input.Window, ToPDF: true}}, )) @@ -711,7 +711,7 @@ type WebviewPrintOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewPrint(_ context.Context, _ *mcp.CallToolRequest, input WebviewPrintInput) (*mcp.CallToolResult, WebviewPrintOutput, error) { +func (s *Subsystem) webviewPrint(_ context.Context, _ *mcp.CallToolRequest, input WebviewPrintInput) (*mcp.CallToolResult, WebviewPrintOutput, resultFailure) { r := s.core.Action("webview.print").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskPrint{Window: input.Window}}, )) @@ -735,7 +735,7 @@ type WebviewNetworkOutput struct { Requests []map[string]any `json:"requests"` } -func (s *Subsystem) webviewNetwork(_ context.Context, _ *mcp.CallToolRequest, input WebviewNetworkInput) (*mcp.CallToolResult, WebviewNetworkOutput, error) { +func (s *Subsystem) webviewNetwork(_ context.Context, _ *mcp.CallToolRequest, input WebviewNetworkInput) (*mcp.CallToolResult, WebviewNetworkOutput, resultFailure) { result, err := s.evaluateWebview(input.Window, webview.NetworkLogScript(input.Limit)) if err != nil { return nil, WebviewNetworkOutput{}, err @@ -757,7 +757,7 @@ type WebviewNetworkClearOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewNetworkClear(_ context.Context, _ *mcp.CallToolRequest, input WebviewNetworkClearInput) (*mcp.CallToolResult, WebviewNetworkClearOutput, error) { +func (s *Subsystem) webviewNetworkClear(_ context.Context, _ *mcp.CallToolRequest, input WebviewNetworkClearInput) (*mcp.CallToolResult, WebviewNetworkClearOutput, resultFailure) { _, err := s.evaluateWebview(input.Window, webview.NetworkClearScript()) if err != nil { return nil, WebviewNetworkClearOutput{}, err @@ -775,7 +775,7 @@ type WebviewNetworkInjectOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewNetworkInject(_ context.Context, _ *mcp.CallToolRequest, input WebviewNetworkInjectInput) (*mcp.CallToolResult, WebviewNetworkInjectOutput, error) { +func (s *Subsystem) webviewNetworkInject(_ context.Context, _ *mcp.CallToolRequest, input WebviewNetworkInjectInput) (*mcp.CallToolResult, WebviewNetworkInjectOutput, resultFailure) { _, err := s.evaluateWebview(input.Window, webview.NetworkInitScript()) if err != nil { return nil, WebviewNetworkInjectOutput{}, err @@ -793,7 +793,7 @@ type WebviewPerformanceOutput struct { Metrics map[string]any `json:"metrics"` } -func (s *Subsystem) webviewPerformance(_ context.Context, _ *mcp.CallToolRequest, input WebviewPerformanceInput) (*mcp.CallToolResult, WebviewPerformanceOutput, error) { +func (s *Subsystem) webviewPerformance(_ context.Context, _ *mcp.CallToolRequest, input WebviewPerformanceInput) (*mcp.CallToolResult, WebviewPerformanceOutput, resultFailure) { result, err := s.evaluateWebview(input.Window, webview.PerformanceScript()) if err != nil { return nil, WebviewPerformanceOutput{}, err @@ -815,7 +815,7 @@ type WebviewResourcesOutput struct { Resources []map[string]any `json:"resources"` } -func (s *Subsystem) webviewResources(_ context.Context, _ *mcp.CallToolRequest, input WebviewResourcesInput) (*mcp.CallToolResult, WebviewResourcesOutput, error) { +func (s *Subsystem) webviewResources(_ context.Context, _ *mcp.CallToolRequest, input WebviewResourcesInput) (*mcp.CallToolResult, WebviewResourcesOutput, resultFailure) { result, err := s.evaluateWebview(input.Window, webview.ResourcesScript()) if err != nil { return nil, WebviewResourcesOutput{}, err @@ -837,7 +837,7 @@ type WebviewDevToolsOpenOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewDevToolsOpen(_ context.Context, _ *mcp.CallToolRequest, input WebviewDevToolsOpenInput) (*mcp.CallToolResult, WebviewDevToolsOpenOutput, error) { +func (s *Subsystem) webviewDevToolsOpen(_ context.Context, _ *mcp.CallToolRequest, input WebviewDevToolsOpenInput) (*mcp.CallToolResult, WebviewDevToolsOpenOutput, resultFailure) { r := s.core.Action("webview.devtoolsOpen").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskDevToolsOpen{Window: input.Window}}, )) @@ -860,7 +860,7 @@ type WebviewDevToolsCloseOutput struct { Success bool `json:"success"` } -func (s *Subsystem) webviewDevToolsClose(_ context.Context, _ *mcp.CallToolRequest, input WebviewDevToolsCloseInput) (*mcp.CallToolResult, WebviewDevToolsCloseOutput, error) { +func (s *Subsystem) webviewDevToolsClose(_ context.Context, _ *mcp.CallToolRequest, input WebviewDevToolsCloseInput) (*mcp.CallToolResult, WebviewDevToolsCloseOutput, resultFailure) { r := s.core.Action("webview.devtoolsClose").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskDevToolsClose{Window: input.Window}}, )) @@ -873,7 +873,7 @@ func (s *Subsystem) webviewDevToolsClose(_ context.Context, _ *mcp.CallToolReque return nil, WebviewDevToolsCloseOutput{Success: true}, nil } -func (s *Subsystem) evaluateWebview(windowName, script string) (any, error) { +func (s *Subsystem) evaluateWebview(windowName, script string) (any, resultFailure) { r := s.core.Action("webview.evaluate").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskEvaluate{Window: windowName, Script: script}}, )) @@ -886,7 +886,7 @@ func (s *Subsystem) evaluateWebview(windowName, script string) (any, error) { return r.Value, nil } -func decodeJSONLike[T any](value any) (T, error) { +func decodeJSONLike[T any](value any) (T, resultFailure) { var out T result := core.JSONUnmarshalString(core.JSONMarshalString(value), &out) if !result.OK { @@ -898,7 +898,7 @@ func decodeJSONLike[T any](value any) (T, error) { return out, nil } -func cropPNGToBoundingBox(pngData []byte, bbox *webview.BoundingBox) ([]byte, error) { +func cropPNGToBoundingBox(pngData []byte, bbox *webview.BoundingBox) ([]byte, resultFailure) { img, err := png.Decode(core.NewBuffer(pngData)) if err != nil { return nil, err diff --git a/go/pkg/mcp/tools_window.go b/go/pkg/mcp/tools_window.go index 63df1079..83fd2e07 100644 --- a/go/pkg/mcp/tools_window.go +++ b/go/pkg/mcp/tools_window.go @@ -16,7 +16,7 @@ type WindowListOutput struct { Windows []window.WindowInfo `json:"windows"` } -func (s *Subsystem) windowList(_ context.Context, _ *mcp.CallToolRequest, _ WindowListInput) (*mcp.CallToolResult, WindowListOutput, error) { +func (s *Subsystem) windowList(_ context.Context, _ *mcp.CallToolRequest, _ WindowListInput) (*mcp.CallToolResult, WindowListOutput, resultFailure) { r := s.core.QUERY(window.QueryWindowList{}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -40,7 +40,7 @@ type WindowGetOutput struct { Window *window.WindowInfo `json:"window"` } -func (s *Subsystem) windowGet(_ context.Context, _ *mcp.CallToolRequest, input WindowGetInput) (*mcp.CallToolResult, WindowGetOutput, error) { +func (s *Subsystem) windowGet(_ context.Context, _ *mcp.CallToolRequest, input WindowGetInput) (*mcp.CallToolResult, WindowGetOutput, resultFailure) { r := s.core.QUERY(window.QueryWindowByName{Name: input.Name}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -62,7 +62,7 @@ type WindowFocusedOutput struct { Window string `json:"window"` } -func (s *Subsystem) windowFocused(_ context.Context, _ *mcp.CallToolRequest, _ WindowFocusedInput) (*mcp.CallToolResult, WindowFocusedOutput, error) { +func (s *Subsystem) windowFocused(_ context.Context, _ *mcp.CallToolRequest, _ WindowFocusedInput) (*mcp.CallToolResult, WindowFocusedOutput, resultFailure) { r := s.core.QUERY(window.QueryWindowList{}) if !r.OK { if e, ok := r.Value.(error); ok { @@ -97,7 +97,7 @@ type WindowCreateOutput struct { Window window.WindowInfo `json:"window"` } -func (s *Subsystem) windowCreate(_ context.Context, _ *mcp.CallToolRequest, input WindowCreateInput) (*mcp.CallToolResult, WindowCreateOutput, error) { +func (s *Subsystem) windowCreate(_ context.Context, _ *mcp.CallToolRequest, input WindowCreateInput) (*mcp.CallToolResult, WindowCreateOutput, resultFailure) { r := s.core.Action("window.open").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskOpenWindow{ Window: &window.Window{ @@ -133,7 +133,7 @@ type WindowCloseOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowClose(_ context.Context, _ *mcp.CallToolRequest, input WindowCloseInput) (*mcp.CallToolResult, WindowCloseOutput, error) { +func (s *Subsystem) windowClose(_ context.Context, _ *mcp.CallToolRequest, input WindowCloseInput) (*mcp.CallToolResult, WindowCloseOutput, resultFailure) { r := s.core.Action("window.close").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskCloseWindow{Name: input.Name}}, )) @@ -157,7 +157,7 @@ type WindowPositionOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowPosition(_ context.Context, _ *mcp.CallToolRequest, input WindowPositionInput) (*mcp.CallToolResult, WindowPositionOutput, error) { +func (s *Subsystem) windowPosition(_ context.Context, _ *mcp.CallToolRequest, input WindowPositionInput) (*mcp.CallToolResult, WindowPositionOutput, resultFailure) { r := s.core.Action("window.setPosition").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetPosition{Name: input.Name, X: input.X, Y: input.Y}}, )) @@ -181,7 +181,7 @@ type WindowSizeOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowSize(_ context.Context, _ *mcp.CallToolRequest, input WindowSizeInput) (*mcp.CallToolResult, WindowSizeOutput, error) { +func (s *Subsystem) windowSize(_ context.Context, _ *mcp.CallToolRequest, input WindowSizeInput) (*mcp.CallToolResult, WindowSizeOutput, resultFailure) { r := s.core.Action("window.setSize").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetSize{Name: input.Name, Width: input.Width, Height: input.Height}}, )) @@ -207,7 +207,7 @@ type WindowBoundsOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowBounds(_ context.Context, _ *mcp.CallToolRequest, input WindowBoundsInput) (*mcp.CallToolResult, WindowBoundsOutput, error) { +func (s *Subsystem) windowBounds(_ context.Context, _ *mcp.CallToolRequest, input WindowBoundsInput) (*mcp.CallToolResult, WindowBoundsOutput, resultFailure) { r := s.core.Action("window.setBounds").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetBounds{ Name: input.Name, X: input.X, Y: input.Y, Width: input.Width, Height: input.Height, @@ -231,7 +231,7 @@ type WindowMaximizeOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowMaximize(_ context.Context, _ *mcp.CallToolRequest, input WindowMaximizeInput) (*mcp.CallToolResult, WindowMaximizeOutput, error) { +func (s *Subsystem) windowMaximize(_ context.Context, _ *mcp.CallToolRequest, input WindowMaximizeInput) (*mcp.CallToolResult, WindowMaximizeOutput, resultFailure) { r := s.core.Action("window.maximise").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskMaximise{Name: input.Name}}, )) @@ -253,7 +253,7 @@ type WindowMinimizeOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowMinimize(_ context.Context, _ *mcp.CallToolRequest, input WindowMinimizeInput) (*mcp.CallToolResult, WindowMinimizeOutput, error) { +func (s *Subsystem) windowMinimize(_ context.Context, _ *mcp.CallToolRequest, input WindowMinimizeInput) (*mcp.CallToolResult, WindowMinimizeOutput, resultFailure) { r := s.core.Action("window.minimise").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskMinimise{Name: input.Name}}, )) @@ -275,7 +275,7 @@ type WindowRestoreOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowRestore(_ context.Context, _ *mcp.CallToolRequest, input WindowRestoreInput) (*mcp.CallToolResult, WindowRestoreOutput, error) { +func (s *Subsystem) windowRestore(_ context.Context, _ *mcp.CallToolRequest, input WindowRestoreInput) (*mcp.CallToolResult, WindowRestoreOutput, resultFailure) { r := s.core.Action("window.restore").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskRestore{Name: input.Name}}, )) @@ -297,7 +297,7 @@ type WindowFocusOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowFocus(_ context.Context, _ *mcp.CallToolRequest, input WindowFocusInput) (*mcp.CallToolResult, WindowFocusOutput, error) { +func (s *Subsystem) windowFocus(_ context.Context, _ *mcp.CallToolRequest, input WindowFocusInput) (*mcp.CallToolResult, WindowFocusOutput, resultFailure) { r := s.core.Action("window.focus").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskFocus{Name: input.Name}}, )) @@ -319,7 +319,7 @@ type FocusSetOutput struct { Success bool `json:"success"` } -func (s *Subsystem) focusSet(ctx context.Context, req *mcp.CallToolRequest, input FocusSetInput) (*mcp.CallToolResult, FocusSetOutput, error) { +func (s *Subsystem) focusSet(ctx context.Context, req *mcp.CallToolRequest, input FocusSetInput) (*mcp.CallToolResult, FocusSetOutput, resultFailure) { result, output, err := s.windowFocus(ctx, req, WindowFocusInput{Name: input.Name}) if err != nil { return nil, FocusSetOutput{}, err @@ -340,7 +340,7 @@ type WindowTitleOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowTitle(_ context.Context, _ *mcp.CallToolRequest, input WindowTitleInput) (*mcp.CallToolResult, WindowTitleOutput, error) { +func (s *Subsystem) windowTitle(_ context.Context, _ *mcp.CallToolRequest, input WindowTitleInput) (*mcp.CallToolResult, WindowTitleOutput, resultFailure) { r := s.core.Action("window.setTitle").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetTitle{Name: input.Name, Title: input.Title}}, )) @@ -363,7 +363,7 @@ type WindowTitleSetOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowTitleSet(ctx context.Context, req *mcp.CallToolRequest, input WindowTitleSetInput) (*mcp.CallToolResult, WindowTitleSetOutput, error) { +func (s *Subsystem) windowTitleSet(ctx context.Context, req *mcp.CallToolRequest, input WindowTitleSetInput) (*mcp.CallToolResult, WindowTitleSetOutput, resultFailure) { result, output, err := s.windowTitle(ctx, req, WindowTitleInput{Name: input.Name, Title: input.Title}) if err != nil { return nil, WindowTitleSetOutput{}, err @@ -383,7 +383,7 @@ type WindowTitleGetOutput struct { Title string `json:"title"` } -func (s *Subsystem) windowTitleGet(_ context.Context, _ *mcp.CallToolRequest, input WindowTitleGetInput) (*mcp.CallToolResult, WindowTitleGetOutput, error) { +func (s *Subsystem) windowTitleGet(_ context.Context, _ *mcp.CallToolRequest, input WindowTitleGetInput) (*mcp.CallToolResult, WindowTitleGetOutput, resultFailure) { r := s.core.QUERY(window.QueryWindowByName{Name: input.Name}) if !r.OK { return nil, WindowTitleGetOutput{}, nil @@ -405,7 +405,7 @@ type WindowVisibilityOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowVisibility(_ context.Context, _ *mcp.CallToolRequest, input WindowVisibilityInput) (*mcp.CallToolResult, WindowVisibilityOutput, error) { +func (s *Subsystem) windowVisibility(_ context.Context, _ *mcp.CallToolRequest, input WindowVisibilityInput) (*mcp.CallToolResult, WindowVisibilityOutput, resultFailure) { r := s.core.Action("window.setVisibility").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetVisibility{Name: input.Name, Visible: input.Visible}}, )) @@ -428,7 +428,7 @@ type WindowAlwaysOnTopOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowAlwaysOnTop(_ context.Context, _ *mcp.CallToolRequest, input WindowAlwaysOnTopInput) (*mcp.CallToolResult, WindowAlwaysOnTopOutput, error) { +func (s *Subsystem) windowAlwaysOnTop(_ context.Context, _ *mcp.CallToolRequest, input WindowAlwaysOnTopInput) (*mcp.CallToolResult, WindowAlwaysOnTopOutput, resultFailure) { r := s.core.Action("window.setAlwaysOnTop").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetAlwaysOnTop{Name: input.Name, AlwaysOnTop: input.AlwaysOnTop}}, )) @@ -451,7 +451,7 @@ type WindowOpacityOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowOpacity(_ context.Context, _ *mcp.CallToolRequest, input WindowOpacityInput) (*mcp.CallToolResult, WindowOpacityOutput, error) { +func (s *Subsystem) windowOpacity(_ context.Context, _ *mcp.CallToolRequest, input WindowOpacityInput) (*mcp.CallToolResult, WindowOpacityOutput, resultFailure) { r := s.core.Action("window.setOpacity").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetOpacity{Name: input.Name, Opacity: input.Opacity}}, )) @@ -477,7 +477,7 @@ type WindowBackgroundColourOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowBackgroundColour(_ context.Context, _ *mcp.CallToolRequest, input WindowBackgroundColourInput) (*mcp.CallToolResult, WindowBackgroundColourOutput, error) { +func (s *Subsystem) windowBackgroundColour(_ context.Context, _ *mcp.CallToolRequest, input WindowBackgroundColourInput) (*mcp.CallToolResult, WindowBackgroundColourOutput, resultFailure) { r := s.core.Action("window.setBackgroundColour").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetBackgroundColour{ Name: input.Name, Red: input.Red, Green: input.Green, Blue: input.Blue, Alpha: input.Alpha, @@ -502,7 +502,7 @@ type WindowFullscreenOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowFullscreen(_ context.Context, _ *mcp.CallToolRequest, input WindowFullscreenInput) (*mcp.CallToolResult, WindowFullscreenOutput, error) { +func (s *Subsystem) windowFullscreen(_ context.Context, _ *mcp.CallToolRequest, input WindowFullscreenInput) (*mcp.CallToolResult, WindowFullscreenOutput, resultFailure) { r := s.core.Action("window.fullscreen").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskFullscreen{Name: input.Name, Fullscreen: input.Fullscreen}}, )) @@ -525,7 +525,7 @@ type WindowZoomSetOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowZoomSet(_ context.Context, _ *mcp.CallToolRequest, input WindowZoomSetInput) (*mcp.CallToolResult, WindowZoomSetOutput, error) { +func (s *Subsystem) windowZoomSet(_ context.Context, _ *mcp.CallToolRequest, input WindowZoomSetInput) (*mcp.CallToolResult, WindowZoomSetOutput, resultFailure) { r := s.core.Action("window.setZoom").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetZoom{Name: input.Name, Magnification: input.Magnification}}, )) @@ -547,7 +547,7 @@ type WindowZoomInOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowZoomIn(_ context.Context, _ *mcp.CallToolRequest, input WindowZoomInInput) (*mcp.CallToolResult, WindowZoomInOutput, error) { +func (s *Subsystem) windowZoomIn(_ context.Context, _ *mcp.CallToolRequest, input WindowZoomInInput) (*mcp.CallToolResult, WindowZoomInOutput, resultFailure) { r := s.core.Action("window.zoomIn").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskZoomIn{Name: input.Name}}, )) @@ -569,7 +569,7 @@ type WindowZoomOutOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowZoomOut(_ context.Context, _ *mcp.CallToolRequest, input WindowZoomOutInput) (*mcp.CallToolResult, WindowZoomOutOutput, error) { +func (s *Subsystem) windowZoomOut(_ context.Context, _ *mcp.CallToolRequest, input WindowZoomOutInput) (*mcp.CallToolResult, WindowZoomOutOutput, resultFailure) { r := s.core.Action("window.zoomOut").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskZoomOut{Name: input.Name}}, )) @@ -591,7 +591,7 @@ type WindowZoomResetOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowZoomReset(_ context.Context, _ *mcp.CallToolRequest, input WindowZoomResetInput) (*mcp.CallToolResult, WindowZoomResetOutput, error) { +func (s *Subsystem) windowZoomReset(_ context.Context, _ *mcp.CallToolRequest, input WindowZoomResetInput) (*mcp.CallToolResult, WindowZoomResetOutput, resultFailure) { r := s.core.Action("window.zoomReset").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskZoomReset{Name: input.Name}}, )) @@ -614,7 +614,7 @@ type WindowURLSetOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowURLSet(_ context.Context, _ *mcp.CallToolRequest, input WindowURLSetInput) (*mcp.CallToolResult, WindowURLSetOutput, error) { +func (s *Subsystem) windowURLSet(_ context.Context, _ *mcp.CallToolRequest, input WindowURLSetInput) (*mcp.CallToolResult, WindowURLSetOutput, resultFailure) { r := s.core.Action("window.setURL").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetURL{Name: input.Name, URL: input.URL}}, )) @@ -637,7 +637,7 @@ type WindowHTMLSetOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowHTMLSet(_ context.Context, _ *mcp.CallToolRequest, input WindowHTMLSetInput) (*mcp.CallToolResult, WindowHTMLSetOutput, error) { +func (s *Subsystem) windowHTMLSet(_ context.Context, _ *mcp.CallToolRequest, input WindowHTMLSetInput) (*mcp.CallToolResult, WindowHTMLSetOutput, resultFailure) { r := s.core.Action("window.setHTML").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetHTML{Name: input.Name, HTML: input.HTML}}, )) @@ -660,7 +660,7 @@ type WindowExecJSOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowExecJS(_ context.Context, _ *mcp.CallToolRequest, input WindowExecJSInput) (*mcp.CallToolResult, WindowExecJSOutput, error) { +func (s *Subsystem) windowExecJS(_ context.Context, _ *mcp.CallToolRequest, input WindowExecJSInput) (*mcp.CallToolResult, WindowExecJSOutput, resultFailure) { r := s.core.Action("window.execJS").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskExecJS{Name: input.Name, JS: input.JS}}, )) @@ -682,7 +682,7 @@ type WindowToggleFullscreenOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowToggleFullscreen(_ context.Context, _ *mcp.CallToolRequest, input WindowToggleFullscreenInput) (*mcp.CallToolResult, WindowToggleFullscreenOutput, error) { +func (s *Subsystem) windowToggleFullscreen(_ context.Context, _ *mcp.CallToolRequest, input WindowToggleFullscreenInput) (*mcp.CallToolResult, WindowToggleFullscreenOutput, resultFailure) { r := s.core.Action("window.toggleFullscreen").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskToggleFullscreen{Name: input.Name}}, )) @@ -704,7 +704,7 @@ type WindowToggleMaximiseOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowToggleMaximise(_ context.Context, _ *mcp.CallToolRequest, input WindowToggleMaximiseInput) (*mcp.CallToolResult, WindowToggleMaximiseOutput, error) { +func (s *Subsystem) windowToggleMaximise(_ context.Context, _ *mcp.CallToolRequest, input WindowToggleMaximiseInput) (*mcp.CallToolResult, WindowToggleMaximiseOutput, resultFailure) { r := s.core.Action("window.toggleMaximise").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskToggleMaximise{Name: input.Name}}, )) @@ -727,7 +727,7 @@ type WindowSetContentProtectionOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowSetContentProtection(_ context.Context, _ *mcp.CallToolRequest, input WindowSetContentProtectionInput) (*mcp.CallToolResult, WindowSetContentProtectionOutput, error) { +func (s *Subsystem) windowSetContentProtection(_ context.Context, _ *mcp.CallToolRequest, input WindowSetContentProtectionInput) (*mcp.CallToolResult, WindowSetContentProtectionOutput, resultFailure) { r := s.core.Action("window.setContentProtection").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskSetContentProtection{Name: input.Name, Protection: input.Protection}}, )) @@ -750,7 +750,7 @@ type WindowFlashOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowFlash(_ context.Context, _ *mcp.CallToolRequest, input WindowFlashInput) (*mcp.CallToolResult, WindowFlashOutput, error) { +func (s *Subsystem) windowFlash(_ context.Context, _ *mcp.CallToolRequest, input WindowFlashInput) (*mcp.CallToolResult, WindowFlashOutput, resultFailure) { r := s.core.Action("window.flash").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskFlash{Name: input.Name, Enabled: input.Enabled}}, )) @@ -772,7 +772,7 @@ type WindowPrintOutput struct { Success bool `json:"success"` } -func (s *Subsystem) windowPrint(_ context.Context, _ *mcp.CallToolRequest, input WindowPrintInput) (*mcp.CallToolResult, WindowPrintOutput, error) { +func (s *Subsystem) windowPrint(_ context.Context, _ *mcp.CallToolRequest, input WindowPrintInput) (*mcp.CallToolResult, WindowPrintOutput, resultFailure) { r := s.core.Action("window.print").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: window.TaskPrint{Name: input.Name}}, )) diff --git a/go/pkg/notification/platform.go b/go/pkg/notification/platform.go index 04d33007..01bed931 100644 --- a/go/pkg/notification/platform.go +++ b/go/pkg/notification/platform.go @@ -3,16 +3,16 @@ package notification // Platform abstracts the native notification backend. type Platform interface { - Send(options NotificationOptions) error - RequestPermission() (bool, error) - CheckPermission() (bool, error) - RevokePermission() error + Send(options NotificationOptions) resultFailure + RequestPermission() (bool, resultFailure) + CheckPermission() (bool, resultFailure) + RevokePermission() resultFailure } // ClearPlatform is an optional extension for backends that can dismiss // notifications after they have been shown. An empty id means "clear all". type ClearPlatform interface { - Clear(id string) error + Clear(id string) resultFailure } // NotificationSeverity indicates the severity for dialog fallback. diff --git a/go/pkg/notification/result_failure.go b/go/pkg/notification/result_failure.go new file mode 100644 index 00000000..dc0d3986 --- /dev/null +++ b/go/pkg/notification/result_failure.go @@ -0,0 +1,5 @@ +package notification + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/notification/service.go b/go/pkg/notification/service.go index 803f5361..ea5fa254 100644 --- a/go/pkg/notification/service.go +++ b/go/pkg/notification/service.go @@ -97,7 +97,7 @@ func (s *Service) handleQuery(_ *core.Core, q core.Query) core.Result { } // send attempts native notification, falls back to dialog via IPC. -func (s *Service) send(options NotificationOptions) error { +func (s *Service) send(options NotificationOptions) resultFailure { // Generate ID if not provided if options.ID == "" { options.ID = "core-" + strconv.FormatInt(time.Now().UnixNano(), 10) @@ -133,7 +133,7 @@ func (s *Service) applyCategoryActions(options NotificationOptions) Notification } // fallbackDialog shows a dialog via IPC when native notifications fail. -func (s *Service) fallbackDialog(options NotificationOptions) error { +func (s *Service) fallbackDialog(options NotificationOptions) resultFailure { // Map severity to dialog type var dt dialog.DialogType switch options.Severity { @@ -168,7 +168,7 @@ func (s *Service) fallbackDialog(options NotificationOptions) error { return nil } -func (s *Service) clear(id string) error { +func (s *Service) clear(id string) resultFailure { if clearer, ok := s.platform.(ClearPlatform); ok { if err := clearer.Clear(id); err != nil { return err @@ -202,7 +202,7 @@ func (s *Service) removeActive(id string) []string { return ids } -func notificationOptionsFrom(opts core.Options) (NotificationOptions, error) { +func notificationOptionsFrom(opts core.Options) (NotificationOptions, resultFailure) { if task := opts.Get("task"); task.OK { switch v := task.Value.(type) { case TaskSend: @@ -214,7 +214,7 @@ func notificationOptionsFrom(opts core.Options) (NotificationOptions, error) { return decodeOptions[NotificationOptions](opts) } -func decodeOptions[T any](opts core.Options) (T, error) { +func decodeOptions[T any](opts core.Options) (T, resultFailure) { var input T items := make(map[string]any, opts.Len()) for _, item := range opts.Items() { diff --git a/go/pkg/notification/service_test.go b/go/pkg/notification/service_test.go index 181fcfb5..0a0729de 100644 --- a/go/pkg/notification/service_test.go +++ b/go/pkg/notification/service_test.go @@ -9,30 +9,30 @@ import ( ) type mockPlatform struct { - sendErr error + sendErr resultFailure permGranted bool - permErr error - revokeErr error + permErr resultFailure + revokeErr resultFailure revokeCalled bool - clearErr error + clearErr resultFailure clearCalled bool clearID string lastOpts NotificationOptions sendCalled bool } -func (m *mockPlatform) Send(opts NotificationOptions) error { +func (m *mockPlatform) Send(opts NotificationOptions) resultFailure { m.sendCalled = true m.lastOpts = opts return m.sendErr } -func (m *mockPlatform) RequestPermission() (bool, error) { return m.permGranted, m.permErr } -func (m *mockPlatform) CheckPermission() (bool, error) { return m.permGranted, m.permErr } -func (m *mockPlatform) RevokePermission() error { +func (m *mockPlatform) RequestPermission() (bool, resultFailure) { return m.permGranted, m.permErr } +func (m *mockPlatform) CheckPermission() (bool, resultFailure) { return m.permGranted, m.permErr } +func (m *mockPlatform) RevokePermission() resultFailure { m.revokeCalled = true return m.revokeErr } -func (m *mockPlatform) Clear(id string) error { +func (m *mockPlatform) Clear(id string) resultFailure { m.clearCalled = true m.clearID = id return m.clearErr @@ -44,12 +44,16 @@ type mockDialogPlatform struct { lastMsgOpts dialog.MessageDialogOptions } -func (m *mockDialogPlatform) OpenFile(opts dialog.OpenFileOptions) ([]string, error) { return nil, nil } -func (m *mockDialogPlatform) SaveFile(opts dialog.SaveFileOptions) (string, error) { return "", nil } -func (m *mockDialogPlatform) OpenDirectory(opts dialog.OpenDirectoryOptions) (string, error) { +func (m *mockDialogPlatform) OpenFile(opts dialog.OpenFileOptions) ([]string, resultFailure) { + return nil, nil +} +func (m *mockDialogPlatform) SaveFile(opts dialog.SaveFileOptions) (string, resultFailure) { + return "", nil +} +func (m *mockDialogPlatform) OpenDirectory(opts dialog.OpenDirectoryOptions) (string, resultFailure) { return "", nil } -func (m *mockDialogPlatform) MessageDialog(opts dialog.MessageDialogOptions) (string, error) { +func (m *mockDialogPlatform) MessageDialog(opts dialog.MessageDialogOptions) (string, resultFailure) { m.messageCalled = true m.lastMsgOpts = opts return "OK", nil @@ -307,8 +311,8 @@ func TestQueryPermission_Bad(t *core.T) { } func TestQueryPermission_Ugly(t *core.T) { - // Platform returns error — QUERY returns OK=false (framework does not propagate Value for failed queries) - mock := &mockPlatform{permErr: core.NewError("platform error")} + // Platform returns resultFailure — QUERY returns OK=false (framework does not propagate Value for failed queries) + mock := &mockPlatform{permErr: core.NewError("platform resultFailure")} c := core.New( core.WithService(Register(mock)), core.WithServiceLock(), diff --git a/go/pkg/p2p/core_helpers.go b/go/pkg/p2p/core_helpers.go index e0fb9a80..412f7b95 100644 --- a/go/pkg/p2p/core_helpers.go +++ b/go/pkg/p2p/core_helpers.go @@ -2,7 +2,7 @@ package p2p import core "dappco.re/go" -func coreResultError(result core.Result, fallback string) error { +func coreResultError(result core.Result, fallback string) resultFailure { if result.OK { return nil } @@ -15,7 +15,7 @@ func coreResultError(result core.Result, fallback string) error { return core.NewError(fallback) } -func jsonMarshal(value any) ([]byte, error) { +func jsonMarshal(value any) ([]byte, resultFailure) { result := core.JSONMarshal(value) if !result.OK { return nil, coreResultError(result, "failed to encode JSON") @@ -23,6 +23,6 @@ func jsonMarshal(value any) ([]byte, error) { return result.Value.([]byte), nil } -func jsonUnmarshal(data []byte, target any) error { +func jsonUnmarshal(data []byte, target any) resultFailure { return coreResultError(core.JSONUnmarshal(data, target), "failed to decode JSON") } diff --git a/go/pkg/p2p/result_failure.go b/go/pkg/p2p/result_failure.go new file mode 100644 index 00000000..4cc81010 --- /dev/null +++ b/go/pkg/p2p/result_failure.go @@ -0,0 +1,5 @@ +package p2p + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/p2p/router.go b/go/pkg/p2p/router.go index a5bd832b..9256f360 100644 --- a/go/pkg/p2p/router.go +++ b/go/pkg/p2p/router.go @@ -22,8 +22,8 @@ type Peer struct { } type Driver interface { - Publish(context.Context, Envelope) error - Subscribe(context.Context, string, func(Envelope)) error + Publish(context.Context, Envelope) resultFailure + Subscribe(context.Context, string, func(Envelope)) resultFailure } type Router struct { @@ -39,7 +39,7 @@ func New(driver Driver) *Router { } } -func (r *Router) Subscribe(ctx context.Context, topic string, handler func(Envelope)) error { +func (r *Router) Subscribe(ctx context.Context, topic string, handler func(Envelope)) resultFailure { if r.driver == nil { return nil } @@ -56,7 +56,7 @@ func (r *Router) Subscribe(ctx context.Context, topic string, handler func(Envel }) } -func (r *Router) Publish(ctx context.Context, envelope Envelope) error { +func (r *Router) Publish(ctx context.Context, envelope Envelope) resultFailure { if r.driver == nil { return nil } diff --git a/go/pkg/p2p/router_test.go b/go/pkg/p2p/router_test.go index d24033b8..28beeccc 100644 --- a/go/pkg/p2p/router_test.go +++ b/go/pkg/p2p/router_test.go @@ -8,16 +8,16 @@ import ( type fakeDriver struct { published []Envelope - publishErr error - subscribeErr error + publishErr resultFailure + subscribeErr resultFailure } -func (d *fakeDriver) Publish(_ context.Context, envelope Envelope) error { +func (d *fakeDriver) Publish(_ context.Context, envelope Envelope) resultFailure { d.published = append(d.published, envelope) return d.publishErr } -func (d *fakeDriver) Subscribe(_ context.Context, topic string, handler func(Envelope)) error { +func (d *fakeDriver) Subscribe(_ context.Context, topic string, handler func(Envelope)) resultFailure { if d.subscribeErr != nil { return d.subscribeErr } diff --git a/go/pkg/p2p/service.go b/go/pkg/p2p/service.go index de0600e1..d246b516 100644 --- a/go/pkg/p2p/service.go +++ b/go/pkg/p2p/service.go @@ -83,11 +83,11 @@ func (s *Service) OnShutdown(_ context.Context) core.Result { return core.Result{OK: true} } -func (s *Service) Publish(ctx context.Context, envelope Envelope) error { +func (s *Service) Publish(ctx context.Context, envelope Envelope) resultFailure { return s.router.Publish(ctx, envelope) } -func (s *Service) Subscribe(ctx context.Context, topic string, handler func(Envelope)) error { +func (s *Service) Subscribe(ctx context.Context, topic string, handler func(Envelope)) resultFailure { return s.router.Subscribe(ctx, topic, handler) } diff --git a/go/pkg/p2p/tcp.go b/go/pkg/p2p/tcp.go index 7e8c6abd..cf5ebf25 100644 --- a/go/pkg/p2p/tcp.go +++ b/go/pkg/p2p/tcp.go @@ -42,7 +42,7 @@ func (d *TCPDriver) ListenAddr() string { return d.options.ListenAddr } -func (d *TCPDriver) Subscribe(ctx context.Context, topic string, handler func(Envelope)) error { +func (d *TCPDriver) Subscribe(ctx context.Context, topic string, handler func(Envelope)) resultFailure { if ctx == nil { ctx = context.Background() } @@ -82,7 +82,7 @@ func (d *TCPDriver) Subscribe(ctx context.Context, topic string, handler func(En return nil } -func (d *TCPDriver) Publish(ctx context.Context, envelope Envelope) error { +func (d *TCPDriver) Publish(ctx context.Context, envelope Envelope) resultFailure { if core.Trim(envelope.Topic) == "" { return core.NewError("topic is required") } @@ -119,7 +119,7 @@ func (d *TCPDriver) Publish(ctx context.Context, envelope Envelope) error { return publishErr } -func (d *TCPDriver) Close() error { +func (d *TCPDriver) Close() resultFailure { d.mu.Lock() defer d.mu.Unlock() if d.listener == nil { @@ -132,7 +132,7 @@ func (d *TCPDriver) Close() error { return err } -func (d *TCPDriver) ensureListener() error { +func (d *TCPDriver) ensureListener() resultFailure { d.mu.Lock() defer d.mu.Unlock() if d.listener != nil || core.Trim(d.options.ListenAddr) == "" { diff --git a/go/pkg/p2p/tcp_test.go b/go/pkg/p2p/tcp_test.go index 9f64e722..bb7f2b7c 100644 --- a/go/pkg/p2p/tcp_test.go +++ b/go/pkg/p2p/tcp_test.go @@ -30,7 +30,7 @@ func TestTCPDriver_Publish_ContinuesAfterPeerFailure(t *core.T) { defer listener.Close() received := make(chan Envelope, 1) - acceptErr := make(chan error, 1) + acceptErr := make(chan resultFailure, 1) go func() { conn, err := listener.Accept() if err != nil { diff --git a/go/pkg/preload/core_helpers.go b/go/pkg/preload/core_helpers.go index e1cd1b0d..564302ea 100644 --- a/go/pkg/preload/core_helpers.go +++ b/go/pkg/preload/core_helpers.go @@ -2,7 +2,7 @@ package preload import core "dappco.re/go" -func coreResultError(result core.Result, fallback string) error { +func coreResultError(result core.Result, fallback string) resultFailure { if result.OK { return nil } @@ -15,7 +15,7 @@ func coreResultError(result core.Result, fallback string) error { return core.NewError(fallback) } -func coreOpen(path string) (*core.OSFile, error) { +func coreOpen(path string) (*core.OSFile, resultFailure) { result := core.Open(path) if !result.OK { return nil, coreResultError(result, "failed to open file") @@ -23,7 +23,7 @@ func coreOpen(path string) (*core.OSFile, error) { return result.Value.(*core.OSFile), nil } -func coreStat(path string) (core.FsFileInfo, error) { +func coreStat(path string) (core.FsFileInfo, resultFailure) { result := core.Stat(path) if !result.OK { return nil, coreResultError(result, "failed to stat path") @@ -31,7 +31,7 @@ func coreStat(path string) (core.FsFileInfo, error) { return result.Value.(core.FsFileInfo), nil } -func coreLstat(path string) (core.FsFileInfo, error) { +func coreLstat(path string) (core.FsFileInfo, resultFailure) { result := core.Lstat(path) if !result.OK { return nil, coreResultError(result, "failed to stat path") @@ -39,7 +39,7 @@ func coreLstat(path string) (core.FsFileInfo, error) { return result.Value.(core.FsFileInfo), nil } -func coreReadFile(path string) ([]byte, error) { +func coreReadFile(path string) ([]byte, resultFailure) { result := core.ReadFile(path) if !result.OK { return nil, coreResultError(result, "failed to read file") @@ -47,15 +47,15 @@ func coreReadFile(path string) ([]byte, error) { return result.Value.([]byte), nil } -func coreWriteFile(path string, data []byte, mode core.FileMode) error { +func coreWriteFile(path string, data []byte, mode core.FileMode) resultFailure { return coreResultError(core.WriteFile(path, data, mode), "failed to write file") } -func coreMkdirAll(path string, mode core.FileMode) error { +func coreMkdirAll(path string, mode core.FileMode) resultFailure { return coreResultError(core.MkdirAll(path, mode), "failed to create directory") } -func pathAbs(path string) (string, error) { +func pathAbs(path string) (string, resultFailure) { result := core.PathAbs(path) if !result.OK { return "", coreResultError(result, "failed to make path absolute") @@ -63,7 +63,7 @@ func pathAbs(path string) (string, error) { return result.Value.(string), nil } -func pathEvalSymlinks(path string) (string, error) { +func pathEvalSymlinks(path string) (string, resultFailure) { result := core.PathEvalSymlinks(path) if !result.OK { return "", coreResultError(result, "failed to resolve symlinks") @@ -71,7 +71,7 @@ func pathEvalSymlinks(path string) (string, error) { return result.Value.(string), nil } -func pathRel(base, target string) (string, error) { +func pathRel(base, target string) (string, resultFailure) { result := core.PathRel(base, target) if !result.OK { return "", coreResultError(result, "failed to compare paths") diff --git a/go/pkg/preload/preload.go b/go/pkg/preload/preload.go index b947b261..ee8dfeb3 100644 --- a/go/pkg/preload/preload.go +++ b/go/pkg/preload/preload.go @@ -47,11 +47,11 @@ var ( electronShimAsset = mustReadAsset("assets/electron_shim.js") ) -func InjectPreload(webview Webview, origin string) error { +func InjectPreload(webview Webview, origin string) resultFailure { return InjectPreloadWithTrustedOriginPolicy(webview, origin, DefaultTrustedOriginPolicy()) } -func InjectPreloadWithTrustedOriginPolicy(webview Webview, origin string, policy TrustedOriginPolicy) error { +func InjectPreloadWithTrustedOriginPolicy(webview Webview, origin string, policy TrustedOriginPolicy) resultFailure { if isNilWebview(webview) { return core.NewError("preload target is required") } @@ -68,11 +68,11 @@ func InjectPreloadWithTrustedOriginPolicy(webview Webview, origin string, policy return nil } -func buildScript(pageURL string) (string, error) { +func buildScript(pageURL string) (string, resultFailure) { return buildScriptWithTrustedOriginPolicy(pageURL, DefaultTrustedOriginPolicy()) } -func buildScriptWithTrustedOriginPolicy(pageURL string, policy TrustedOriginPolicy) (string, error) { +func buildScriptWithTrustedOriginPolicy(pageURL string, policy TrustedOriginPolicy) (string, resultFailure) { var loaded *loadedManifest manifestAllowed := manifestBackedPreloadOriginAllowedByPolicy(pageURL, policy) if manifestAllowed { @@ -141,7 +141,7 @@ func mustReadAsset(name string) string { return string(body) } -func renderAppPreloads(loaded *loadedManifest) (string, error) { +func renderAppPreloads(loaded *loadedManifest) (string, resultFailure) { if loaded == nil || len(loaded.Preloads) == 0 { return "", nil } @@ -167,7 +167,7 @@ func renderAppPreloads(loaded *loadedManifest) (string, error) { return core.Join("\n", scripts...), nil } -func loadManifestForOrigin(pageURL string) (*loadedManifest, error) { +func loadManifestForOrigin(pageURL string) (*loadedManifest, resultFailure) { path, err := discoverManifestPath(pageURL) if err != nil { return nil, err @@ -206,7 +206,7 @@ func collectManifestPreloads(manifest viewManifest) []ManifestPreload { return out } -func discoverManifestPath(pageURL string) (string, error) { +func discoverManifestPath(pageURL string) (string, resultFailure) { trimmed := core.Trim(pageURL) if trimmed == "" { return "", errViewManifestNotFound @@ -263,7 +263,7 @@ func manifestBaseDir(manifestPath string) string { return baseDir } -func readManifestPreload(baseDir, preloadPath string) ([]byte, error) { +func readManifestPreload(baseDir, preloadPath string) ([]byte, resultFailure) { resolvedPath, err := safeManifestRelativePath(baseDir, preloadPath) if err != nil { return nil, err @@ -271,7 +271,7 @@ func readManifestPreload(baseDir, preloadPath string) ([]byte, error) { return coreReadFile(resolvedPath) } -func safeManifestRelativePath(baseDir, relativePath string) (string, error) { +func safeManifestRelativePath(baseDir, relativePath string) (string, resultFailure) { trimmed := core.Trim(relativePath) if trimmed == "" { return "", core.NewError("preload path is empty") diff --git a/go/pkg/preload/result_failure.go b/go/pkg/preload/result_failure.go new file mode 100644 index 00000000..c6478139 --- /dev/null +++ b/go/pkg/preload/result_failure.go @@ -0,0 +1,5 @@ +package preload + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/systray/menu.go b/go/pkg/systray/menu.go index c1433578..e72991cf 100644 --- a/go/pkg/systray/menu.go +++ b/go/pkg/systray/menu.go @@ -5,7 +5,7 @@ import core "dappco.re/go" // SetMenu sets a dynamic menu on the tray from TrayMenuItem descriptors. // Use: _ = m.SetMenu([]TrayMenuItem{{Label: "Quit", ActionID: "quit"}}) -func (m *Manager) SetMenu(items []TrayMenuItem) error { +func (m *Manager) SetMenu(items []TrayMenuItem) resultFailure { if m.tray == nil { return core.E("systray.SetMenu", "tray not initialised", nil) } diff --git a/go/pkg/systray/menu_test.go b/go/pkg/systray/menu_test.go index db7796fb..ba8fec68 100644 --- a/go/pkg/systray/menu_test.go +++ b/go/pkg/systray/menu_test.go @@ -26,13 +26,13 @@ type recordingTray struct { attachedWindow WindowHandle } -func (t *recordingTray) SetIcon(data []byte) { t.icon = append([]byte(nil), data...) } -func (t *recordingTray) SetTemplateIcon(data []byte) { t.templateIcon = append([]byte(nil), data...) } -func (t *recordingTray) SetTooltip(text string) { t.tooltip = text } -func (t *recordingTray) SetLabel(text string) { t.label = text } -func (t *recordingTray) SetMenu(menu PlatformMenu) { t.menu = menu } -func (t *recordingTray) AttachWindow(w WindowHandle) { t.attachedWindow = w } -func (t *recordingTray) ShowMessage(title, message string) error { return nil } +func (t *recordingTray) SetIcon(data []byte) { t.icon = append([]byte(nil), data...) } +func (t *recordingTray) SetTemplateIcon(data []byte) { t.templateIcon = append([]byte(nil), data...) } +func (t *recordingTray) SetTooltip(text string) { t.tooltip = text } +func (t *recordingTray) SetLabel(text string) { t.label = text } +func (t *recordingTray) SetMenu(menu PlatformMenu) { t.menu = menu } +func (t *recordingTray) AttachWindow(w WindowHandle) { t.attachedWindow = w } +func (t *recordingTray) ShowMessage(title, message string) resultFailure { return nil } type recordingTrayMenu struct { items []*recordingTrayMenuItem diff --git a/go/pkg/systray/mock_platform.go b/go/pkg/systray/mock_platform.go index 7cc955fd..474fba82 100644 --- a/go/pkg/systray/mock_platform.go +++ b/go/pkg/systray/mock_platform.go @@ -22,13 +22,13 @@ type exportedMockTray struct { tooltip, label string } -func (t *exportedMockTray) SetIcon(data []byte) { t.icon = data } -func (t *exportedMockTray) SetTemplateIcon(data []byte) { t.templateIcon = data } -func (t *exportedMockTray) SetTooltip(text string) { t.tooltip = text } -func (t *exportedMockTray) SetLabel(text string) { t.label = text } -func (t *exportedMockTray) SetMenu(menu PlatformMenu) {} -func (t *exportedMockTray) AttachWindow(w WindowHandle) {} -func (t *exportedMockTray) ShowMessage(title, message string) error { return nil } +func (t *exportedMockTray) SetIcon(data []byte) { t.icon = data } +func (t *exportedMockTray) SetTemplateIcon(data []byte) { t.templateIcon = data } +func (t *exportedMockTray) SetTooltip(text string) { t.tooltip = text } +func (t *exportedMockTray) SetLabel(text string) { t.label = text } +func (t *exportedMockTray) SetMenu(menu PlatformMenu) {} +func (t *exportedMockTray) AttachWindow(w WindowHandle) {} +func (t *exportedMockTray) ShowMessage(title, message string) resultFailure { return nil } type exportedMockMenu struct { items []exportedMockMenuItem diff --git a/go/pkg/systray/mock_test.go b/go/pkg/systray/mock_test.go index 9ab2f1c4..09bf1692 100644 --- a/go/pkg/systray/mock_test.go +++ b/go/pkg/systray/mock_test.go @@ -59,7 +59,7 @@ func (t *mockTray) SetTooltip(text string) { t.tooltip = text } func (t *mockTray) SetLabel(text string) { t.label = text } func (t *mockTray) SetMenu(menu PlatformMenu) { t.menu = menu } func (t *mockTray) AttachWindow(w WindowHandle) { t.attachedWindow = w } -func (t *mockTray) ShowMessage(title, message string) error { +func (t *mockTray) ShowMessage(title, message string) resultFailure { t.lastMessageTitle = title t.lastMessageBody = message return nil diff --git a/go/pkg/systray/platform.go b/go/pkg/systray/platform.go index 055ca0c8..e59968ea 100644 --- a/go/pkg/systray/platform.go +++ b/go/pkg/systray/platform.go @@ -17,7 +17,7 @@ type PlatformTray interface { SetLabel(text string) SetMenu(menu PlatformMenu) AttachWindow(w WindowHandle) - ShowMessage(title, message string) error + ShowMessage(title, message string) resultFailure } // PlatformMenu is a tray menu built by the backend. diff --git a/go/pkg/systray/result_failure.go b/go/pkg/systray/result_failure.go new file mode 100644 index 00000000..07781c48 --- /dev/null +++ b/go/pkg/systray/result_failure.go @@ -0,0 +1,5 @@ +package systray + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/systray/service.go b/go/pkg/systray/service.go index c39673ac..b28978e8 100644 --- a/go/pkg/systray/service.go +++ b/go/pkg/systray/service.go @@ -121,7 +121,7 @@ func (s *Service) handleQuery(_ *core.Core, q core.Query) core.Result { } } -func (s *Service) taskSetTrayMenu(t TaskSetTrayMenu) error { +func (s *Service) taskSetTrayMenu(t TaskSetTrayMenu) resultFailure { // Register IPC-emitting callbacks for each menu item for _, item := range t.Items { if item.ActionID != "" { diff --git a/go/pkg/systray/service_test.go b/go/pkg/systray/service_test.go index 8a5431ce..c52986bc 100644 --- a/go/pkg/systray/service_test.go +++ b/go/pkg/systray/service_test.go @@ -90,15 +90,15 @@ type fallbackNotificationPlatform struct { opts notification.NotificationOptions } -func (m *fallbackNotificationPlatform) Send(opts notification.NotificationOptions) error { +func (m *fallbackNotificationPlatform) Send(opts notification.NotificationOptions) resultFailure { m.sent = true m.opts = opts return nil } -func (m *fallbackNotificationPlatform) RequestPermission() (bool, error) { return true, nil } -func (m *fallbackNotificationPlatform) CheckPermission() (bool, error) { return true, nil } -func (m *fallbackNotificationPlatform) RevokePermission() error { return nil } -func (m *fallbackNotificationPlatform) Clear(id string) error { return nil } +func (m *fallbackNotificationPlatform) RequestPermission() (bool, resultFailure) { return true, nil } +func (m *fallbackNotificationPlatform) CheckPermission() (bool, resultFailure) { return true, nil } +func (m *fallbackNotificationPlatform) RevokePermission() resultFailure { return nil } +func (m *fallbackNotificationPlatform) Clear(id string) resultFailure { return nil } type failingTrayPlatform struct{} @@ -107,7 +107,7 @@ func (failingTrayPlatform) NewMenu() PlatformMenu { return &mockTrayMenu{} } type failingTray struct{ mockTray } -func (t *failingTray) ShowMessage(title, message string) error { +func (t *failingTray) ShowMessage(title, message string) resultFailure { return core.NewError("tray balloon unavailable") } diff --git a/go/pkg/systray/tray.go b/go/pkg/systray/tray.go index 3eac8077..677b9dfc 100644 --- a/go/pkg/systray/tray.go +++ b/go/pkg/systray/tray.go @@ -38,7 +38,7 @@ func NewManager(platform Platform) *Manager { // Setup creates the system tray with default icon and tooltip. // systray.NewManager(systray.NewWailsPlatform(app)).Setup("Core", "Core") -func (m *Manager) Setup(tooltip, label string) error { +func (m *Manager) Setup(tooltip, label string) resultFailure { m.tray = m.platform.NewTray() if m.tray == nil { return core.E("systray.Setup", "platform returned nil tray", nil) @@ -54,7 +54,7 @@ func (m *Manager) Setup(tooltip, label string) error { // SetIcon sets the tray icon. // Use: _ = manager.SetIcon(iconBytes) -func (m *Manager) SetIcon(data []byte) error { +func (m *Manager) SetIcon(data []byte) resultFailure { if m.tray == nil { return core.E("systray.SetIcon", "tray not initialised", nil) } @@ -65,7 +65,7 @@ func (m *Manager) SetIcon(data []byte) error { // SetTemplateIcon sets the template icon (macOS). // Use: _ = manager.SetTemplateIcon(iconBytes) -func (m *Manager) SetTemplateIcon(data []byte) error { +func (m *Manager) SetTemplateIcon(data []byte) resultFailure { if m.tray == nil { return core.E("systray.SetTemplateIcon", "tray not initialised", nil) } @@ -76,7 +76,7 @@ func (m *Manager) SetTemplateIcon(data []byte) error { // SetTooltip sets the tray tooltip. // Use: _ = manager.SetTooltip("Core is ready") -func (m *Manager) SetTooltip(text string) error { +func (m *Manager) SetTooltip(text string) resultFailure { if m.tray == nil { return core.E("systray.SetTooltip", "tray not initialised", nil) } @@ -87,7 +87,7 @@ func (m *Manager) SetTooltip(text string) error { // SetLabel sets the tray label. // Use: _ = manager.SetLabel("Core") -func (m *Manager) SetLabel(text string) error { +func (m *Manager) SetLabel(text string) resultFailure { if m.tray == nil { return core.E("systray.SetLabel", "tray not initialised", nil) } @@ -98,7 +98,7 @@ func (m *Manager) SetLabel(text string) error { // AttachWindow attaches a panel window to the tray. // Use: _ = manager.AttachWindow(windowHandle) -func (m *Manager) AttachWindow(w WindowHandle) error { +func (m *Manager) AttachWindow(w WindowHandle) resultFailure { if m.tray == nil { return core.E("systray.AttachWindow", "tray not initialised", nil) } @@ -110,7 +110,7 @@ func (m *Manager) AttachWindow(w WindowHandle) error { } // ShowMessage displays a tray message if the backend supports it. -func (m *Manager) ShowMessage(title, message string) error { +func (m *Manager) ShowMessage(title, message string) resultFailure { if m.tray == nil { return core.E("systray.ShowMessage", "tray not initialised", nil) } @@ -118,7 +118,7 @@ func (m *Manager) ShowMessage(title, message string) error { } // ShowPanel reveals the attached tray panel window. -func (m *Manager) ShowPanel() error { +func (m *Manager) ShowPanel() resultFailure { m.mu.RLock() panel := m.panelWindow m.mu.RUnlock() @@ -129,7 +129,7 @@ func (m *Manager) ShowPanel() error { } // HidePanel hides the attached tray panel window. -func (m *Manager) HidePanel() error { +func (m *Manager) HidePanel() resultFailure { m.mu.RLock() panel := m.panelWindow m.mu.RUnlock() @@ -151,7 +151,7 @@ func (m *Manager) IsActive() bool { return m.tray != nil } -func invokePanelMethod(panel WindowHandle, method string) error { +func invokePanelMethod(panel WindowHandle, method string) resultFailure { value := reflect.ValueOf(panel) if !value.IsValid() { return core.E("systray.invokePanelMethod", "panel window is invalid", nil) diff --git a/go/pkg/systray/wails.go b/go/pkg/systray/wails.go index 7ae5f92d..a195f76a 100644 --- a/go/pkg/systray/wails.go +++ b/go/pkg/systray/wails.go @@ -53,7 +53,7 @@ func (wt *wailsTray) AttachWindow(w WindowHandle) { // bridge is routed through a concrete Wails window wrapper. } -func (wt *wailsTray) ShowMessage(title, message string) error { +func (wt *wailsTray) ShowMessage(title, message string) resultFailure { _ = title _ = message return core.E("systray.wailsTray.ShowMessage", "tray balloon messages are not supported by this backend", nil) diff --git a/go/pkg/webview/core_helpers_test.go b/go/pkg/webview/core_helpers_test.go index 4cbc4850..7717305a 100644 --- a/go/pkg/webview/core_helpers_test.go +++ b/go/pkg/webview/core_helpers_test.go @@ -2,10 +2,10 @@ package webview import core "dappco.re/go" -func jsonMarshal(value any) ([]byte, error) { +func jsonMarshal(value any) ([]byte, resultFailure) { result := core.JSONMarshal(value) if !result.OK { - if err, ok := result.Value.(error); ok { + if err, ok := result.Value.(resultFailure); ok { return nil, err } return nil, core.NewError(result.Error()) diff --git a/go/pkg/webview/result_failure.go b/go/pkg/webview/result_failure.go new file mode 100644 index 00000000..41dbecf9 --- /dev/null +++ b/go/pkg/webview/result_failure.go @@ -0,0 +1,5 @@ +package webview + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/webview/service.go b/go/pkg/webview/service.go index a507a249..638390e0 100644 --- a/go/pkg/webview/service.go +++ b/go/pkg/webview/service.go @@ -17,27 +17,27 @@ import ( // connector abstracts go-webview for testing. The real implementation wraps // *gowebview.Webview, converting go-webview types to our own types at the boundary. type connector interface { - Navigate(url string) error - Click(selector string) error - Type(selector, text string) error - Hover(selector string) error - Select(selector, value string) error - Check(selector string, checked bool) error - Evaluate(script string) (any, error) - Screenshot() ([]byte, error) - GetURL() (string, error) - GetTitle() (string, error) - GetHTML(selector string) (string, error) - QuerySelector(selector string) (*ElementInfo, error) - QuerySelectorAll(selector string) ([]*ElementInfo, error) + Navigate(url string) resultFailure + Click(selector string) resultFailure + Type(selector, text string) resultFailure + Hover(selector string) resultFailure + Select(selector, value string) resultFailure + Check(selector string, checked bool) resultFailure + Evaluate(script string) (any, resultFailure) + Screenshot() ([]byte, resultFailure) + GetURL() (string, resultFailure) + GetTitle() (string, resultFailure) + GetHTML(selector string) (string, resultFailure) + QuerySelector(selector string) (*ElementInfo, resultFailure) + QuerySelectorAll(selector string) ([]*ElementInfo, resultFailure) GetConsole() []ConsoleMessage ClearConsole() - SetViewport(width, height int) error - UploadFile(selector string, paths []string) error - GetZoom() (float64, error) - SetZoom(zoom float64) error - Print(toPDF bool) ([]byte, error) - Close() error + SetViewport(width, height int) resultFailure + UploadFile(selector string, paths []string) resultFailure + GetZoom() (float64, resultFailure) + SetZoom(zoom float64) resultFailure + Print(toPDF bool) ([]byte, resultFailure) + Close() resultFailure } type Options struct { @@ -53,8 +53,8 @@ type Service struct { mu sync.RWMutex diagMu sync.RWMutex exceptions map[string][]ExceptionInfo - newConn func(debugURL, windowName string) (connector, error) // injectable for tests - watcherSetup func(conn connector, windowName string) // called after connection creation + newConn func(debugURL, windowName string) (connector, resultFailure) // injectable for tests + watcherSetup func(conn connector, windowName string) // called after connection creation } // Register binds the webview service to a Core instance. @@ -83,8 +83,8 @@ func Register(optionFns ...func(*Options)) func(*core.Core) core.Result { } // defaultNewConn creates real go-webview connections. -func defaultNewConn(options Options) func(string, string) (connector, error) { - return func(debugURL, windowName string) (connector, error) { +func defaultNewConn(options Options) func(string, string) (connector, resultFailure) { + return func(debugURL, windowName string) (connector, resultFailure) { windowName = core.Trim(windowName) if windowName == "" { return nil, core.E("webview.connect", "window name is required", nil) @@ -201,7 +201,7 @@ func (s *Service) HandleIPCEvents(_ *core.Core, msg core.Message) core.Result { } // getConn returns the connector for a window, creating it if needed. -func (s *Service) getConn(windowName string) (connector, error) { +func (s *Service) getConn(windowName string) (connector, resultFailure) { windowName = core.Trim(windowName) if windowName == "" { return nil, core.E("webview.getConn", "window name is required", nil) @@ -486,8 +486,8 @@ func (s *Service) exceptionLog(windowName string, limit int) []ExceptionInfo { return log } -func (s *Service) devToolsOpen(windowName string) error { - return s.withWindowHandle(windowName, func(handle any) error { +func (s *Service) devToolsOpen(windowName string) resultFailure { + return s.withWindowHandle(windowName, func(handle any) resultFailure { if opener, ok := handle.(interface{ OpenDevTools() }); ok { opener.OpenDevTools() return nil @@ -496,8 +496,8 @@ func (s *Service) devToolsOpen(windowName string) error { }) } -func (s *Service) devToolsClose(windowName string) error { - return s.withWindowHandle(windowName, func(handle any) error { +func (s *Service) devToolsClose(windowName string) resultFailure { + return s.withWindowHandle(windowName, func(handle any) resultFailure { if closer, ok := handle.(interface{ CloseDevTools() }); ok { closer.CloseDevTools() return nil @@ -506,7 +506,7 @@ func (s *Service) devToolsClose(windowName string) error { }) } -func (s *Service) withWindowHandle(windowName string, fn func(handle any) error) error { +func (s *Service) withWindowHandle(windowName string, fn func(handle any) resultFailure) resultFailure { windowService, ok := core.ServiceFor[*window.Service](s.Core(), "window") if !ok { return core.E("webview.withWindowHandle", "window service unavailable", nil) @@ -526,22 +526,24 @@ type realConnector struct { debugURL string // Chrome debug HTTP endpoint (e.g., http://localhost:9222) for direct CDP calls } -func (r *realConnector) Navigate(url string) error { return r.wv.Navigate(url) } -func (r *realConnector) Click(sel string) error { return r.wv.Click(sel) } -func (r *realConnector) Type(sel, text string) error { return r.wv.Type(sel, text) } -func (r *realConnector) Evaluate(script string) (any, error) { return r.wv.Evaluate(script) } -func (r *realConnector) Screenshot() ([]byte, error) { return r.wv.Screenshot() } -func (r *realConnector) GetURL() (string, error) { return r.wv.GetURL() } -func (r *realConnector) GetTitle() (string, error) { return r.wv.GetTitle() } -func (r *realConnector) GetHTML(sel string) (string, error) { return r.wv.GetHTML(sel) } -func (r *realConnector) ClearConsole() { r.wv.ClearConsole() } -func (r *realConnector) Close() error { return r.wv.Close() } -func (r *realConnector) SetViewport(w, h int) error { return r.wv.SetViewport(w, h) } -func (r *realConnector) UploadFile(sel string, p []string) error { return r.wv.UploadFile(sel, p) } +func (r *realConnector) Navigate(url string) resultFailure { return r.wv.Navigate(url) } +func (r *realConnector) Click(sel string) resultFailure { return r.wv.Click(sel) } +func (r *realConnector) Type(sel, text string) resultFailure { return r.wv.Type(sel, text) } +func (r *realConnector) Evaluate(script string) (any, resultFailure) { return r.wv.Evaluate(script) } +func (r *realConnector) Screenshot() ([]byte, resultFailure) { return r.wv.Screenshot() } +func (r *realConnector) GetURL() (string, resultFailure) { return r.wv.GetURL() } +func (r *realConnector) GetTitle() (string, resultFailure) { return r.wv.GetTitle() } +func (r *realConnector) GetHTML(sel string) (string, resultFailure) { return r.wv.GetHTML(sel) } +func (r *realConnector) ClearConsole() { r.wv.ClearConsole() } +func (r *realConnector) Close() resultFailure { return r.wv.Close() } +func (r *realConnector) SetViewport(w, h int) resultFailure { return r.wv.SetViewport(w, h) } +func (r *realConnector) UploadFile(sel string, p []string) resultFailure { + return r.wv.UploadFile(sel, p) +} // GetZoom returns the current CSS zoom level as a float64. // zoom, _ := conn.GetZoom() // 1.0 = 100%, 1.5 = 150% -func (r *realConnector) GetZoom() (float64, error) { +func (r *realConnector) GetZoom() (float64, resultFailure) { raw, err := r.wv.Evaluate("parseFloat(document.documentElement.style.zoom) || 1.0") if err != nil { return 0, core.E("realConnector.GetZoom", "failed to get zoom", err) @@ -559,7 +561,7 @@ func (r *realConnector) GetZoom() (float64, error) { // SetZoom sets the CSS zoom level on the document root element. // conn.SetZoom(1.5) // 150% // conn.SetZoom(1.0) // reset to normal -func (r *realConnector) SetZoom(zoom float64) error { +func (r *realConnector) SetZoom(zoom float64) resultFailure { script := "document.documentElement.style.zoom = '" + strconv.FormatFloat(zoom, 'g', -1, 64) + "'; undefined" _, err := r.wv.Evaluate(script) if err != nil { @@ -572,7 +574,7 @@ func (r *realConnector) SetZoom(zoom float64) error { // When toPDF is false the browser print dialog is opened (via window.print()) and nil bytes are returned. // When toPDF is true a fresh CDPClient is opened against the stored WebSocket URL to issue // Page.printToPDF, which returns raw PDF bytes. -func (r *realConnector) Print(toPDF bool) ([]byte, error) { +func (r *realConnector) Print(toPDF bool) ([]byte, resultFailure) { if !toPDF { _, err := r.wv.Evaluate("window.print(); undefined") if err != nil { @@ -616,19 +618,19 @@ func (r *realConnector) Print(toPDF bool) ([]byte, error) { return pdfBytes, nil } -func (r *realConnector) Hover(sel string) error { +func (r *realConnector) Hover(sel string) resultFailure { return gowebview.NewActionSequence().Add(&gowebview.HoverAction{Selector: sel}).Execute(context.Background(), r.wv) } -func (r *realConnector) Select(sel, val string) error { +func (r *realConnector) Select(sel, val string) resultFailure { return gowebview.NewActionSequence().Add(&gowebview.SelectAction{Selector: sel, Value: val}).Execute(context.Background(), r.wv) } -func (r *realConnector) Check(sel string, checked bool) error { +func (r *realConnector) Check(sel string, checked bool) resultFailure { return gowebview.NewActionSequence().Add(&gowebview.CheckAction{Selector: sel, Checked: checked}).Execute(context.Background(), r.wv) } -func (r *realConnector) QuerySelector(sel string) (*ElementInfo, error) { +func (r *realConnector) QuerySelector(sel string) (*ElementInfo, resultFailure) { el, err := r.wv.QuerySelector(sel) if err != nil { return nil, err @@ -636,7 +638,7 @@ func (r *realConnector) QuerySelector(sel string) (*ElementInfo, error) { return convertElementInfo(el), nil } -func (r *realConnector) QuerySelectorAll(sel string) ([]*ElementInfo, error) { +func (r *realConnector) QuerySelectorAll(sel string) ([]*ElementInfo, resultFailure) { els, err := r.wv.QuerySelectorAll(sel) if err != nil { return nil, err diff --git a/go/pkg/webview/service_test.go b/go/pkg/webview/service_test.go index 26c875e0..087be0e6 100644 --- a/go/pkg/webview/service_test.go +++ b/go/pkg/webview/service_test.go @@ -39,72 +39,72 @@ type mockConnector struct { printToPDF bool printCalled bool printPDFBytes []byte - printErr error + printErr resultFailure } -func (m *mockConnector) Navigate(url string) error { m.lastNavURL = url; return nil } -func (m *mockConnector) Click(sel string) error { m.lastClickSel = sel; return nil } -func (m *mockConnector) Type(sel, text string) error { +func (m *mockConnector) Navigate(url string) resultFailure { m.lastNavURL = url; return nil } +func (m *mockConnector) Click(sel string) resultFailure { m.lastClickSel = sel; return nil } +func (m *mockConnector) Type(sel, text string) resultFailure { m.lastTypeSel = sel m.lastTypeText = text return nil } -func (m *mockConnector) Hover(sel string) error { m.lastHoverSel = sel; return nil } -func (m *mockConnector) Select(sel, val string) error { +func (m *mockConnector) Hover(sel string) resultFailure { m.lastHoverSel = sel; return nil } +func (m *mockConnector) Select(sel, val string) resultFailure { m.lastSelectSel = sel m.lastSelectVal = val return nil } -func (m *mockConnector) Check(sel string, c bool) error { +func (m *mockConnector) Check(sel string, c bool) resultFailure { m.lastCheckSel = sel m.lastCheckVal = c return nil } -func (m *mockConnector) Evaluate(s string) (any, error) { return m.evalResult, nil } -func (m *mockConnector) Screenshot() ([]byte, error) { return m.screenshot, nil } -func (m *mockConnector) GetURL() (string, error) { return m.url, nil } -func (m *mockConnector) GetTitle() (string, error) { return m.title, nil } -func (m *mockConnector) GetHTML(sel string) (string, error) { return m.html, nil } -func (m *mockConnector) ClearConsole() { m.consoleClearCalled = true } -func (m *mockConnector) Close() error { m.closed = true; return nil } -func (m *mockConnector) SetViewport(w, h int) error { +func (m *mockConnector) Evaluate(s string) (any, resultFailure) { return m.evalResult, nil } +func (m *mockConnector) Screenshot() ([]byte, resultFailure) { return m.screenshot, nil } +func (m *mockConnector) GetURL() (string, resultFailure) { return m.url, nil } +func (m *mockConnector) GetTitle() (string, resultFailure) { return m.title, nil } +func (m *mockConnector) GetHTML(sel string) (string, resultFailure) { return m.html, nil } +func (m *mockConnector) ClearConsole() { m.consoleClearCalled = true } +func (m *mockConnector) Close() resultFailure { m.closed = true; return nil } +func (m *mockConnector) SetViewport(w, h int) resultFailure { m.lastViewportW = w m.lastViewportH = h return nil } -func (m *mockConnector) UploadFile(sel string, p []string) error { +func (m *mockConnector) UploadFile(sel string, p []string) resultFailure { m.lastUploadSel = sel m.lastUploadPaths = p return nil } -func (m *mockConnector) QuerySelector(sel string) (*ElementInfo, error) { +func (m *mockConnector) QuerySelector(sel string) (*ElementInfo, resultFailure) { if len(m.elements) > 0 { return m.elements[0], nil } return nil, nil } -func (m *mockConnector) QuerySelectorAll(sel string) ([]*ElementInfo, error) { +func (m *mockConnector) QuerySelectorAll(sel string) ([]*ElementInfo, resultFailure) { return m.elements, nil } func (m *mockConnector) GetConsole() []ConsoleMessage { return m.console } -func (m *mockConnector) GetZoom() (float64, error) { +func (m *mockConnector) GetZoom() (float64, resultFailure) { if m.zoom == 0 { return 1.0, nil } return m.zoom, nil } -func (m *mockConnector) SetZoom(zoom float64) error { +func (m *mockConnector) SetZoom(zoom float64) resultFailure { m.lastZoomSet = zoom m.zoom = zoom return nil } -func (m *mockConnector) Print(toPDF bool) ([]byte, error) { +func (m *mockConnector) Print(toPDF bool) ([]byte, resultFailure) { m.printCalled = true m.printToPDF = toPDF return m.printPDFBytes, m.printErr @@ -117,7 +117,7 @@ func newTestService(t *core.T, mock *mockConnector) (*Service, *core.Core) { core.RequireTrue(t, c.ServiceStartup(context.Background(), nil).OK) svc := core.MustServiceFor[*Service](c, "webview") // Inject mock connector - svc.newConn = func(_, _ string) (connector, error) { return mock, nil } + svc.newConn = func(_, _ string) (connector, resultFailure) { return mock, nil } return svc, c } @@ -137,7 +137,7 @@ func newTestServiceWithWindow(t *core.T, mock *mockConnector) (*Service, *window core.RequireTrue(t, result.OK) svc := core.MustServiceFor[*Service](c, "webview") - svc.newConn = func(_, _ string) (connector, error) { return mock, nil } + svc.newConn = func(_, _ string) (connector, resultFailure) { return mock, nil } return svc, windowPlatform, c } @@ -288,7 +288,7 @@ func TestTaskSetURL_Bad_UnknownWindow(t *core.T) { _, c := newTestService(t, &mockConnector{}) // Inject a connector factory that errors svc := core.MustServiceFor[*Service](c, "webview") - svc.newConn = func(_, _ string) (connector, error) { + svc.newConn = func(_, _ string) (connector, resultFailure) { return nil, core.E("test", "no connection", nil) } r := taskRun(c, "webview.setURL", TaskSetURL{Window: "bad", URL: "https://example.com"}) diff --git a/go/pkg/window/core_helpers.go b/go/pkg/window/core_helpers.go index b0ffd6ff..178588d1 100644 --- a/go/pkg/window/core_helpers.go +++ b/go/pkg/window/core_helpers.go @@ -2,7 +2,7 @@ package window import core "dappco.re/go" -func coreResultError(result core.Result, fallback string) error { +func coreResultError(result core.Result, fallback string) resultFailure { if result.OK { return nil } @@ -15,7 +15,7 @@ func coreResultError(result core.Result, fallback string) error { return core.NewError(fallback) } -func coreReadFile(path string) ([]byte, error) { +func coreReadFile(path string) ([]byte, resultFailure) { result := core.ReadFile(path) if !result.OK { return nil, coreResultError(result, "failed to read file") @@ -23,11 +23,11 @@ func coreReadFile(path string) ([]byte, error) { return result.Value.([]byte), nil } -func coreWriteFile(path string, data []byte, mode core.FileMode) error { +func coreWriteFile(path string, data []byte, mode core.FileMode) resultFailure { return coreResultError(core.WriteFile(path, data, mode), "failed to write file") } -func coreMkdirAll(path string, mode core.FileMode) error { +func coreMkdirAll(path string, mode core.FileMode) resultFailure { return coreResultError(core.MkdirAll(path, mode), "failed to create directory") } diff --git a/go/pkg/window/layout.go b/go/pkg/window/layout.go index fc833dbf..a9985746 100644 --- a/go/pkg/window/layout.go +++ b/go/pkg/window/layout.go @@ -130,7 +130,7 @@ func (lm *LayoutManager) load() { lm.mu.Unlock() } -func (lm *LayoutManager) save() error { +func (lm *LayoutManager) save() resultFailure { if lm.configDir == "" && lm.layoutPath == "" { return nil } @@ -174,7 +174,7 @@ func (lm *LayoutManager) save() error { } // SaveLayout creates or updates a named layout. -func (lm *LayoutManager) SaveLayout(name string, windowStates map[string]WindowState) error { +func (lm *LayoutManager) SaveLayout(name string, windowStates map[string]WindowState) resultFailure { if name == "" { return core.E("window.LayoutManager.SaveLayout", "layout name cannot be empty", nil) } diff --git a/go/pkg/window/mock_platform.go b/go/pkg/window/mock_platform.go index f2a16a1c..b0b3dc9e 100644 --- a/go/pkg/window/mock_platform.go +++ b/go/pkg/window/mock_platform.go @@ -99,7 +99,7 @@ func (w *MockWindow) ToggleFullscreen() { w.fullscreened = !w func (w *MockWindow) ToggleMaximise() { w.maximised = !w.maximised } func (w *MockWindow) ExecJS(js string) { w.execJSCalls = append(w.execJSCalls, js) } func (w *MockWindow) Flash(enabled bool) { w.flashed = enabled } -func (w *MockWindow) Print() error { return nil } +func (w *MockWindow) Print() resultFailure { return nil } func (w *MockWindow) OpenDevTools() { w.devToolsOpen = true } func (w *MockWindow) CloseDevTools() { w.devToolsOpen = false } func (w *MockWindow) OnWindowEvent(handler func(WindowEvent)) { diff --git a/go/pkg/window/mock_test.go b/go/pkg/window/mock_test.go index 3b894079..daa03df4 100644 --- a/go/pkg/window/mock_test.go +++ b/go/pkg/window/mock_test.go @@ -96,7 +96,7 @@ func (w *mockWindow) ToggleFullscreen() { w.fullscreened = !w func (w *mockWindow) ToggleMaximise() { w.maximised = !w.maximised } func (w *mockWindow) ExecJS(js string) { w.execJSCalls = append(w.execJSCalls, js) } func (w *mockWindow) Flash(enabled bool) { w.flashed = enabled } -func (w *mockWindow) Print() error { return nil } +func (w *mockWindow) Print() resultFailure { return nil } func (w *mockWindow) OpenDevTools() { w.devToolsOpen = true } func (w *mockWindow) CloseDevTools() { w.devToolsOpen = false } func (w *mockWindow) OnWindowEvent(handler func(WindowEvent)) { diff --git a/go/pkg/window/options.go b/go/pkg/window/options.go index 157b1113..7581bcce 100644 --- a/go/pkg/window/options.go +++ b/go/pkg/window/options.go @@ -3,10 +3,10 @@ package window // WindowOption is the compatibility layer for option-chain callers. // Prefer a Window literal with Manager.Create. -type WindowOption func(*Window) error +type WindowOption func(*Window) resultFailure // Deprecated: use Manager.Create(&Window{Name: "settings", URL: "/", Width: 800, Height: 600}). -func ApplyOptions(options ...WindowOption) (*Window, error) { +func ApplyOptions(options ...WindowOption) (*Window, resultFailure) { windowSpec := &Window{} for _, option := range options { if option == nil { @@ -22,83 +22,95 @@ func ApplyOptions(options ...WindowOption) (*Window, error) { // Compatibility helpers for callers still using option chains. // Use: window.WithName("main") func WithName(name string) WindowOption { - return func(windowSpec *Window) error { windowSpec.Name = name; return nil } + return func(windowSpec *Window) resultFailure { windowSpec.Name = name; return nil } } // WithTitle sets the window title. // Use: window.WithTitle("Core Editor") func WithTitle(title string) WindowOption { - return func(windowSpec *Window) error { windowSpec.Title = title; return nil } + return func(windowSpec *Window) resultFailure { windowSpec.Title = title; return nil } } // WithURL sets the initial window URL. // Use: window.WithURL("/editor") func WithURL(url string) WindowOption { - return func(windowSpec *Window) error { windowSpec.URL = url; return nil } + return func(windowSpec *Window) resultFailure { windowSpec.URL = url; return nil } } // WithHTML sets the initial HTML content. // Use: window.WithHTML("
Ready
") func WithHTML(html string) WindowOption { - return func(windowSpec *Window) error { windowSpec.HTML = html; return nil } + return func(windowSpec *Window) resultFailure { windowSpec.HTML = html; return nil } } // WithJS sets the initial preload JavaScript. // Use: window.WithJS("window.__CORE_READY__ = true") func WithJS(js string) WindowOption { - return func(windowSpec *Window) error { windowSpec.JS = js; return nil } + return func(windowSpec *Window) resultFailure { windowSpec.JS = js; return nil } } // WithSize sets the initial window size. // Use: window.WithSize(1280, 800) func WithSize(width, height int) WindowOption { - return func(windowSpec *Window) error { windowSpec.Width = width; windowSpec.Height = height; return nil } + return func(windowSpec *Window) resultFailure { + windowSpec.Width = width + windowSpec.Height = height + return nil + } } // WithPosition sets the initial window position. // Use: window.WithPosition(160, 120) func WithPosition(x, y int) WindowOption { - return func(windowSpec *Window) error { windowSpec.X = x; windowSpec.Y = y; return nil } + return func(windowSpec *Window) resultFailure { windowSpec.X = x; windowSpec.Y = y; return nil } } // WithMinSize sets the minimum window size. // Use: window.WithMinSize(640, 480) func WithMinSize(width, height int) WindowOption { - return func(windowSpec *Window) error { windowSpec.MinWidth = width; windowSpec.MinHeight = height; return nil } + return func(windowSpec *Window) resultFailure { + windowSpec.MinWidth = width + windowSpec.MinHeight = height + return nil + } } // WithMaxSize sets the maximum window size. // Use: window.WithMaxSize(1920, 1080) func WithMaxSize(width, height int) WindowOption { - return func(windowSpec *Window) error { windowSpec.MaxWidth = width; windowSpec.MaxHeight = height; return nil } + return func(windowSpec *Window) resultFailure { + windowSpec.MaxWidth = width + windowSpec.MaxHeight = height + return nil + } } // WithFrameless toggles the native window frame. // Use: window.WithFrameless(true) func WithFrameless(frameless bool) WindowOption { - return func(windowSpec *Window) error { windowSpec.Frameless = frameless; return nil } + return func(windowSpec *Window) resultFailure { windowSpec.Frameless = frameless; return nil } } // WithHidden starts the window hidden. // Use: window.WithHidden(true) func WithHidden(hidden bool) WindowOption { - return func(windowSpec *Window) error { windowSpec.Hidden = hidden; return nil } + return func(windowSpec *Window) resultFailure { windowSpec.Hidden = hidden; return nil } } // WithAlwaysOnTop keeps the window above other windows. // Use: window.WithAlwaysOnTop(true) func WithAlwaysOnTop(alwaysOnTop bool) WindowOption { - return func(windowSpec *Window) error { windowSpec.AlwaysOnTop = alwaysOnTop; return nil } + return func(windowSpec *Window) resultFailure { windowSpec.AlwaysOnTop = alwaysOnTop; return nil } } // WithBackgroundColour sets the window background colour with alpha. // Use: window.WithBackgroundColour(0, 0, 0, 0) func WithBackgroundColour(r, g, b, a uint8) WindowOption { - return func(windowSpec *Window) error { windowSpec.BackgroundColour = [4]uint8{r, g, b, a}; return nil } + return func(windowSpec *Window) resultFailure { windowSpec.BackgroundColour = [4]uint8{r, g, b, a}; return nil } } // WithFileDrop enables drag-and-drop file handling. // Use: window.WithFileDrop(true) func WithFileDrop(enabled bool) WindowOption { - return func(windowSpec *Window) error { windowSpec.EnableFileDrop = enabled; return nil } + return func(windowSpec *Window) resultFailure { windowSpec.EnableFileDrop = enabled; return nil } } diff --git a/go/pkg/window/options_test.go b/go/pkg/window/options_test.go index 61ff2c3d..8763524e 100644 --- a/go/pkg/window/options_test.go +++ b/go/pkg/window/options_test.go @@ -149,7 +149,7 @@ func TestOptions_ApplyOptions_Bad(t *core.T) { w, err := ApplyOptions( WithName("before"), - func(*Window) error { return boom }, + func(*Window) resultFailure { return boom }, WithTitle("after"), ) diff --git a/go/pkg/window/platform.go b/go/pkg/window/platform.go index 7eb4f8ef..4b947aa8 100644 --- a/go/pkg/window/platform.go +++ b/go/pkg/window/platform.go @@ -76,7 +76,7 @@ type PlatformWindow interface { // Utilities Flash(enabled bool) - Print() error + Print() resultFailure // Events OnWindowEvent(handler func(event WindowEvent)) diff --git a/go/pkg/window/result_failure.go b/go/pkg/window/result_failure.go new file mode 100644 index 00000000..4800e950 --- /dev/null +++ b/go/pkg/window/result_failure.go @@ -0,0 +1,5 @@ +package window + +type resultFailure = interface { + Error() string +} diff --git a/go/pkg/window/service.go b/go/pkg/window/service.go index 53543f25..49154d29 100644 --- a/go/pkg/window/service.go +++ b/go/pkg/window/service.go @@ -390,7 +390,7 @@ func (s *Service) registerTaskActions() { }) } -func taskFromOptions[T any](action string, opts core.Options) (T, error) { +func taskFromOptions[T any](action string, opts core.Options) (T, resultFailure) { var zero T task := opts.Get("task") if !task.OK { @@ -527,7 +527,7 @@ func (s *Service) trackWindow(pw PlatformWindow) { }) } -func (s *Service) taskCloseWindow(name string) error { +func (s *Service) taskCloseWindow(name string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskClose", "window not found: "+name, nil) @@ -540,7 +540,7 @@ func (s *Service) taskCloseWindow(name string) error { return nil } -func (s *Service) taskSetPosition(name string, x, y int) error { +func (s *Service) taskSetPosition(name string, x, y int) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetPosition", "window not found: "+name, nil) @@ -550,7 +550,7 @@ func (s *Service) taskSetPosition(name string, x, y int) error { return nil } -func (s *Service) taskSetSize(name string, width, height int) error { +func (s *Service) taskSetSize(name string, width, height int) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetSize", "window not found: "+name, nil) @@ -560,7 +560,7 @@ func (s *Service) taskSetSize(name string, width, height int) error { return nil } -func (s *Service) taskMaximise(name string) error { +func (s *Service) taskMaximise(name string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskMaximise", "window not found: "+name, nil) @@ -570,7 +570,7 @@ func (s *Service) taskMaximise(name string) error { return nil } -func (s *Service) taskMinimise(name string) error { +func (s *Service) taskMinimise(name string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskMinimise", "window not found: "+name, nil) @@ -579,7 +579,7 @@ func (s *Service) taskMinimise(name string) error { return nil } -func (s *Service) taskFocus(name string) error { +func (s *Service) taskFocus(name string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskFocus", "window not found: "+name, nil) @@ -588,7 +588,7 @@ func (s *Service) taskFocus(name string) error { return nil } -func (s *Service) taskRestore(name string) error { +func (s *Service) taskRestore(name string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskRestore", "window not found: "+name, nil) @@ -598,7 +598,7 @@ func (s *Service) taskRestore(name string) error { return nil } -func (s *Service) taskSetTitle(name, title string) error { +func (s *Service) taskSetTitle(name, title string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetTitle", "window not found: "+name, nil) @@ -607,7 +607,7 @@ func (s *Service) taskSetTitle(name, title string) error { return nil } -func (s *Service) taskSetAlwaysOnTop(name string, alwaysOnTop bool) error { +func (s *Service) taskSetAlwaysOnTop(name string, alwaysOnTop bool) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetAlwaysOnTop", "window not found: "+name, nil) @@ -616,7 +616,7 @@ func (s *Service) taskSetAlwaysOnTop(name string, alwaysOnTop bool) error { return nil } -func (s *Service) taskSetOpacity(name string, opacity float64) error { +func (s *Service) taskSetOpacity(name string, opacity float64) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetOpacity", "window not found: "+name, nil) @@ -631,7 +631,7 @@ func (s *Service) taskSetOpacity(name string, opacity float64) error { return nil } -func (s *Service) taskSetBackgroundColour(name string, red, green, blue, alpha uint8) error { +func (s *Service) taskSetBackgroundColour(name string, red, green, blue, alpha uint8) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetBackgroundColour", "window not found: "+name, nil) @@ -640,7 +640,7 @@ func (s *Service) taskSetBackgroundColour(name string, red, green, blue, alpha u return nil } -func (s *Service) taskSetVisibility(name string, visible bool) error { +func (s *Service) taskSetVisibility(name string, visible bool) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetVisibility", "window not found: "+name, nil) @@ -649,7 +649,7 @@ func (s *Service) taskSetVisibility(name string, visible bool) error { return nil } -func (s *Service) taskFullscreen(name string, fullscreen bool) error { +func (s *Service) taskFullscreen(name string, fullscreen bool) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskFullscreen", "window not found: "+name, nil) @@ -662,7 +662,7 @@ func (s *Service) taskFullscreen(name string, fullscreen bool) error { return nil } -func (s *Service) taskSaveLayout(name string) error { +func (s *Service) taskSaveLayout(name string) resultFailure { windows := s.queryWindowList() states := make(map[string]WindowState, len(windows)) for _, w := range windows { @@ -674,7 +674,7 @@ func (s *Service) taskSaveLayout(name string) error { return s.manager.Layout().SaveLayout(name, states) } -func (s *Service) taskRestoreLayout(name string) error { +func (s *Service) taskRestoreLayout(name string) resultFailure { layout, ok := s.manager.Layout().GetLayout(name) if !ok { return core.E("window.taskRestoreLayout", "layout not found: "+name, nil) @@ -704,7 +704,7 @@ var tileModeMap = map[string]TileMode{ "left-right": TileModeLeftRight, "grid": TileModeGrid, } -func (s *Service) taskTileWindows(mode string, names []string) error { +func (s *Service) taskTileWindows(mode string, names []string) resultFailure { tm, ok := tileModeMap[mode] if !ok { return core.E("window.taskTileWindows", "unknown tile mode: "+mode, nil) @@ -716,7 +716,7 @@ func (s *Service) taskTileWindows(mode string, names []string) error { return s.manager.TileWindows(tm, names, screenWidth, screenHeight, originX, originY) } -func (s *Service) taskStackWindows(names []string, offsetX, offsetY int) error { +func (s *Service) taskStackWindows(names []string, offsetX, offsetY int) resultFailure { if len(names) == 0 { names = s.manager.List() } @@ -732,7 +732,7 @@ var snapPosMap = map[string]SnapPosition{ "center": SnapCenter, "centre": SnapCenter, } -func (s *Service) taskSnapWindow(name, position string) error { +func (s *Service) taskSnapWindow(name, position string) resultFailure { pos, ok := snapPosMap[position] if !ok { return core.E("window.taskSnapWindow", "unknown snap position: "+position, nil) @@ -748,7 +748,7 @@ var workflowLayoutMap = map[string]WorkflowLayout{ "side-by-side": WorkflowSideBySide, } -func (s *Service) taskApplyWorkflow(workflow string, names []string) error { +func (s *Service) taskApplyWorkflow(workflow string, names []string) resultFailure { layout, ok := workflowLayoutMap[workflow] if !ok { return core.E("window.taskApplyWorkflow", "unknown workflow layout: "+workflow, nil) @@ -770,7 +770,7 @@ func (s *Service) queryWindowZoom(name string) core.Result { return core.Result{Value: pw.GetZoom(), OK: true} } -func (s *Service) taskSetZoom(name string, magnification float64) error { +func (s *Service) taskSetZoom(name string, magnification float64) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetZoom", "window not found: "+name, nil) @@ -779,7 +779,7 @@ func (s *Service) taskSetZoom(name string, magnification float64) error { return nil } -func (s *Service) taskZoomIn(name string) error { +func (s *Service) taskZoomIn(name string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskZoomIn", "window not found: "+name, nil) @@ -789,7 +789,7 @@ func (s *Service) taskZoomIn(name string) error { return nil } -func (s *Service) taskZoomOut(name string) error { +func (s *Service) taskZoomOut(name string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskZoomOut", "window not found: "+name, nil) @@ -803,7 +803,7 @@ func (s *Service) taskZoomOut(name string) error { return nil } -func (s *Service) taskZoomReset(name string) error { +func (s *Service) taskZoomReset(name string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskZoomReset", "window not found: "+name, nil) @@ -814,7 +814,7 @@ func (s *Service) taskZoomReset(name string) error { // --- Content --- -func (s *Service) taskSetURL(name, url string) error { +func (s *Service) taskSetURL(name, url string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetURL", "window not found: "+name, nil) @@ -842,7 +842,7 @@ func (s *Service) taskSetURL(name, url string) error { return nil } -func (s *Service) taskSetHTML(name, html string) error { +func (s *Service) taskSetHTML(name, html string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetHTML", "window not found: "+name, nil) @@ -851,7 +851,7 @@ func (s *Service) taskSetHTML(name, html string) error { return nil } -func (s *Service) taskExecJS(name, js string) error { +func (s *Service) taskExecJS(name, js string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskExecJS", "window not found: "+name, nil) @@ -862,7 +862,7 @@ func (s *Service) taskExecJS(name, js string) error { // --- State toggles --- -func (s *Service) taskToggleFullscreen(name string) error { +func (s *Service) taskToggleFullscreen(name string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskToggleFullscreen", "window not found: "+name, nil) @@ -871,7 +871,7 @@ func (s *Service) taskToggleFullscreen(name string) error { return nil } -func (s *Service) taskToggleMaximise(name string) error { +func (s *Service) taskToggleMaximise(name string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskToggleMaximise", "window not found: "+name, nil) @@ -891,7 +891,7 @@ func (s *Service) queryWindowBounds(name string) core.Result { return core.Result{Value: WindowBounds{X: x, Y: y, Width: width, Height: height}, OK: true} } -func (s *Service) taskSetBounds(name string, x, y, width, height int) error { +func (s *Service) taskSetBounds(name string, x, y, width, height int) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetBounds", "window not found: "+name, nil) @@ -904,7 +904,7 @@ func (s *Service) taskSetBounds(name string, x, y, width, height int) error { // --- Content protection --- -func (s *Service) taskSetContentProtection(name string, protection bool) error { +func (s *Service) taskSetContentProtection(name string, protection bool) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskSetContentProtection", "window not found: "+name, nil) @@ -915,7 +915,7 @@ func (s *Service) taskSetContentProtection(name string, protection bool) error { // --- Flash --- -func (s *Service) taskFlash(name string, enabled bool) error { +func (s *Service) taskFlash(name string, enabled bool) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskFlash", "window not found: "+name, nil) @@ -926,7 +926,7 @@ func (s *Service) taskFlash(name string, enabled bool) error { // --- Print --- -func (s *Service) taskPrint(name string) error { +func (s *Service) taskPrint(name string) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.taskPrint", "window not found: "+name, nil) diff --git a/go/pkg/window/service_test.go b/go/pkg/window/service_test.go index 681fc02b..249687fc 100644 --- a/go/pkg/window/service_test.go +++ b/go/pkg/window/service_test.go @@ -416,7 +416,7 @@ func TestTaskSaveLayout_Good(t *core.T) { func TestTaskSaveLayout_Bad(t *core.T) { _, c := newTestWindowService(t) - // Saving an empty layout with empty name returns an error from LayoutManager + // Saving an empty layout with empty name returns an resultFailure from LayoutManager r := taskRun(c, "window.saveLayout", TaskSaveLayout{Name: ""}) core.AssertFalse(t, r.OK) } diff --git a/go/pkg/window/smart_layout.go b/go/pkg/window/smart_layout.go index a8987ab1..c3dd9022 100644 --- a/go/pkg/window/smart_layout.go +++ b/go/pkg/window/smart_layout.go @@ -13,7 +13,7 @@ type schemeResponse struct { Body string } -func (s *Service) buildWindowSpec(t TaskOpenWindow) (*Window, error) { +func (s *Service) buildWindowSpec(t TaskOpenWindow) (*Window, resultFailure) { if t.Window != nil { spec := *t.Window return &spec, nil @@ -21,7 +21,7 @@ func (s *Service) buildWindowSpec(t TaskOpenWindow) (*Window, error) { return ApplyOptions(t.Options...) } -func (s *Service) prepareWindowSpec(spec *Window) error { +func (s *Service) prepareWindowSpec(spec *Window) resultFailure { if spec == nil { return core.E("window.prepareWindowSpec", "window spec is nil", nil) } @@ -66,7 +66,7 @@ func (s *Service) buildPreload(rawURL string) string { return script } -func (s *Service) resolveCoreScheme(rawURL string) (schemeResponse, bool, error) { +func (s *Service) resolveCoreScheme(rawURL string) (schemeResponse, bool, resultFailure) { result := s.Core().Action("display.resolveScheme").Run(context.Background(), core.NewOptions( core.Option{Key: "url", Value: rawURL}, )) @@ -87,7 +87,7 @@ func (s *Service) resolveCoreScheme(rawURL string) (schemeResponse, bool, error) } } -func (s *Service) applyWindowBounds(name string, bounds WindowBounds) error { +func (s *Service) applyWindowBounds(name string, bounds WindowBounds) resultFailure { pw, ok := s.manager.Get(name) if !ok { return core.E("window.applyWindowBounds", "window not found: "+name, nil) @@ -216,7 +216,7 @@ func preferredEditorWindow(windows []WindowInfo, target string, explicit string) return nil } -func (s *Service) taskLayoutBesideEditor(task TaskLayoutBesideEditor) (LayoutBesideEditorResult, error) { +func (s *Service) taskLayoutBesideEditor(task TaskLayoutBesideEditor) (LayoutBesideEditorResult, resultFailure) { target := s.queryWindowByName(task.Name) if target == nil { return LayoutBesideEditorResult{}, core.E("window.taskLayoutBesideEditor", "window not found: "+task.Name, nil) @@ -467,7 +467,7 @@ func (s *Service) taskScreenFindSpace(task TaskScreenFindSpace) ScreenSpace { return best } -func (s *Service) taskWindowArrangePair(task TaskWindowArrangePair) (PairArrangement, error) { +func (s *Service) taskWindowArrangePair(task TaskWindowArrangePair) (PairArrangement, resultFailure) { if task.Primary == "" || task.Secondary == "" { return PairArrangement{}, core.E("window.taskWindowArrangePair", "primary and secondary windows are required", nil) } diff --git a/go/pkg/window/state.go b/go/pkg/window/state.go index fa4d266e..3d795dc4 100644 --- a/go/pkg/window/state.go +++ b/go/pkg/window/state.go @@ -129,7 +129,7 @@ func (sm *StateManager) load() { sm.mu.Unlock() } -func (sm *StateManager) save() error { +func (sm *StateManager) save() resultFailure { if sm.configDir == "" && sm.statePath == "" { return nil } @@ -289,7 +289,7 @@ func (sm *StateManager) stopSaveTimerLocked() { } // ForceSync writes state to disk immediately. -func (sm *StateManager) ForceSync() error { +func (sm *StateManager) ForceSync() resultFailure { sm.mu.Lock() sm.stopSaveTimerLocked() sm.mu.Unlock() diff --git a/go/pkg/window/tiling.go b/go/pkg/window/tiling.go index c5e24c4b..888855bf 100644 --- a/go/pkg/window/tiling.go +++ b/go/pkg/window/tiling.go @@ -370,7 +370,7 @@ func normalizeWindowForLayout(pw PlatformWindow) { } // TileWindows arranges the named windows in the given mode across the screen area. -func (m *Manager) TileWindows(mode TileMode, names []string, screenW, screenH int, origin ...int) error { +func (m *Manager) TileWindows(mode TileMode, names []string, screenW, screenH int, origin ...int) resultFailure { originX, originY := layoutOrigin(origin) windows := make([]PlatformWindow, 0, len(names)) for _, name := range names { @@ -465,7 +465,7 @@ func (m *Manager) TileWindows(mode TileMode, names []string, screenW, screenH in } // SnapWindow snaps a window to a screen edge/corner/centre. -func (m *Manager) SnapWindow(name string, pos SnapPosition, screenW, screenH int, origin ...int) error { +func (m *Manager) SnapWindow(name string, pos SnapPosition, screenW, screenH int, origin ...int) resultFailure { originX, originY := layoutOrigin(origin) pw, ok := m.Get(name) if !ok { @@ -509,7 +509,7 @@ func (m *Manager) SnapWindow(name string, pos SnapPosition, screenW, screenH int } // StackWindows cascades windows with an offset. -func (m *Manager) StackWindows(names []string, offsetX, offsetY int, origin ...int) error { +func (m *Manager) StackWindows(names []string, offsetX, offsetY int, origin ...int) resultFailure { originX, originY := layoutOrigin(origin) for i, name := range names { pw, ok := m.Get(name) @@ -523,7 +523,7 @@ func (m *Manager) StackWindows(names []string, offsetX, offsetY int, origin ...i } // ApplyWorkflow arranges windows in a predefined workflow layout. -func (m *Manager) ApplyWorkflow(workflow WorkflowLayout, names []string, screenW, screenH int, origin ...int) error { +func (m *Manager) ApplyWorkflow(workflow WorkflowLayout, names []string, screenW, screenH int, origin ...int) resultFailure { originX, originY := layoutOrigin(origin) if len(names) == 0 { return core.E("window.Manager.ApplyWorkflow", "no windows for workflow", nil) diff --git a/go/pkg/window/wails.go b/go/pkg/window/wails.go index 6a091ee9..6dd602eb 100644 --- a/go/pkg/window/wails.go +++ b/go/pkg/window/wails.go @@ -224,21 +224,21 @@ func (ww *wailsWindow) SetZoom(magnification float64) { ww.w.SetZoom(magnificati func (ww *wailsWindow) SetContentProtection(protection bool) { ww.w.SetContentProtection(protection) } -func (ww *wailsWindow) Maximise() { ww.w.Maximise() } -func (ww *wailsWindow) Restore() { ww.w.Restore() } -func (ww *wailsWindow) Minimise() { ww.w.Minimise() } -func (ww *wailsWindow) Focus() { ww.w.Focus() } -func (ww *wailsWindow) Close() { ww.w.Close() } -func (ww *wailsWindow) Show() { ww.w.Show() } -func (ww *wailsWindow) Hide() { ww.w.Hide() } -func (ww *wailsWindow) Fullscreen() { ww.w.Fullscreen() } -func (ww *wailsWindow) UnFullscreen() { ww.w.UnFullscreen() } -func (ww *wailsWindow) ToggleFullscreen() { ww.w.ToggleFullscreen() } -func (ww *wailsWindow) ToggleMaximise() { ww.w.ToggleMaximise() } -func (ww *wailsWindow) ExecJS(js string) { ww.w.ExecJS(js) } -func (ww *wailsWindow) Flash(enabled bool) { ww.w.Flash(enabled) } -func (ww *wailsWindow) Print() error { return ww.w.Print() } -func (ww *wailsWindow) OpenDevTools() { ww.w.OpenDevTools() } +func (ww *wailsWindow) Maximise() { ww.w.Maximise() } +func (ww *wailsWindow) Restore() { ww.w.Restore() } +func (ww *wailsWindow) Minimise() { ww.w.Minimise() } +func (ww *wailsWindow) Focus() { ww.w.Focus() } +func (ww *wailsWindow) Close() { ww.w.Close() } +func (ww *wailsWindow) Show() { ww.w.Show() } +func (ww *wailsWindow) Hide() { ww.w.Hide() } +func (ww *wailsWindow) Fullscreen() { ww.w.Fullscreen() } +func (ww *wailsWindow) UnFullscreen() { ww.w.UnFullscreen() } +func (ww *wailsWindow) ToggleFullscreen() { ww.w.ToggleFullscreen() } +func (ww *wailsWindow) ToggleMaximise() { ww.w.ToggleMaximise() } +func (ww *wailsWindow) ExecJS(js string) { ww.w.ExecJS(js) } +func (ww *wailsWindow) Flash(enabled bool) { ww.w.Flash(enabled) } +func (ww *wailsWindow) Print() resultFailure { return ww.w.Print() } +func (ww *wailsWindow) OpenDevTools() { ww.w.OpenDevTools() } func (ww *wailsWindow) CloseDevTools() { if closer, ok := any(ww.w).(interface{ CloseDevTools() }); ok { closer.CloseDevTools() diff --git a/go/pkg/window/window.go b/go/pkg/window/window.go index e693825a..51ca85c9 100644 --- a/go/pkg/window/window.go +++ b/go/pkg/window/window.go @@ -85,7 +85,7 @@ func (m *Manager) SetDefaultHeight(height int) { // Open creates a window from compatibility options. // Use: manager.Open(window.WithName("main"), window.WithURL("/"), window.WithSize(1280, 800)) -func (m *Manager) Open(options ...WindowOption) (PlatformWindow, error) { +func (m *Manager) Open(options ...WindowOption) (PlatformWindow, resultFailure) { windowSpec, err := ApplyOptions(options...) if err != nil { return nil, core.E("window.Manager.Open", "failed to apply options", err) @@ -94,7 +94,7 @@ func (m *Manager) Open(options ...WindowOption) (PlatformWindow, error) { } // Create creates a window from a Window descriptor. -func (m *Manager) Create(w *Window) (PlatformWindow, error) { +func (m *Manager) Create(w *Window) (PlatformWindow, resultFailure) { if w.Name == "" { w.Name = "main" } diff --git a/go/pkg/window/window_test.go b/go/pkg/window/window_test.go index b93c4236..5fe0641d 100644 --- a/go/pkg/window/window_test.go +++ b/go/pkg/window/window_test.go @@ -73,7 +73,7 @@ func TestApplyOptions_Good(t *core.T) { } func TestApplyOptions_Bad(t *core.T) { - _, err := ApplyOptions(func(w *Window) error { + _, err := ApplyOptions(func(w *Window) resultFailure { return core.AnError }) core.AssertError(t, err) @@ -143,7 +143,7 @@ func TestManager_Open_Bad(t *core.T) { ax7Variant := "Open:bad" core.AssertContains(t, ax7Variant, "bad") m, _ := newTestManager() - _, err := m.Open(func(w *Window) error { return core.AnError }) + _, err := m.Open(func(w *Window) resultFailure { return core.AnError }) core.AssertError(t, err) } diff --git a/go/stubs/wails/pkg/application/application.go b/go/stubs/wails/pkg/application/application.go index 7c2bf688..df95e66a 100644 --- a/go/stubs/wails/pkg/application/application.go +++ b/go/stubs/wails/pkg/application/application.go @@ -660,7 +660,7 @@ func (w *WebviewWindow) DispatchWailsEvent(event *CustomEvent) {} // GetScreen returns the screen on which this window is currently displayed. // // screen, err := w.GetScreen() -func (w *WebviewWindow) GetScreen() (*Screen, error) { return &Screen{}, nil } +func (w *WebviewWindow) GetScreen() (*Screen, resultFailure) { return &Screen{}, nil } // GetBorderSizes returns the platform-specific window border dimensions. // @@ -690,7 +690,7 @@ func (w *WebviewWindow) Flash(enabled bool) {} // Print opens the system print dialog for the webview contents. // // err := w.Print() -func (w *WebviewWindow) Print() error { return nil } +func (w *WebviewWindow) Print() resultFailure { return nil } // Error logs an error-level message on behalf of this window. // diff --git a/go/stubs/wails/pkg/application/browser.go b/go/stubs/wails/pkg/application/browser.go index a258f512..8bda0026 100644 --- a/go/stubs/wails/pkg/application/browser.go +++ b/go/stubs/wails/pkg/application/browser.go @@ -6,7 +6,7 @@ func newBrowserManager() *BrowserManager { return &BrowserManager{} } -func (bm *BrowserManager) Open(target string) error { +func (bm *BrowserManager) Open(target string) resultFailure { if bm == nil { return nil } diff --git a/go/stubs/wails/pkg/application/browser_manager.go b/go/stubs/wails/pkg/application/browser_manager.go index 606077b6..46ed8d66 100644 --- a/go/stubs/wails/pkg/application/browser_manager.go +++ b/go/stubs/wails/pkg/application/browser_manager.go @@ -17,7 +17,7 @@ type BrowserManager struct { // // manager.OpenURL("https://lthn.io") // _ = manager.LastURL // "https://lthn.io" -func (bm *BrowserManager) OpenURL(url string) error { +func (bm *BrowserManager) OpenURL(url string) resultFailure { if bm == nil { return nil } @@ -31,7 +31,7 @@ func (bm *BrowserManager) OpenURL(url string) error { // // manager.OpenFile("/home/user/report.pdf") // _ = manager.LastFile // "/home/user/report.pdf" -func (bm *BrowserManager) OpenFile(path string) error { +func (bm *BrowserManager) OpenFile(path string) resultFailure { if bm == nil { return nil } diff --git a/go/stubs/wails/pkg/application/browser_window.go b/go/stubs/wails/pkg/application/browser_window.go index 069e5dcb..bd609c6d 100644 --- a/go/stubs/wails/pkg/application/browser_window.go +++ b/go/stubs/wails/pkg/application/browser_window.go @@ -142,7 +142,7 @@ func (browserWindow *BrowserWindow) Fullscreen() Window { return browserWindow } func (browserWindow *BrowserWindow) GetBorderSizes() *LRTB { return &LRTB{} } -func (browserWindow *BrowserWindow) GetScreen() (*Screen, error) { +func (browserWindow *BrowserWindow) GetScreen() (*Screen, resultFailure) { return &Screen{}, nil } func (browserWindow *BrowserWindow) GetZoom() float64 { @@ -543,7 +543,7 @@ func (browserWindow *BrowserWindow) SetContentProtection(protection bool) Window } func (browserWindow *BrowserWindow) SetEnabled(enabled bool) {} func (browserWindow *BrowserWindow) Flash(enabled bool) {} -func (browserWindow *BrowserWindow) Print() error { return nil } +func (browserWindow *BrowserWindow) Print() resultFailure { return nil } func (browserWindow *BrowserWindow) RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() { return func() {} } diff --git a/go/stubs/wails/pkg/application/dialog.go b/go/stubs/wails/pkg/application/dialog.go index e658ebe2..4587eef7 100644 --- a/go/stubs/wails/pkg/application/dialog.go +++ b/go/stubs/wails/pkg/application/dialog.go @@ -136,7 +136,7 @@ func (d *OpenFileDialogStruct) SetSelectedFiles(paths []string) { // // path, err := dialog.PromptForSingleSelection() // if err != nil { return err } -func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) { +func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, resultFailure) { d.mu.RLock() defer d.mu.RUnlock() if len(d.selectedFiles) > 0 { @@ -149,7 +149,7 @@ func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) { // // paths, err := dialog.PromptForMultipleSelection() // for _, p := range paths { process(p) } -func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) { +func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, resultFailure) { d.mu.RLock() defer d.mu.RUnlock() return append([]string(nil), d.selectedFiles...), nil @@ -243,7 +243,7 @@ func (d *SaveFileDialogStruct) SetSelectedPath(path string) { // // path, err := dialog.PromptForSingleSelection() // if err != nil { return err } -func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) { +func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, resultFailure) { d.mu.RLock() defer d.mu.RUnlock() return d.selectedPath, nil @@ -342,7 +342,7 @@ func (d *MessageDialog) SetButtonClickedForStub(label string) { // // clicked, err := dialog.Show() // if clicked == "Yes" { deleteFile() } -func (d *MessageDialog) Show() (string, error) { +func (d *MessageDialog) Show() (string, resultFailure) { d.mu.RLock() defer d.mu.RUnlock() return d.clickedButton, nil @@ -424,23 +424,23 @@ func (dm *DialogManager) Error() *MessageDialog { return newMessageDialog(ErrorDialogType) } -func (dm *DialogManager) ShowInfo(args ...string) (string, error) { +func (dm *DialogManager) ShowInfo(args ...string) (string, resultFailure) { return dm.showDialog(dm.Info(), args...) } -func (dm *DialogManager) ShowQuestion(args ...string) (string, error) { +func (dm *DialogManager) ShowQuestion(args ...string) (string, resultFailure) { return dm.showDialog(dm.Question(), args...) } -func (dm *DialogManager) ShowWarning(args ...string) (string, error) { +func (dm *DialogManager) ShowWarning(args ...string) (string, resultFailure) { return dm.showDialog(dm.Warning(), args...) } -func (dm *DialogManager) ShowError(args ...string) (string, error) { +func (dm *DialogManager) ShowError(args ...string) (string, resultFailure) { return dm.showDialog(dm.Error(), args...) } -func (dm *DialogManager) showDialog(dialog *MessageDialog, args ...string) (string, error) { +func (dm *DialogManager) showDialog(dialog *MessageDialog, args ...string) (string, resultFailure) { if dialog == nil { return "", nil } diff --git a/go/stubs/wails/pkg/application/environment.go b/go/stubs/wails/pkg/application/environment.go index 1a6cbc10..bb78288b 100644 --- a/go/stubs/wails/pkg/application/environment.go +++ b/go/stubs/wails/pkg/application/environment.go @@ -109,7 +109,7 @@ func (em *EnvironmentManager) Info() EnvironmentInfo { } } -func (em *EnvironmentManager) OpenFileManager(path string, selectFile bool) error { +func (em *EnvironmentManager) OpenFileManager(path string, selectFile bool) resultFailure { if em == nil { return nil } diff --git a/go/stubs/wails/pkg/application/result_failure.go b/go/stubs/wails/pkg/application/result_failure.go new file mode 100644 index 00000000..b9f8dd16 --- /dev/null +++ b/go/stubs/wails/pkg/application/result_failure.go @@ -0,0 +1,5 @@ +package application + +type resultFailure = interface { + Error() string +} diff --git a/go/stubs/wails/pkg/application/screen.go b/go/stubs/wails/pkg/application/screen.go index 42f1265a..64a3ee1b 100644 --- a/go/stubs/wails/pkg/application/screen.go +++ b/go/stubs/wails/pkg/application/screen.go @@ -224,7 +224,7 @@ func (m *ScreenManager) GetCurrent() *Screen { return m.primary } -func (m *ScreenManager) LayoutScreens(screens []*Screen) error { +func (m *ScreenManager) LayoutScreens(screens []*Screen) resultFailure { m.SetScreens(screens) return nil } diff --git a/go/stubs/wails/pkg/application/window.go b/go/stubs/wails/pkg/application/window.go index db2c3a94..d2b341f1 100644 --- a/go/stubs/wails/pkg/application/window.go +++ b/go/stubs/wails/pkg/application/window.go @@ -190,7 +190,7 @@ type Window interface { // Screen and display // screen, err := w.GetScreen() - GetScreen() (*Screen, error) + GetScreen() (*Screen, resultFailure) // borders := w.GetBorderSizes() GetBorderSizes() *LRTB @@ -211,7 +211,7 @@ type Window interface { // w.Flash(true) Flash(enabled bool) // err := w.Print() - Print() error + Print() resultFailure // w.Error("something went wrong: %s", details) Error(message string, args ...any) // w.Info("window ready")