Commit 8125309
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- docs
- prd
- pkg/ui
6 files changed
+969
-65
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
| 91 | + | |
| 92 | + | |
91 | 93 | | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
92 | 113 | | |
93 | 114 | | |
94 | 115 | | |
| |||
102 | 123 | | |
103 | 124 | | |
104 | 125 | | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
105 | 134 | | |
106 | 135 | | |
107 | 136 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
305 | 305 | | |
306 | 306 | | |
307 | 307 | | |
308 | | - | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
309 | 314 | | |
310 | 315 | | |
311 | 316 | | |
| |||
411 | 416 | | |
412 | 417 | | |
413 | 418 | | |
414 | | - | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
415 | 438 | | |
| 439 | + | |
416 | 440 | | |
| 441 | + | |
417 | 442 | | |
| 443 | + | |
418 | 444 | | |
| 445 | + | |
419 | 446 | | |
420 | 447 | | |
421 | 448 | | |
| |||
0 commit comments