From 186d9f7f4418f17571cb0cd334ff6651d51308b9 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 9 Apr 2026 13:00:23 +0300 Subject: [PATCH] fix(ui): route raw fmt.Print calls through proper renderers - event_handler: route default extension print level through DisplayInfo instead of bare fmt.Println for consistent styling and timestamps - factory: remove orphan fmt.Println("") before system messages; the renderer already manages its own spacing - app: PrintFromExtension non-interactive fallback now respects level, writing errors/info to stderr with prefix to keep stdout clean - app: PrintBlockFromExtension non-interactive fallback writes framed blocks to stderr instead of raw text to stdout --- internal/app/app.go | 22 ++++++++++++++++------ internal/ui/event_handler.go | 4 +++- internal/ui/factory.go | 4 +--- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 9ca2d3a7..c1566743 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -930,7 +930,8 @@ func (a *App) QuitFromExtension() { // controls styling: "" for plain text, "info" for a system message block, // "error" for an error block. In interactive mode it sends an // ExtensionPrintEvent through the program so the TUI can render it with the -// appropriate renderer. In non-interactive mode it falls back to stdout. +// appropriate renderer. In non-interactive mode it falls back to stderr with +// a level prefix so errors are distinguishable from plain output. func (a *App) PrintFromExtension(level, text string) { a.mu.Lock() prog := a.program @@ -939,8 +940,16 @@ func (a *App) PrintFromExtension(level, text string) { prog.Send(ExtensionPrintEvent{Text: text, Level: level}) return } - // Non-interactive fallback: write directly to stdout. - fmt.Println(text) + // Non-interactive fallback: write to stderr with a level prefix so that + // errors and info messages are distinguishable from plain output. + switch level { + case "error": + fmt.Fprintf(os.Stderr, "[ERROR] %s\n", text) + case "info": + fmt.Fprintf(os.Stderr, "[INFO] %s\n", text) + default: + fmt.Println(text) + } } // SetEditorTextFromExtension sends an EditorTextSetEvent to the TUI to @@ -1122,11 +1131,12 @@ func (a *App) PrintBlockFromExtension(opts extensions.PrintBlockOpts) { }) return } - // Non-interactive fallback. + // Non-interactive fallback: render a simple framed block to stderr so + // it is visually distinct from plain stdout output. if opts.Subtitle != "" { - fmt.Printf("%s\n — %s\n", opts.Text, opts.Subtitle) + fmt.Fprintf(os.Stderr, "--- %s ---\n%s\n", opts.Subtitle, opts.Text) } else { - fmt.Println(opts.Text) + fmt.Fprintf(os.Stderr, "---\n%s\n---\n", opts.Text) } } diff --git a/internal/ui/event_handler.go b/internal/ui/event_handler.go index 51d31623..ac74afe6 100644 --- a/internal/ui/event_handler.go +++ b/internal/ui/event_handler.go @@ -139,7 +139,9 @@ func (h *CLIEventHandler) Handle(msg tea.Msg) { case "block": h.cli.DisplayExtensionBlock(e.Text, e.BorderColor, e.Subtitle) default: - fmt.Println(e.Text) + // Route unstyled extension prints through the system message + // renderer so they get consistent formatting and timestamps. + h.cli.DisplayInfo(e.Text) } case app.StepCompleteEvent: diff --git a/internal/ui/factory.go b/internal/ui/factory.go index 36994876..7a8e85c9 100644 --- a/internal/ui/factory.go +++ b/internal/ui/factory.go @@ -109,9 +109,7 @@ func SetupCLI(opts *CLISetupOptions) (*CLI, error) { } } - fmt.Println("") - - // Display model info + // Display model info (the system message block provides its own spacing). if provider != "unknown" && model != "unknown" { cli.DisplayInfo(fmt.Sprintf("Model loaded: %s (%s)", provider, model)) }