Skip to content

Commit 8125309

Browse files
ostermanClaude (via Conductor)claude
authored
feat: Add ui.Toast() pattern for status notifications (#1794)
* feat: Add ui.Toast() pattern for status notifications Introduce a new ui.Toast() and ui.Toastf() pattern for flexible toast-style status notifications with custom icons, providing a unified approach for all user-facing messages. New API: - ui.Toast(icon, message) - Toast with custom icon - ui.Toastf(icon, format, ...args) - Formatted toast with custom icon - Existing ui.Success(), ui.Error(), etc. now documented as convenience wrappers around the Toast pattern Documentation: - Updated docs/prd/io-handling-strategy.md with Toast pattern examples - Updated docs/io-and-ui-output.md API reference with Toast functions - Added comprehensive usage examples and guidelines Implementation: - Added ui.Toast() and ui.Toastf() to pkg/ui/formatter.go - Updated existing convenience functions with improved comments - All toast notifications route through stderr (UI channel) - Automatic secret masking and TTY detection Benefits: - Consistent pattern for all toast notifications - Flexible icon support (custom emojis or themed icons) - Automatic channel routing (stderr for UI) - Automatic secret masking - Cleaner, more maintainable code 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * feat: Add multiline toast support with automatic indentation Add support for multiline toast messages that automatically split on newlines and indent continuation lines to align with the first line text. Implementation: - Added formatToast() method to handle single and multiline messages - Calculates proper indentation based on icon rune count - Preserves empty lines in multiline messages - Handles unicode emojis correctly using rune count - Added iconMessageFormat constant to avoid string literal duplication Testing: - Added comprehensive tests for single-line toasts - Added tests for multiline toasts with various icons - Added tests for unicode width handling - Added integration tests for Toast() and Toastf() - All existing tests pass Documentation: - Updated docs/io-and-ui-output.md with multiline examples - Updated docs/prd/io-handling-strategy.md with usage patterns - Added visual output examples showing indentation Example usage: ui.Toast("✓", "Installation complete\nVersion: 1.2.3\nLocation: /usr/local/bin") Output: ✓ Installation complete Version: 1.2.3 Location: /usr/local/bin 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * test: Add comprehensive edge case tests for multiline toast Add extensive test coverage for multiline toast functionality including: Edge Cases: - Empty messages - Messages with only newlines - Messages starting/ending with newlines - Multiple consecutive newlines - Long multiline messages (5+ lines) - Special characters (tabs, spaces) - Unicode characters in messages Real-World Examples: - Installation success with details - Error messages with context - Progress updates with statistics - Configuration summaries Integration Tests: - Multiline support with convenience functions (Success, Error, Warning, Info) - Formatted multiline messages with Toastf - Multiple format argument types (string, int, float, bool) Error Handling: - Toast/Toastf behavior when formatter not initialized Test Results: - All 10 new test functions pass - 50+ individual test cases added - UI package coverage: 92.2% - No regressions in existing tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * feat: Add multiline support to convenience functions (Success, Error, Warning, Info) Enable all convenience functions to support multiline messages with colored icons: Implementation: - Refactored Success/Error/Warning/Info to use formatToast with colored icons - Added stripANSI() helper to calculate proper indentation with colored icons - ANSI color codes only applied to icons, not text (per design) - All *f variants (Successf, Errorf, etc.) delegate to base functions Key Changes: - Success/Error/Warning/Info now support multiline via formatToast - Colored icons properly handled in multiline layout - Indentation calculated on plain icon width (ANSI codes stripped) - Maintains backward compatibility with single-line messages Testing: - Added TestFormatter_ConvenienceFunctions_Multiline (4 test cases) - Added TestFormatter_ANSIStripping (6 test cases for ANSI handling) - Added TestFormatter_Successf_Multiline - Added TestFormatter_Errorf_Multiline - Added TestFormatter_Warningf_Multiline - Added TestFormatter_Infof_Multiline - All tests pass, coverage increased to 93.4% Example: ui.Success("Done\nAll tasks completed") // With color: // \x1b[32m✓\x1b[0m Done // All tasks completed ui.Errorf("Failed: %s\nReason: %s", "deploy", "timeout") // With color: // \x1b[31m✗\x1b[0m Failed: deploy // Reason: timeout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * refactor: Use lipgloss.Width() and simplify color handling - Replace custom stripANSI() with lipgloss.Width() for proper ANSI and emoji width calculation - Remove redundant SupportsColor() checks (styles handle color degradation automatically) - Simplify convenience functions (Success, Error, Warning, Info) to always use styled icons - Update test expectations for accurate emoji width (2 cells + 1 space = 3 total indent) - Add test for lipgloss.Width() behavior with ANSI codes and emojis Benefits: - More accurate width calculation for multi-cell characters (emojis, CJK) - Better integration with Charmbracelet ecosystem - Less code to maintain - Automatic color degradation through lipgloss styles * refactor: Separate formatting from I/O in UI package - UI functions now call ui.Format methods, then ui.Write() for output - Formatter has ZERO I/O responsibilities - only formats and returns strings - Added Toast() and Toastf() to Formatter interface for public API - Package-level functions follow pattern: format → write (not format+write) - Proper separation: formatter formats, terminal writes, UI orchestrates This follows 'option 2' pattern: ui.Success() → ui.Format.Success() → ui.Write() Benefits: - Clear separation of concerns (formatting vs I/O) - Formatter is pure (no side effects, testable without mocking I/O) - Consistent API pattern across all UI functions - Users can call Format methods directly for formatted strings without writing * test: Improve coverage for Format nil checks - Set Format = nil in TestPackageFunctions_NotInitialized - Now tests all nil check paths for Success, Error, Warning, Info functions - Coverage increased from 86.6% to 91.7% All toast and convenience functions now have 100% test coverage. * test: Use lipgloss.Width() in test to match production code - Updated TestFormatter_FormatToast_UnicodeWidth to use lipgloss.Width() - Previously used len([]rune(icon)) which doesn't match production width calculation - Now test correctly validates wide emoji handling (📦 = 2 cells, not 1 rune) This ensures test width model matches the formatter's actual behavior. --------- Co-authored-by: Claude (via Conductor) <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent 98e49cc commit 8125309

File tree

6 files changed

+969
-65
lines changed

6 files changed

+969
-65
lines changed

docs/io-and-ui-output.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,28 @@ See [Logging Guidelines](logging.md) for logging details.
8888

8989
### UI Messages (→ stderr)
9090

91+
**Toast Notifications** - Status messages with icons:
92+
9193
```go
94+
// Custom icons - flexible for any emoji or symbol
95+
ui.Toast("📦", "Using latest version: 1.2.3")
96+
ui.Toastf("🔧", "Tool %s is not installed", toolName)
97+
ui.Toastf("", "Set %s@%s in %s", tool, version, file)
98+
99+
// Multiline toasts - automatically indented
100+
ui.Toast("", "Installation complete\nVersion: 1.2.3\nLocation: /usr/local/bin")
101+
// Output:
102+
// ✓ Installation complete
103+
// Version: 1.2.3
104+
// Location: /usr/local/bin
105+
106+
ui.Toastf("📦", "Package: %s\nVersion: %s\nSize: %dMB", name, version, size)
107+
// Output:
108+
// 📦 Package: atmos
109+
// Version: 1.2.3
110+
// Size: 42MB
111+
112+
// Themed icons - convenience wrappers for common status types
92113
ui.Success("Done!") // ✓ Done! (green)
93114
ui.Successf("Loaded %d items", n) // ✓ Loaded 5 items (green)
94115

@@ -102,6 +123,14 @@ ui.Info("Processing...") // ℹ Processing... (cyan)
102123
ui.Infof("Found %d files", count) // ℹ Found 10 files (cyan)
103124
```
104125

126+
**Plain UI Text** - No icons or automatic styling:
127+
128+
```go
129+
ui.Write("Loading configuration...")
130+
ui.Writef("Processing %d items...", count)
131+
ui.Writeln("Done") // With newline
132+
```
133+
105134
### Data Output (→ stdout)
106135

107136
```go

docs/prd/io-handling-strategy.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,12 @@ package ui
305305
306306
// ===== Package-level functions (what developers use) =====
307307
308-
// UI channel output (stderr) - formatted messages with icons
308+
// Toast notifications (stderr) - status messages with custom or themed icons
309+
// This is the primary pattern for all toast-style notifications
310+
func Toast(icon, message string) error // {icon} {message} → stderr
311+
func Toastf(icon, format string, a ...any) error // {icon} {formatted} → stderr
312+
313+
// Convenience wrappers for common toast types (implemented as Toast calls)
309314
func Success(text string) error // ✓ {text} in green → stderr
310315
func Successf(format string, a ...any) error // ✓ {formatted} in green → stderr
311316
func Error(text string) error // ✗ {text} in red → stderr
@@ -411,11 +416,33 @@ ui.Write("Loading configuration...")
411416
ui.Writef("Processing %d items...", count)
412417
ui.Writeln("Done") // Automatic newline
413418
414-
// Formatted messages (with icons and colors)
419+
// Toast notifications with custom icons
420+
ui.Toast("📦", "Using latest version: 1.2.3")
421+
ui.Toastf("🔧", "Tool %s is not installed. Installing...", toolName)
422+
ui.Toastf("✓", "Set %s@%s in %s", tool, version, file)
423+
424+
// Multiline toast notifications - automatically indented
425+
ui.Toast("✓", "Installation complete\nVersion: 1.2.3\nLocation: /usr/local/bin")
426+
// Output:
427+
// ✓ Installation complete
428+
// Version: 1.2.3
429+
// Location: /usr/local/bin
430+
431+
ui.Toastf("📦", "Installed: %s\nVersion: %s\nSize: %dMB", name, version, size)
432+
// Output:
433+
// 📦 Installed: atmos
434+
// Version: 1.2.3
435+
// Size: 42MB
436+
437+
// Toast notifications with themed icons (convenience wrappers)
415438
ui.Success("Configuration loaded!")
439+
ui.Successf("Installed %s/%s@%s", owner, repo, version)
416440
ui.Error("Failed to load configuration")
441+
ui.Errorf("Install failed %s/%s@%s: %v", owner, repo, version, err)
417442
ui.Warning("Stack is deprecated")
443+
ui.Warningf("Slow operation took %s", duration)
418444
ui.Info("Processing 150 components...")
445+
ui.Infof("Found %d matching files", count)
419446
```
420447

421448
#### Pattern 3: Markdown (Context-Dependent)

0 commit comments

Comments
 (0)